【问题标题】:Is it possible to determine if a symbol is a variable or function in C?是否可以确定符号是 C 中的变量还是函数?
【发布时间】:2013-11-20 21:59:52
【问题描述】:

我正在为在 Linux 机器上运行的用 C 语言编写的应用程序实现一些有限的远程调试功能。目标是与应用程序通信并查找任意变量的值或运行任意函数。

我可以通过dlsym() 调用查找符号,但我无法确定返回的地址是指函数还是变量。有没有办法通过这个符号表来确定打字信息?

【问题讨论】:

  • 依赖于平台,但你可能会侥幸逃脱 1. 检查地址(空格),或 2. 寻找一些特殊功能的起始代码(蹦床等)
  • 或 3. 从 DWARF 调试信息中提取信息(如果有)(这很重要)
  • 此应用程序没有调试信息;该应用程序太大,以至于尝试使用调试信息进行编译会使任何尝试读取它的程序崩溃(gdb)

标签: c linux symbol-table symbol-tables


【解决方案1】:

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

push ebp
mov ebp, esp

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

push rbp
mov rbp, rsp

This 描述了 C 调用约定

但请记住,编译器优化可能会优化这些指令。如果您希望它起作用,您可能必须添加一个标志来禁用此优化。我相信对于 GCC,-fno-omit-frame-pointer 可以解决问题。

【讨论】:

  • 除非代码在没有优化的情况下编译,否则帧指针可能会在可能的情况下被省略。所以那是不可靠的。
  • 哦,原来如此。我相信他可以禁用那个优化。我会编辑我的答案,谢谢
【解决方案2】:

可以读取文件/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/&lt;pid&gt;/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。

【讨论】:

  • 很好的答案!为了向其他读者澄清,/proc/&lt;pid&gt;/maps 中的第一列数字是地址范围。因此,要确定一个符号是否是一个函数,请查看它的指针是否落在标有x 的地址范围内。变量的地址将在未标有x 的范围内。
  • @rodrigo 你能告诉我%*s 是做什么的吗?
  • @phyrrus9:它从标准输入 (%s) 读取一个字符串,然后将其丢弃而不将其保存在任何地方 (*)。请注意,对scanf() 的调用没有任何额外参数。我写这个是为了停止程序直到按下 ENTER 以便可以读取文件/proc/&lt;pid&gt;/maps。有些人更喜欢使用getchar()...
  • @rodrigo 我一直在使用临时变量,谢谢!
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

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

    此方法在编译器/链接器将所有全局变量打包到特定内存块,并将所有函数打包到另一个内存块的假设下工作。例如,Microsoft 编译器将所有全局变量放在(虚拟内存中的较低地址)函数之前。

    我假设您不会愿意检查局部变量,因为函数无法返回其地址(一旦函数结束,局部变量就会丢失)

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2018-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-16
        • 2012-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多