assemblygdbx86-64disassemblyrelative-addressing

Why gdb shows different addresses in RIP-relative mode from absolute address?


Having this in c:

#include <stdio.h>
#include <stdlib.h>
int x;
int main(){
    printf("eneter x\n");   
    scanf("%i",&x);
    printf("you enetered: %i\n", x);
    return 0;
}

in gdb:

starti
disas main

0x0000555555555155 <+0>:    push   %rbp
   0x0000555555555156 <+1>: mov    %rsp,%rbp
   0x0000555555555159 <+4>: lea    0xea4(%rip),%rdi        # 0x555555556004
   0x0000555555555160 <+11>:    callq  0x555555555030 <puts@plt>
   0x0000555555555165 <+16>:    lea    0x2ed8(%rip),%rsi        # 0x555555558044 <x>
   0x000055555555516c <+23>:    lea    0xe9a(%rip),%rdi        # 0x55555555600d
   0x0000555555555173 <+30>:    mov    $0x0,%eax
   0x0000555555555178 <+35>:    callq  0x555555555050 <__isoc99_scanf@plt>
   0x000055555555517d <+40>:    mov    0x2ec1(%rip),%eax        # 0x555555558044 <x>
   0x0000555555555183 <+46>:    mov    %eax,%esi
   0x0000555555555185 <+48>:    lea    0xe84(%rip),%rdi        # 0x555555556010
   0x000055555555518c <+55>:    mov    $0x0,%eax
   0x0000555555555191 <+60>:    callq  0x555555555040 <printf@plt>
   0x0000555555555196 <+65>:    mov    $0x0,%eax
   0x000055555555519b <+70>:    pop    %rbp
   0x000055555555519c <+71>:    retq 

here the relative address of x variable is $rip+0x2ed8 (from instruction lea 0x2ed8(%rip),%rsi # 0x555555558044). But as you can see in the comment #, the absolute address is 0x555555558044. Ok will I get that address when try to read from the relative one? Lets see:

x $rip+0x2ed8
0x555555558055: 0x00000000

nop - relative address did not use the absolute address, where the x var is really stored (0x555555558055 != 0x555555558044) the difference is 17 bytes. Is it the number of bytes of the instruction itself (lea + operands)? I do not know, but do not think so. So why does relative and absolute addressing differ in gdb?

PS, generated assembly:

.file   "a.c"
    .comm   x,4,4
    .section    .rodata
.LC0:
    .string "eneter x"
.LC1:
    .string "%i"
.LC2:
    .string "you enetered: %i\n"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp    #
    movq    %rsp, %rbp  #,
# a.c:5:    printf("eneter x\n");   
    leaq    .LC0(%rip), %rdi    #,
    call    puts@PLT    #
# a.c:6:    scanf("%i",&x);
    leaq    x(%rip), %rsi   #,
    leaq    .LC1(%rip), %rdi    #,
    movl    $0, %eax    #,
    call    __isoc99_scanf@PLT  #
# a.c:7:    printf("you enetered: %i\n", x);
    movl    x(%rip), %eax   # x, x.0_1
    movl    %eax, %esi  # x.0_1,
    leaq    .LC2(%rip), %rdi    #,
    movl    $0, %eax    #,
    call    printf@PLT  #
# a.c:8:    return 0;
    movl    $0, %eax    #, _6
# a.c:9: }
    popq    %rbp    #
    ret 
    .size   main, .-main
    .ident  "GCC: (Debian 8.3.0-6) 8.3.0"
    .section    .note.GNU-stack,"",@progbits

Here, the RIP-relative mode is used:

# a.c:6:    scanf("%i",&x);
    leaq    x(%rip), %rsi   #,

where the x is position of the x symbol. But in comments, someone said, that $rip+0x2ed8 is not the same, and the offset 0x2ed8 does not lead to the address of the x. But why those two differ? but should be RIP-relative mode addressing and both should gain the same offset (and thus address).


Solution

  •    0x0000555555555165 <+16>:    lea    0x2ed8(%rip),%rsi        # 0x555555558044 <x>
       0x000055555555516c <+23>:    lea    0xe9a(%rip),%rdi        # 0x55555555600d
    

    A RIP relative address in an instruction is relative to the address just after the current instruction (i.e. the address of the instruction plus the size of the instruction, or the address of the following instruction). This is because when the instruction has been loaded into the processor, the RIP register is advanced by the size of the current instruction just before it is executed. (At least that is the model that is followed even though modern processors use all sorts of tricks behind the scenes to speed up execution.) (Note: The above is true for several CPU architectures, including x86 variants, but some other CPU architectures differ in the point from which PC-relative addresses are measured1.)

    The first instruction above is at address 0x555555555165 and the following instruction is at address 0x55555555516c (the instruction is 7 bytes long). In the first instruction, the RIP relative address 0x2ed8(%rip) refers to 0x2ed8 + 0x000055555555516c = 0x555555558044.

    Note that if you set a breakpoint on an instruction in a debugger and show the registers when the breakpoint is reached, RIP will point to the current instruction, not the next one, because the current instruction is not being executed yet.


    1 Thanks to Peter Cordes for details about PC-relative addressing for ARM and RISC-V CPU architectures.