我查看了我的c代码的反汇编,发现指向函数的指针实际指向jmp
指令,并没有指出函数在内存中的真正开始(不是吗?点push ebp
指令,表示函数框架的开始)。
我有跟随的功能(基本上没什么,它只是一个例子):
int func2(int a, int b)
{
return 1;
}
我尝试打印功能的地址 - printf("%p", &func2);
我查看了代码的反汇编,发现打印的地址是汇编代码中jmp
库的地址。我想得到代表函数框架开始的地址。有没有办法从jmp
指令的给定地址计算它?
此外,我有代表jmp
指令的字节。
011A11EF E9 CC 08 00 00 jmp func2 (011A1AC0h)
如何从内存中找到表示函数框架开始的地址(在这种情况下为011A1AC0h
),仅从jmp
指令的地址和字节中获取代表jmp
指令本身?我读了一些有关它的信息,我发现它是相对jmp
,这意味着我需要将jmp
保存的值添加到jmp
指令本身的地址。不确定这是否是解决方案的良好方向,如果是,我如何获得jmp
持有的价值?
答案 0 :(得分:2)
E9 16 是用于具有 rel32 偏移的jmp指令的Intel 64和IA-32操作码。接下来的四个字节包含偏移量。你的反汇编程序将它们显示为“CC 08 00 00”,但这是相反的;偏移量为000008CC 16 ,即2252 10 。偏移量是带符号的32位值,添加到EIP寄存器以获取跳转目标的地址。 EIP包含要执行的下一条指令的地址。
因此,在这种特定情况下,取字节的地址超出跳转指令并添加32位偏移量。
然而:
我在Intel 64和IA-32手册中统计了11种形式的jmp指令。谁知道当您对源代码或编译器开关进行轻微更改并重新编译时编译器可能会使用什么?您需要准备解码任何形式的jmp指令,或者编译器可能使用的其他指令。
英特尔在其架构中具有一些传统细分功能。您系统上的代码段可能是一件大事,因此您不必担心,但我无法提供保证。
您的编译器可能已使用此jmp指令作为为指针创建值的便捷方式,而不是使用例程的入口点(函数执行通常开始的指令的正确术语,而不是帧),因为它使链接器执行重定位工作,而不是要求编译器插入指令以在运行时执行该工作(具体地,在必须对函数地址进行求值以便将其分配给指针时)。这有点猜测,但编译器可能会在下次做其他事情。你正在正常计算之外跋涉。
答案 1 :(得分:1)
我不确定是否提出您的问题,但请this sample:
#include <stdio.h>
int foo(int x)
{
return x+1;
}
int main(int argc, char** argv)
{
printf("foo = %p\n", foo);
return 0;
}
产生以下反汇编:
foo(int):
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
addl $1, %eax
popq %rbp
ret
.LC0:
.string "foo = %p\n"
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl foo(int), %esi # pass the label argument (2) to printf
movl $.LC0, %edi # pass the format argument (1) to printf
movl $0, %eax
call printf
movl $0, %eax
leave
ret
如您所见,只有标签传递给printf。此标签由编译器解析为地址。
另请注意,您很难获得正在运行的二进制文件的绝对地址:ASLR (Address Space Layout Randomization将为二进制文件选择随机基址。二进制内部的偏移仍然存在,因此相对调用。
答案 2 :(得分:0)
在X86机器上,E9是JMP rel16 / 32的操作码。所以cpu将使用值0x000008CC作为跳转偏移量。基址是JMP指令后的指令地址。