1

In assembly if I write (at&t syntax, x86-64):

jmpq 0x4(%rip)

Then it jumps to 4+rip

But to where it jumps if I write:

jmpq label(%rip)

Where label = 4?

I'm confused because it can be the same answer or the same as movq where we move label and NOT label + rip.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Dan
  • 99
  • 6
  • If `label` is defined as a constant e.g. using `.equ` then it will behave exactly as if you used `4`. Otherwise the assembler will adjust the offset. Note this works the same for `mov`. – Jester Oct 06 '21 at 16:14
  • @Jester sorry I meant if label is a label like this label: in .text – Dan Oct 06 '21 at 16:18
  • @CherryDT why you added both? in mov it ignore rip... – Dan Oct 06 '21 at 16:26

1 Answers1

2

To do a normal relative jump that results in RIP = label, use jmp label.
The assembler calculates the relative displacement.

(And yes, you must use jmp not jmpq. An operand-size suffix for jmp implies an indirect jump in AT&T syntax, unlike with calll / callq, which is an insane and confusing bad design.)

Relative direct jumps work the same as they have since 8086, and don't have anything to do with the RIP-relative data addressing mode that was new in x86-64. (How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? also covers how the AT&T syntax works for 4(%rip) vs. label(%rip) for data addressing.)

You don't want to load a new RIP from a function pointer, so don't use a memory addressing mode. A relative jmp doesn't access memory itself; the memory at label only gets accessed by code-fetch after the jmp finishes. (Real CPUs are pipelined with branch prediction and stuff, but still maintain the illusion of instructions fully executing before the next one starts, e.g. a relative jmp to an unmapped page can fully execute without itself faulting, only the next code-fetch operation faults with saved RIP = the new location.)

Then jmpq 0x4(%rip) jumps to 4+rip

No, it doesn't. If you actually assemble that, it warns indirect jmp without '*'. Disassembly shows

ff 25 04 00 00 00       jmp    *0x4(%rip)

i.e. it loads a pointer from memory into RIP, from the qword that starts 4 bytes after the end of the jmp instruction. It doesn't set RIP = RIP+4, that would be jmp .+2 + 4 (because a short jump is 2 bytes long, or if you put a label after the jmp, jmp end_of_insn + 4)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • So to summarize this will jump to what: jmpq my_int_var(%rip)? where my_int_var=4? – Dan Oct 07 '21 at 17:44
  • @Dan: You mean if you used `my_int_var=4` in the asm source, which is the same thing as `.equ my_int_var, 4` to set the symbol address to `4`? Turns out that's the same as doing `jmpq 4(%rip)`, rather than using a RIP-relative addressing mode to reach absolute address 4. You can and should try this yourself with `gcc -c foo.s` && `objdump -drwC foo.o`. It warns about an indirect jump without `*`. – Peter Cordes Oct 07 '21 at 19:08
  • @Dan: unless the definition of `my_int_var=4` appears after the `jmp` in the source, in which case you get a relocation in the object file, and upon linking you do get `401000: ff 25 fe ef bf ff jmp *-0x401002(%rip) # 4 ` which loads a pointer from address `4`, which it reached with a RIP-relative addressing mode. GAS is weird like that I guess because it's a 1-pass (over the source) assembler. [Distinguishing memory from constant in GNU as .intel\_syntax](https://stackoverflow.com/q/39355188) is the basically the same issue. – Peter Cordes Oct 07 '21 at 19:17