是否可以确定符号是否是C中的变量或函数?

时间:2013-11-20 21:59:53

标签: c linux symbol-table symbol-tables

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

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

5 个答案:

答案 0 :(得分:3)

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

push ebp
mov ebp, esp

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

push rbp
mov rbp, rsp

This描述了C调用约定

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

答案 1 :(得分:2)

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

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

答案 2 :(得分: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/<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]
....

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

答案 3 :(得分:1)

我想这不是一个非常可靠的方法,但可能会有效:

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

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

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

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

答案 4 :(得分:1)

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