$+0x51
looks like NASM syntax. For GAS you'd want jmp . + 0x51
. But it turns out jmp $+0x51
happens to assemble correctly with GAS without even a warning. I don't know how GAS is interpreting the $
in Intel-syntax noprefix mode! See the GAS manual re: the "special dot symbol". The manual says you can even assign to it to skip bytes in the output, like . = . + 0x51-2
. (-2
because the jump itself was 2 bytes.)
clang
rejects $+0x51
, but strangely also rejects . + 0x51
. If the auto-grader script is running on a Mac, you might need to do something else.
Other than that, your code looks correct to me; that's how I'd interpret that assignment. You should email your instructor to ask what's wrong with your code and why the auto-grader program isn't accepting it. Are you sure they want 64-bit code? You originally tagged this [nasm], do they want NASM source instead of GAS? Is that why you used $
instead of .
for the current position?
There are a couple minor ambiguities in the instructions, like "Make that jmp a relative jump to 0x51 bytes from its current position"; x86 relative jumps are relative to the end of the instruction. So do they perhaps want jmp . + 0x53
to jump 0x53 bytes forward from the start of the jmp, which is 0x51
forward from the end of the jump, with machine code EB 51
(https://felixcloutier.com/x86/jmp).
But they say to put code "at 0x51", which implies that offset within the section, and jumping to .+0x53
would thus jump into the middle of an instruction and do weird things. So I think they do mean that you should jump to 0x51
bytes ahead of the start of the jmp
instruction, like you're doing.
When I assemble+link it into a static executable for Linux (gcc -nostdlib -static foo.S
) and run it under gdb ./a.out
(then layout reg
/ layout next
/ starti
/ stepi
), I see a relative jump to 0x401051
, skipping past all the NOPs. The next instruction is pop
of argc into RDI, then mov
+jmp
leaving RIP=0x403000. (And then stepping again raises SIGSEGV, because code fetch from that address faulted.)
So it works for me, doing all the things the assignment says it should.
You don't need to write out each NOP separately
I'd have used .rept 0x51-2
; nop
; .endr
or something. Or .fill 0x51-2, 1, 0x90
to fill with 0x4f repeats of a 1-byte pattern with value 0x90
(nop). (https://sourceware.org/binutils/docs/as/Fill.html).
Or fill with zeros since you're jumping over them. (.space
or .zero
).
Also you could just put a label at the branch target and jmp after_padding
! That would be a way to work around clang's built-in assembler not handling jmp . + 0x51
.
You linked Absolute indirect far jump syntax in GAS x86_64 asm but that's about far jumps. You don't want to jump to a new CS:RIP (or CS:EIP like you wrote with 6-byte FWORD
). And the assignment is telling you to jump "to 0x403000", not to load a function pointer from there.
The normal way to do a near jump to an absolute address is indeed mov reg, imm32
/ jmp reg
. There are other ways like push/ret but that's less efficient. For example:
Call an absolute pointer in x86 machine code - in GAS and NASM, jmp 0x403000
Just Works, assuming you link it into a non-PIE executable so that destination is in range of the jmp rel32
. (which can reach +-2GiB)
That's a relative jump to reach that absolute address (from a known code address, so it doesn't work with ASLR), but it's unclear if that's what they want.
How to jump to memory location in intel syntax for both x64 and x32 - jmp qword ptr [RIP+destination]
; destination: .quad 0x403000
to do a memory-indirect jump with a RIP-relative addressing mode to load an 8-byte pointer into RIP. You can even put the pointer right after the jmp
, instead of in .rodata
.
JMP to absolute address (op codes) - shows the inefficient push/ret way first, without pointing out the efficiency problem with branch prediction.