如何在Windows中以编程方式从调用堆栈帧中读取函数参数?

10

我试图遍历调用栈帧并从中提取一些信息。我能够通过使用WinDBG的StackWalk64SymGetSymFromAddr64SymGetLineFromAddr64 API提取文件名、行号和函数名。

然而,在STACKFRAME64中的DWORD64 Params[4]只支持从一个帧中读取回四个64位函数参数。更糟糕的是,在32位系统上,只有Params[4]的低32位被使用,因此具有超过32位的单个参数需要两个或更多元素。

typedef struct _tagSTACKFRAME64 {
  ADDRESS64 AddrPC;
  ADDRESS64 AddrReturn;
  ADDRESS64 AddrFrame;
  ADDRESS64 AddrStack;
  ADDRESS64 AddrBStore;
  PVOID     FuncTableEntry;
  DWORD64   Params[4];
  BOOL      Far;
  BOOL      Virtual;
  DWORD64   Reserved[3];
  KDHELP64  KdHelp;
} STACKFRAME64, *LPSTACKFRAME64;

我找不到任何可以读取堆栈帧中所有参数的API,没有限制地读取。

我想使用 ebp / rbp 从堆栈(x86 / x64)和寄存器(x64)中提取值。但是,如果我这样做,仍然只能获得“可能”的参数值。

是否有任何API可用于获取准确的值?如果我能够获得参数的类型和名称,那将更好。


8
由于编译器可以自由地优化参数使用方式,因此您无法获得准确的值,这样原始参数可能不再存储在堆栈中(例如,在x64上,参数是通过寄存器传递的,实际上可能永远不会写入堆栈)。从STACKFRAME64获取的值是最大努力而为。 - Raymond Chen
1
SymGetSymFromAddr64 可以给你函数本身的符号名称,这是一个混淆的 C++ 名称。如果你通过解淆器运行它,你可以推导出参数类型,尽管仅仅知道类型可能不足以解释 Params 成员。但我认为这已经是你能得到的最接近的答案了。 - Mooing Duck
3
无法存在这样的API,正如@RaymondChen所说的那样。操作系统无法知道编译器所做的事情。 - user207421
2
该参数可能已经不存在了。例如,如果在第10行之后不再使用该参数,则编译器可能会将内存重新用于其他用途。在第14行调用另一个函数,然后问“参数2的值是多少?”答案是:“那个信息已经不存在了。” - Raymond Chen
3
即使是 MSVC 和 WinDBG,如果参数已经不存在,它们也无法获取参数。 - Raymond Chen
显示剩余7条评论
1个回答

1
没有相关的API。为什么需要呢?现代操作系统对于某些人玩弄这些东西不感兴趣。 正如之前所说,编译器可以自由进行优化,因此您无法使用任何确定性工具来执行此操作。 但是,有一些启发式方法!如果在调用之前解析汇编语言或在调用后返回,则可以知道函数中有多少参数,您始终可以获得返回地址,可以检查它是否在CS中。 最重要的是-您应该阅读有关“堆栈展开”的术语。

近年来,由于异常处理的要求,几乎所有的编程语言都采用了取消表(unwind tables),这些表需要参数数据,以便能够取消非平凡类型的按值传递参数。 - LThode

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接