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

是否可以确定符号是C中的变量还是函数?,c,linux,symbol-table,symbol-tables,C,Linux,Symbol Table,Symbol Tables,我正在为运行在Linux机器上的C语言编写的应用程序实现一些有限的远程调试功能。目标是与应用程序通信并查找任意变量的值或运行任意函数 我可以通过dlsym()调用查找符号,但无法确定返回的地址是指函数还是变量。有没有办法通过此符号表确定键入信息?在x86平台上,如果可以查看函数的地址空间,可以查看用于设置函数堆栈的指令。它通常是: push ebp mov ebp, esp 我对x64平台不是很乐观,但我认为它是相似的: push rbp mov rbp, rsp 描述C调用约定 但是请记住

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


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

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

push ebp
mov ebp, esp
我对x64平台不是很乐观,但我认为它是相似的:

push rbp
mov rbp, rsp
描述C调用约定


但是请记住,编译器优化可能会优化这些指令。如果您希望这样做,您可能需要添加一个标志来禁用此优化。我相信对于GCC,-fno省略帧指针就可以了。

一个可能的解决方案是通过解析。nm包括有关符号类型的信息。T(全局文本)类型的符号是函数


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

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

取已知函数的地址,例如
main()
和已知全局变量的地址

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

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


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

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

<begin-addr>-<end-addr> rwxp ...
…在我的系统中提供以下输出:

0x400570
0x6009e4
0x7fff4c9b4e2c
…这些是
/proc//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]
....

因此地址是:代码、数据和数据。

可以通过组合
dlsym()
dladdr1()
来完成

定义GNU源
#包括
#包括
#包括
int symbolType(void*sym){
ElfW(Sym)*pElfSym;
Dl_信息i;
if(dladdr1(符号和i,(无效**)和pElfSym,RTLD(符号))
返回ELF32_ST_TYPE(pElfSym->ST_info);
返回0;
}
int main(int argc,char*argv[]){
对于(int i=1;i
取决于平台,但您可能会侥幸逃脱1。检查地址(空格),或2。通过寻找一些特殊的功能开始代码(蹦床等)或3。如果此应用程序没有可用的调试信息,则从DWARF调试信息中提取该信息(这是非常重要的);应用程序太大了,如果试图使用调试信息进行编译,那么任何试图读取它的程序都会崩溃(gdb),除非代码在编译时没有对其进行优化,否则可能会忽略帧指针。所以这是不可靠的。哦,那是真的。我相信他可以禁用这个优化。我会编辑我的答案,谢谢你,很好的答案!为了向其他读者说明,在
/proc//maps
中,数字的第一列是地址范围。因此,要确定符号是否是函数,请查看其指针是否位于标有
x
的地址范围内。变量的地址将位于未标记有
x
@rodrigo您能告诉我
%*s
的作用吗?@phyrrus9:它从标准输入(
%s
)读取字符串,但随后丢弃它,而不将其保存在任何地方(
*
)。请注意,对
scanf()
的调用没有任何额外参数。我这样写是为了在按下ENTER键之前停止程序,以便可以读取文件
/proc//maps
。有些人更喜欢使用
getchar()
来代替…@rodrigo我只是用了临时变量,谢谢!
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]
....
#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;
}