我正在为在 Linux 机器上运行的用 C 语言编写的应用程序实现一些有限的远程调试功能。目标是与应用程序通信并查找任意变量的值或运行任意函数。
我可以通过dlsym()
调用查找符号,但我无法确定返回的地址是指函数还是变量。有没有办法通过这个符号表来确定打字信息?
我正在为在 Linux 机器上运行的用 C 语言编写的应用程序实现一些有限的远程调试功能。目标是与应用程序通信并查找任意变量的值或运行任意函数。
我可以通过dlsym()
调用查找符号,但我无法确定返回的地址是指函数还是变量。有没有办法通过这个符号表来确定打字信息?
在 x86 平台上,如果可以查看函数的地址空间,则可以检查用于设置函数堆栈的指令。它通常是:
push ebp
mov ebp, esp
我对 x64 平台并不乐观,但我认为它是相似的:
push rbp
mov rbp, rsp
这描述了 C 调用约定
但是请记住,编译器优化可能会优化这些指令。如果您希望它起作用,您可能必须添加一个标志来禁用此优化。我相信对于 GCC,-fno-omit-frame-pointer 可以解决问题。
您可以读取文件/proc/self/maps
并解析每行的前三个字段:
<begin-addr>-<end-addr> rwxp ...
然后搜索包含您要查找的地址的行并检查权限:
r-x
:是代码;rw-
:是可写数据;r--
:它是只读数据;rwxp
:生成的代码,...)。例如下面的程序:
#include <stdio.h>
void foo() {}
int x;
int main()
{
int y;
printf("%p\n%p\n%p\n", foo, &x, &y);
scanf("%*s");
return 0;
}
...在我的系统中给出了这个输出:
0x400570
0x6009e4
0x7fff4c9b4e2c
...这些是来自的相关行/proc/<pid>/maps
:
00400000-00401000 r-xp 00000000 00:1d 641656 /tmp/a.out
00600000-00601000 rw-p 00000000 00:1d 641656 /tmp/a.out
....
7fff4c996000-7fff4c9b7000 rw-p 00000000 00:00 0 [stack]
....
所以地址是:code,data和data。
一种可能的解决方案是通过解析nm 实用程序的输出来为应用程序提取符号表。nm 包括有关符号类型的信息。T(全局文本)类型的符号是函数。
此解决方案的问题在于您必须确保符号表与目标匹配(特别是如果您要使用它来提取地址,尽管将其与 dlsym() 结合使用会更安全)。我用来确保这一点的方法是将符号表生成作为构建过程的一部分作为后处理步骤。
我想这不是一个非常可靠的方法,但它可能会起作用:
取一个众所周知的函数main()
的地址,例如一个众所周知的全局变量的地址。
现在取未知符号的地址并计算该地址与其他两个地址之间的差值的绝对值。最小的差异将表明未知地址更接近函数或全局变量,这意味着它可能是另一个函数或另一个全局变量。
此方法在编译器/链接器将所有全局变量打包到特定内存块,并将所有函数打包到另一个内存块的假设下工作。例如,Microsoft 编译器将所有全局变量放在(虚拟内存中的较低地址)函数之前。
我假设您不愿意检查局部变量,因为函数无法返回其地址(一旦函数结束,局部变量就会丢失)
它可以通过组合dlsym()
和来完成dladdr1()
。
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
int symbolType(void *sym) {
ElfW(Sym) *pElfSym;
Dl_info i;
if (dladdr1(sym, &i, (void **)&pElfSym, RTLD_DL_SYMENT))
return ELF32_ST_TYPE(pElfSym->st_info);
return 0;
}
int main(int argc, char *argv[]) {
for (int i=1; i < argc; ++i) {
printf("Symbol [%s]: ", argv[i]);
void *mySym = dlsym(RTLD_DEFAULT, argv[i]);
// This will not work with symbols that have a 0 value, but that's not going to be very common
if (!mySym)
puts("not found!");
else {
int type = symbolType(mySym);
switch (type) {
case STT_FUNC: puts("Function"); break;
case STT_OBJECT: puts("Data"); break;
case STT_COMMON: puts("Common data"); break;
/* get all the other types from the elf.h header file */
default: printf("Dunno! [%d]\n", type);
}
}
}
return 0;
}