我有这个函数指针和代码:
0x0000555555556e80 <+0>: push %rbp
0x0000555555556e81 <+1>: mov %rsp,%rbp
0x0000555555556e84 <+4>: sub $0x10,%rsp
0x0000555555556e88 <+8>: movl $0x0,-0x4(%rbp)
0x0000555555556e8f <+15>: movslq -0x4(%rbp),%rcx
0x0000555555556e93 <+19>: lea 0x7406(%rip),%rax # 0x55555555e2a0 <init_functions>
0x0000555555556e9a <+26>: cmpq $0x0,(%rax,%rcx,8)
0x0000555555556e9f <+31>: je 0x555555556ec1 <initialize_bomb+65>
0x0000555555556ea5 <+37>: movslq -0x4(%rbp),%rcx
0x0000555555556ea9 <+41>: lea 0x73f0(%rip),%rax # 0x55555555e2a0 <init_functions>
0x0000555555556eb0 <+48>: call *(%rax,%rcx,8)
0x0000555555556eb3 <+51>: mov -0x4(%rbp),%eax
0x0000555555556eb6 <+54>: add $0x1,%eax
0x0000555555556eb9 <+57>: mov %eax,-0x4(%rbp)
0x0000555555556ebc <+60>: jmp 0x555555556e8f <initialize_bomb+15>
0x0000555555556ec1 <+65>: add $0x10,%rsp
0x0000555555556ec5 <+69>: pop %rbp
0x0000555555556ec6 <+70>: ret
这是一个迭代11次的循环,我真的不知道如何处理函数指针。当有call *(%rax,%rcx,8)
时,涉及到的两个寄存器(RAX和RCX)发生变化,但我不明白如何或为什么发生变化,因为我不知道在那个调用中发生了什么...
我不能设置断点。我不知道该怎么做。
3条答案
按热度按时间mtb9vblg1#
call *address
将函数指针从内存加载到RIP,使用*
后面的寻址模式(或寄存器名称)的标准AT&T语法。请参见 * What does an asterisk * before an address mean in x86-64 AT&T assembly?。因此,这将推送返回地址,然后从地址[rax + rcx*8]
加载新的RIP。call *foo
语法(EIP/RIP = dword/qword,从foo
的内存中加载,内存间接)有一个星号,以与call foo
(RIP =foo
的地址,直接call rell32
)区分开来,以防您只使用一个裸符号名作为寻址模式。在64位模式下,您通常会使用
call *foo(%rip)
表示不在数组中的静态函数指针,但AT&T语法是在x86-64存在很久之前设计的,64位模式仍然存在这种模糊性。(在所有其他情况下,如果遗漏了*
,GAS将发出警告,并推断如果您写类似call (%rax)
或call %rax
的内容,您指的是间接跳转/调用。)RAX和RCX被调用重命名,因此它们不保持其值是正常的;请注意它们在
call
之前的值是如何来自莱亚以及如何从堆栈上的本地加载的。(What registers are preserved through a linux x86-64 function call *)如果您想查看调用了哪些函数,请使用GDB
stepi
(akasi
)单步执行调用。(在此函数中的某个位置放置一个断点,以便您可以从那里单步执行。)如果您想了解循环,请查看
call
周围的代码。RIP相关莱亚将恒定地址放入RAX;正如fjs所指出的,有一个符号名为
init_functions
。RCX是从堆栈上的一个局部变量加载的,符号扩展从32位到64位。看看周围的代码,这显然是一个循环计数器,在函数中初始化为零。可能是一个
int
。在调用之前,同样的数组索引被用来检查它是否是一个NULL指针。这显然是debug-mode compiler output,其中每个C语句被编译成一个单独的asm块。这意味着你只需要在本地查看一个块在隔离状态下做什么,但是这会导致比必要的代码多得多的代码。例如对阵列的两次访问,并且每次都重做循环计数器的符号扩展。
像这样的一些事情是足够简单的,整个循环很容易在一个优化的构建中遵循。愚者会旋转循环,使条件位于底部,部分剥离它。并在保存RBX(收缩 Package 优化)之前检查第一个条件,它使用RBX将指针保存到数组中。(而不是使用指针和单独的整数索引)。
Godbolt
niknxzdl2#
此指令后面的注解告诉您rax的值:
<+41>: lea 0x73f0(%rip),%rax # 0x55555555e2a0 <init_functions>
关于%rcx,看看你的反汇编代码中从-0x4(%rbp)加载或存储到-0x4(%rbp)的任何地方。它是一个用作循环计数器的局部变量,初始化为0,每次迭代增加1。代码看起来很像这样:
for(int idx=0; init_functions[idx]!=NULL; ++idx) init_functions[idx]();
每个函数指针的长度为8个字节,因此您可以调用%rax + 0x8 * %rcx
(感谢Peter Cordes对%rcx含义的建议。)
dba5bblo3#
跳转地址是%rax+%rcx*8,不管你的环境是什么。注意%rax是% rip相对的,%rcx是从堆栈加载的。