Home > Software engineering >  What is the problem with this jump instruction?
What is the problem with this jump instruction?

Time:11-04

I was given the following instructions:

  1. Make the first instruction in your code a jmp
  2. Make that jmp a relative jump to 0x51 bytes from its current position
  3. At 0x51 write the following code:
  4. Place the top value on the stack into register RDI
  5. jmp to the absolute address 0x403000

What I did so far is:

.intel_syntax noprefix
    .global _start
    _start:
        jmp $ 0x51
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        pop rdi
        mov eax, 0x403000
        jmp rax

Isn't this exactly what is required by the assignment? Is there a problem with the relative jump or the absolute jump?

EDIT:

I tried to change the absolute jump with the following, but still no result:

    . . .
    nop
    pop rdi
    mov rax, 0x403000
    jmp fword ptr [eax]

If it can help, this is the object dump derived from the assembled file.

CodePudding user response:

$ 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.

CodePudding user response:

The solution is somewhat irritating: the instructions used are actually correct, even the absolute jump using a simple register. It is the text of the request that is incorrect or otherwise ambiguous, the pop instruction is requested to be at position 0x51, but what the sentence really means (according to the organizer) is that there must be 0x51 nop instructions.

This means that the total and exact number of nops is 81 and that the offset of the first jump (in addition to taking into account the size of the jmp instruction itself) must take into account the added nops, so it becomes jmp $ 0x53.

  • Related