AT&T syntax always requires a *
on indirect calls, like call *%rax
. This sets RIP = RAX.
call *(%rax, %rdi)
would load from memory like RIP = mem_load(RDI+RAX).
call *foo(%rip)
would load a new RIP from memory at the address foo
, using a RIP-relative addressing mode.
When it's missing a *
but still unambiguously an indirect call (because of a register operand or a (%reg)
addressing mode), the assembler will infer that and warn you about it.
That AT&T syntax design avoids ambiguity between call foo
(direct) and call *foo
(memory indirect with an absolute-direct addressing mode).
Normally in 64-bit mode you'd use call *foo(%rip)
, but the ambiguity would still exist if call foo
could mean memory indirect. And of course the syntax was designed before AMD64 existed.
In Intel syntax, memory-indirect call that loads a pointer into EIP/RIP from memory at the address foo
with absolute addressing would be:
GAS Intel syntax: call [foo]
, call qword ptr foo
, or call ds:foo
MASM: call [foo]
might ignore the brackets so only qword ptr
or ds:
work.
NASM: call [foo]
or call qword [foo]
So as you can see, MASM-style Intel-syntax (as used by GAS .intel_syntax noprefix
) uses ds:
or qword ptr
to indicate that something is a memory operand, allowing call foo
to be a normal E8 rel32
call direct.
Of course, as I said, in 64-bit mode you'd normally want to use call [RIP + foo]
in GAS or call [rel foo]
in NASM. (In actual MASM, I think RIP-relative is on by default.)