为什么gdb在RIP相对模式下显示的地址与绝对地址不同?

时间:2020-06-12 10:00:47

标签: c debugging gdb disassembly

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;
}

在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 

此处x变量的相对地址为$rip+0x2ed8(来自指令lea 0x2ed8(%rip),%rsi # 0x555555558044)。但是,正如您在注释#中看到的那样,绝对地址为0x555555558044。好吧,当您尝试从亲戚那里读时,我会得到那个地址吗?让我们看看:

x $rip+0x2ed8
0x555555558055: 0x00000000

nop-相对地址未使用绝对地址,实际上存储了x var(0x555555558055!= 0x555555558044)的绝对地址,相差17个字节。指令本身的字节数(lea +操作数)吗?我不知道,但不这么认为。那么,为什么相对寻址和绝对寻址在gdb中有所不同?

PS,生成的程序集:

.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

在此,使用相对于RIP的模式

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

其中xx符号的位置。但是在评论中,有人说$rip+0x2ed8是不相同的,并且偏移量0x2ed8不会导致x的地址。但是为什么这两个不同?但应为RIP相对模式寻址,并且两者都应获得相同的偏移量(并因此获得地址)。

1 个答案:

答案 0 :(得分:1)

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

一条指令中的RIP相对地址是相对于当前指令之后 的地址(即该指令的地址加上指令的大小,或后一条指令的地址)。这是因为当指令已加载到处理器中时,RIP寄存器将在执行前将当前指令的大小提前。 (即使现代处理器在后台使用各种技巧来加快执行速度,也至少要遵循该模型。)(注:上面的内容适用于包括x86变体在内的几种CPU架构,但其他一些CPU架构也有所不同相对于PC相对地址的测量点 1 。)

上面的第一条指令在地址0x555555555165处,随后的指令在地址0x55555555516c(该指令长7个字节)。在第一条指令中,RIP相对地址0x2ed8(%rip)表示0x2ed8 + 0x000055555555516c = 0x555555558044。

请注意,如果您在调试器中的一条指令上设置了断点并在达到断点时显示寄存器,则RIP将指向当前指令,而不是下一条指令,因为当前指令尚未执行。 / p>


1 感谢Peter Cordes提供了有关ARM和RISC-V CPU体系结构的PC相对寻址的详细信息。