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
)