为什么 gdb 在 RIP 相对模式下显示与绝对地址不同的地址?
Why gdb shows different addresses in RIP-relative mode from absolute address?
在 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 [=11=]x0,%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 [=11=]x0,%eax
0x0000555555555191 <+60>: callq 0x555555555040 <printf@plt>
0x0000555555555196 <+65>: mov [=11=]x0,%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 [=13=], %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 [=13=], %eax #,
call printf@PLT #
# a.c:8: return 0;
movl [=13=], %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-relative模式是:
# a.c:6: scanf("%i",&x);
leaq x(%rip), %rsi #,
其中 x
是 x
符号的位置。但是在评论里,有人说,$rip+0x2ed8
不一样,偏移量0x2ed8
并没有指向x
的地址。但是为什么这两者不同呢?但应该是 RIP 相对模式寻址,并且两者都应该获得相同的偏移量(以及地址)。
0x0000555555555165 <+16>: lea 0x2ed8(%rip),%rsi # 0x555555558044 <x>
0x000055555555516c <+23>: lea 0xe9a(%rip),%rdi # 0x55555555600d
指令中的 RIP 相对地址是相对于 当前指令之后的地址(即指令的地址加上指令的大小,或地址以下说明)。这是因为当指令被加载到处理器中时,RIP 寄存器会在执行之前提前当前指令的大小。 (至少这是遵循的模型,即使现代处理器在幕后使用各种技巧来加速执行。)(注意:以上内容适用于几种 CPU 架构,包括 x86 变体,但一些其他 CPU 架构在测量 PC 相关地址的点上有所不同1。)
上面的第一条指令在地址0x555555555165,后面的指令在地址0x55555555516c(指令长7字节)。在第一条指令中,RIP相对地址0x2ed8(%rip)
指的是0x2ed8 + 0x000055555555516c = 0x555555558044.
请注意,如果您在调试器中的指令上设置断点并在到达断点时显示寄存器,RIP 将指向当前指令,而不是下一条指令,因为当前指令尚未执行.
1 感谢 Peter Cordes 提供有关 ARM 和 RISC-V CPU 架构的 PC 相对寻址的详细信息。
在 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 [=11=]x0,%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 [=11=]x0,%eax
0x0000555555555191 <+60>: callq 0x555555555040 <printf@plt>
0x0000555555555196 <+65>: mov [=11=]x0,%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 [=13=], %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 [=13=], %eax #,
call printf@PLT #
# a.c:8: return 0;
movl [=13=], %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-relative模式是:
# a.c:6: scanf("%i",&x);
leaq x(%rip), %rsi #,
其中 x
是 x
符号的位置。但是在评论里,有人说,$rip+0x2ed8
不一样,偏移量0x2ed8
并没有指向x
的地址。但是为什么这两者不同呢?但应该是 RIP 相对模式寻址,并且两者都应该获得相同的偏移量(以及地址)。
0x0000555555555165 <+16>: lea 0x2ed8(%rip),%rsi # 0x555555558044 <x>
0x000055555555516c <+23>: lea 0xe9a(%rip),%rdi # 0x55555555600d
指令中的 RIP 相对地址是相对于 当前指令之后的地址(即指令的地址加上指令的大小,或地址以下说明)。这是因为当指令被加载到处理器中时,RIP 寄存器会在执行之前提前当前指令的大小。 (至少这是遵循的模型,即使现代处理器在幕后使用各种技巧来加速执行。)(注意:以上内容适用于几种 CPU 架构,包括 x86 变体,但一些其他 CPU 架构在测量 PC 相关地址的点上有所不同1。)
上面的第一条指令在地址0x555555555165,后面的指令在地址0x55555555516c(指令长7字节)。在第一条指令中,RIP相对地址0x2ed8(%rip)
指的是0x2ed8 + 0x000055555555516c = 0x555555558044.
请注意,如果您在调试器中的指令上设置断点并在到达断点时显示寄存器,RIP 将指向当前指令,而不是下一条指令,因为当前指令尚未执行.
1 感谢 Peter Cordes 提供有关 ARM 和 RISC-V CPU 架构的 PC 相对寻址的详细信息。