4

我想在我的跟踪器中打印返回值,有两个问题

  1. 如何获得退货地址?
  2. 返回位置在 ~Tracer() 之前或之后更新?

此处需要文本,以便 Stackoverflow 格式化代码:

struct Tracer
{
  int* _retval;
  ~Tracer() 
  { printf("return value is %d", *_retval); }
};


int foo()
{
  Tracer __tracter = { __Question_1_how_to_get_return_address_here__ };

  if(cond) {
     return 0;
  } else {
     return 99;
  }

  //Question-2: 
  // return postion is updated before OR after ~Tracer() called ???
}
4

3 回答 3

5

我找到了问题 1 的一些提示,现在检查 Vc 代码

对于 gcc,__builtin_return_address http://gcc.gnu.org/onlinedocs/gcc/Return-Address.html

对于 Visual C++,_ReturnAddress

于 2012-11-20T15:55:40.837 回答
3

您不能在 C++ 中可移植或可靠地执行此操作。返回值可能在内存或寄存器中,并且在不同情况下可能会或可能不会间接。

您可能可以使用内联汇编来使某些硬件/编译器工作。

一种可能的方法是让您Tracer的模板引用返回值变量(在适当时)并在销毁之前将其打印出来

另请注意,带有__(双下划线)的标识符是为实现保留的。

于 2012-11-20T15:53:12.557 回答
1

您的问题相当令人困惑,您可以互换使用不可互换的术语“地址”和“价值”。

返回值是函数在 x86(_64) 中以 E/RAX 或 EDX:EAX 或 XMM0 等形式出现的 4/8 字节值形式吐出的内容,您可以在此处阅读有关它的更多信息。

另一方面,返回地址是 E/RSP 在调用时所指向的地址(也就是堆栈顶部的东西),并保存函数完成后“跳转”回的地址(定义为称为返回)。

现在我什至不知道什么是示踪剂 tbh,但我可以告诉你你是怎么得到的,这都是关于钩子的。

对于值,假设您在内部执行此操作,只需将函数与具有相同定义的函数挂钩,一旦它返回,您将获得结果。

对于地址,它有点复杂,因为你必须降低一点,并且可能会做一些 asm 恶作剧,我真的不知道你到底想要完成什么,但如果你愿意的话,我做了一个小“存根” , 为被调用者提供返回指针。

这是:

 void __declspec(noinline) __declspec(naked) __stdcall _replaceFirstArgWithRetPtrAndJump_() {
    __asm {                   //let's call the function we jump to "callee", and the function that called us "caller"                       
        push ebp             //save ebp, ESP IS NOW -4
        mov ebp, [esp + 4]  //save return address
        mov eax, [esp + 8] //get callee's address (which is the first param) - eax is volatile so iz fine
        mov[esp + 8], ebp //put the return address where the callee's address was (to the callee, it will be the caller)
        pop ebp          //restore ebp
        jmp eax         //jump to callee
}   }
#define CallFunc_RetPtr(Function, ...) ((decltype(&Function))_replaceFirstArgWithRetPtrAndJump_)(Function, __VA_ARGS__)

unsigned __declspec(noinline) __stdcall printCaller(void* caller, unsigned param1, unsigned param2) {
    printf("I'm printCaller, Called By %p; Param1: %u, Param2: %u\n", caller, param1, param2);
    return 20;
}

void __declspec(noinline) doshit() {
    printf("us: %p\nFunction we're calling: %p\n", doshit, printCaller);
    CallFunc_RetPtr(printCaller, 69, 420);  
}

现在可以肯定,您可以并且也许应该使用 _ReturnAddress() 或任何不同的编译器的内在函数,但如果这不可用(这应该是一个非常罕见的场景,取决于您的工作)并且您知道您的 ASM,这个概念应该适用于任何架构,因为无论指令集如何不同,它们都遵循相同的程序计数器设计。

我写这个更多是因为我很久以前出于某种目的寻找这个答案,但我找不到一个好的答案,因为大多数人只是说“呃,这是不可能的或便携的或其他什么”,并且我觉得这会有所帮助。

于 2018-08-19T10:59:05.483 回答