1

我想知道如何使用内联汇编检索调用我的函数的函数的地址。我的想法是获取调用 mine 的函数将返回的地址,并使用它之前的指令(即调用 mine 的函数)检索调用 mine 的函数的地址,并将其添加到给定的偏移量调用,到下一条指令的地址(调用我的函数将返回的地址)。到目前为止,我能够做到这一点,但要得到我的地址。它相当简单并且有效:

_asm
{
    mov eax, [ebp+4]
    mov returnTo,eax
}

long addressOfMine = (*((long*)(returnTo - sizeof(long)))) + returnTo)

这可以很好地检索我的地址。(通过知道 [ebp+4] 是我的返回地址)

为了做同样的事情,但在上面一步,我试图获取旧的 ebp 并做同样的事情。我在一个站点上看到 [ebp+0] 是旧的 ebp,所以我尝试了:

_asm
{ 
    mov eax, [ebp]
    mov ebx, [eax+4]
    mov returnTo,ebx
}

long addressOfCaller = (*((long*)(returnTo - sizeof(long)))) + returnTo)

但它不起作用。所以,我的假设是错误的,或者我做错了什么,所以我想请你帮忙。

4

5 回答 5

6

好的,只要你知道你正在做一些非便携式的事情。你知道吗?

我的意思是,这不是十几个人还没有说过...

所以。对于 x86(但不是 X64)上的函数,当帧指针省略和其他优化未启用时,这应该可以工作。不适用于所有调用约定,但它应该适用于标准 C/C++ 调用约定。

void ** puEBP = NULL;
__asm { mov puEBP, ebp };
void * pvReturn = puEBP[1]; // this is the caller of my function

puEBP = (void**)puEBP[0];    // walk back to the previous frame
void * pvReturn2 = puEBP[1]; // this is the caller's caller

编辑:好的,我现在很困惑。我再次查看了您的问题,据我所知,您的第一个代码片段在功能上与我刚刚编写的相同。但是您说该代码为您提供了函数的地址-但这不应该是正确的。该代码片段应该将调用者的地址返回给您的函数。

edit2:添加代码以获取调用者的调用者。

顺便说一句,您在问题中显示的这段代码

long addressOfCaller = (*((long*)(returnTo - sizeof(long)))) + returnTo)

不会工作。它基于这样的假设,即拨打电话的唯一方法是

call symbol

其中符号是函数的 4 字节绝对地址。但这不是拨打电话的唯一方式。也可以间接调用

mov eax, symbol
call [eax]

或者

call [ref_symbol]

并且也可以相对于当前指令调用

call +12

但是你不需要这样做,一旦你知道调用函数中的任何地址,你就可以使用调试帮助库找到调用你的函数的地址。

为了使用 DebugHelp,您必须拥有代码的调试符号。然后只需使用 SymFromAddr

于 2010-01-31T03:40:14.020 回答
2

为什么不直接使用你的操作系统提供的功能呢?这是一对链接如何在 C 中获取堆栈跟踪?

您可以尝试更复杂的http://www.yosefk.com/blog/getting-the-call-stack-without-a-frame-pointer.html

但是您必须知道,在 x86 模式下调用约定并不容易,这取决于一些因素,例如您的操作系统、编译器、使用的调用约定等。

于 2010-01-31T02:53:44.523 回答
1

这并不容易。您必须知道调用者有多少个参数和局部变量,在大多数情况下,以编程方式计算出来并非易事。如果您做出可能错误的假设,即编译器保留EBP为堆栈帧持有者并且从不更改它(如,这可能不适用于 -O3/2/1)

在被调用函数内部,您可以执行类似的操作

mov ecx,ebp ;save our current stack frame
pop ebp ;get the old value of EBP that was there before our function got called
mov dword [_caller_address],dword [ebp+4] ;this isn't actually a valid opcode. you figure it out. 
mov ebp,ecx ;restore old state
push ebp 

不过这是非常不安全的。对编译器的优化可能会破坏它。它仅适用于 x86-32,并且取决于操作系统和编译器,如果它遵循不同的调用标准,它可能无法正常工作。

它的工作方式是这样的:

C中的函数看起来像

_Sum:
        push    ebp             ; create stack frame
        mov     ebp, esp
        mov     eax, [ebp+8]    ; grab the first argument
        mov     ecx, [ebp+12]   ; grab the second argument
        add     eax, ecx        ; sum the arguments
        pop     ebp             ; restore the base pointer
        ret

所以,如果说你有 _SumCall 它看起来像

_SumCall:
        push    ebp             ; create stack frame
        mov     ebp, esp
        mov     eax, [ebp+8]    ; grab the first argument
        mov     ecx, [ebp+12]   ; grab the second argument
        push ecx ;first argument for call
        push eax ;second argument for call
        call _Sum
        pop     ebp             ; restore the base pointer
        ret

所以你看,它依赖于被调用者负责堆栈帧保存的事实。

如果您尝试它但它不起作用,请确保调用者和被调用者至少有 1 个局部变量/参数。如果做不到这一点,那么你就搞砸了,因为编译器做了一些优化,打破了这个假设。

再次。这是非常不安全的并且非常不便携

参考:http ://courses.ece.illinois.edu/ece390/books/labmanual/c-prog-mixing.html

于 2010-01-31T18:13:40.210 回答
0

我对你有一个想法“未知(谷歌)”。检索您的函数将返回的地址,修补该地址以“到达它返回的位置并调用您的函数传递您该值”,返回,并在您的回调中使用您收到的参数并修复您修补的内容与原始值。

顺便说一句,我希望你不要用它来入侵五角大楼的计算机。

于 2010-01-31T19:43:01.210 回答
0

更直接一点的东西呢?*不确定它是否适用于任何平台/CPU:

void adr(void *x)
{
    *(void**)x = *(&x - 1); // x points to input parameter pushed before call
}

void *x;
adr(&x); // sets return adress to x
于 2021-10-08T15:50:26.623 回答