0

I just cant figure out how to add an offset to my destination when moving a value, specifically in Intel syntax I have:

MOV   [gdtr + 2], EAX

and for AT&T syntax I have tried converting it to:

movl %eax, gdtr(2,1)

which gives an error when compiling junk '(2,1)' after expression but using just gdtr(,1) works fine.

I don't understand why I cant use base offset, and only the scale factor.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
drewpel
  • 465
  • 1
  • 3
  • 16

2 Answers2

2

Simply write

movl %eax, gdtr+2

Base-offset addressing only works when the offset is a register. There's no point in having an addressing mode to add two constants together; the way this works (regardless of syntax) is that the assembler / linker resolve symbol+constant into a a single number for the displacement field of the instruction encoding.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • You can also switch your assembler to intel syntax. – Jester Apr 07 '20 at 22:37
  • Thanks for the help! I didn't realize you could do it like this in AT&T. And yes I suppose I could rewrite my code in Intel style, but I already have a good amount in AT&T. Thanks everyone :) – drewpel Apr 07 '20 at 23:06
1

It's just gdtr+2

gdtr(2,1) [gives an error] but using just gdtr(,1) works fine.

The stuff inside the () parens in an AT&T addressing mode can only be registers (and a scale factor): disp(base, index, scale). Base and index are optional so empty is fine but invalid (non-register) is not.

And when you specify a scale with no base or index, apparently you must use only one comma: (,,1) is an error about an empty scale factor.
You could write it as gdtr+2(,1) to explicitly use no registers.

The +2 is part of the displacement in an addressing mode, not the base or index registers, regardless of syntax. See A couple of questions about [base + index*scale + disp] or Intel or AMD's manual about how addressing modes are encoded. (What you're doing is the [disp32] or [disp16] addressing mode, in terms of how it's going to be encoded into machine code.)

As Nate pointed out, the linker takes care of turning assemble-time literal constants + link-time-constant symbol addresses into a final address in the machine code, encoded as a disp32 (or disp16 for 16-bit address size). Or RIP-relative rel32 for x86-64.

Fun fact: some AT&T assemblers accept (gdtr) as an alternative to gdtr, but not 2(gdtr).


Ways to solve this yourself:

Normally if you're stuck on NASM -> AT&T you can assemble NASM source (e.g. nasm -felf) and disassemble with an AT&T disassembler like objdump -drwC. But that doesn't help here for symbolic addressing mode syntax because at best objdump -dr just annotates numeric addressing modes with symbol name info.

So in this case your best bet is to get GCC or clang to emit an instruction that uses a symbol name and a numeric constant, like this

char gdtr[1024];                // global var so it has a symbol
char foo() { return gdtr[2]; }  // load from global symbol + constant.

compiles with gcc9.3 -O2 -m32 on the Godbolt compiler explorer to this asm:

foo:
        movzbl  gdtr+2, %eax
        ret
gdtr:
        .zero   1024

There's your addressing mode, and as a bonus the AT&T mnemonic for movzx with a byte source. Of course you can fiddle with the types.

Compilers are useful resources; they know how to do lots of things "the normal way" when compiling simple C functions, and they know the calling convention and type widths. And AT&T syntax for everything including function pointers. If you're stuck, ask a compiler. Basically the only thing you can't get a compiler to show you is the syntax for jmp far (AT&T ljmp)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847