是否有可能确定符号是否是C中的variables或函数?

我正在为在Linux机器上运行的C编写的应用程序实现一些有限的远程debuggingfunction。 目标是与应用程序进行通信,并查找任意variables的值或运行任意函数。

我能够通过dlsym()调用来查找符号,但是我无法确定返回的地址是指一个函数还是一个variables。 有没有办法通过这个符号表来确定input信息?

您可以读取文件/proc/self/maps并解析每行的前三个字段:

 <begin-addr>-<end-addr> rwxp ... 

然后,您搜索包含您正在寻找的地址的行,并检查权限:

  • rx :是代码;
  • 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] .... 

所以地址是: 代码数据数据

在x86平台上,如果可以查看它的地址空间,可以检查用于为函数设置堆栈的指令。 通常是:

 push ebp mov ebp, esp 

我对x64平台并不积极,但我认为它是相似的:

 push rbp mov rbp, rsp 

这描述了C调用约定

请记住,编译器优化可能会优化这些指令。 如果你想这个工作,你可能需要添加一个标志来禁用这个优化。 我相信对于GCC,-fno-omit-frame-pointer将会有所斩获。

一种可能的解决方案是通过解析nm实用程序的输出来为应用程序提取符号表。 nm包含符号类型的信息。 T(全局文本)类型的符号是函数。

这个解决方案的麻烦是,你必须确保你的符号表匹配目标(特别是如果你要使用它来提取地址,尽管与dlsym()结合使用会更安全)。 我用来确保的方法是将构建过程的符号表生成部分作为后处理步骤。

我想这不是一个非常可靠的方法,但它可能工作:

取一个众所周知的函数的地址,比如main()和一个众所周知的全局变量的地址。

现在取未知符号的地址,并计算这个地址与另外两个地址之间差值的绝对值。 最小的差异将表明未知地址更靠近函数或全局变量,这意味着它可能是另一个函数或另一个全局变量。

该方法假定编译器/链接器将所有全局变量打包到特定的内存块,并将所有的功能打包到另一个内存块。 例如,微软的编译器把所有的全局变量放在(虚拟内存中的低地址)函数之前。

我假设你不会愿意检查局部变量,因为它的地址不能被函数返回(一旦函数结束,局部变量丢失)

它可以通过结合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; }