【问题标题】:Can a running C program access its own symbol table?正在运行的 C 程序可以访问自己的符号表吗?
【发布时间】:2012-06-30 13:52:32
【问题描述】:

我有一个 linux C 程序来处理发送到 TCP 套接字(绑定到特定端口)的请求。我希望能够通过对该端口的请求来查询 C 程序的内部状态,但我不想硬编码可以查询哪些全局变量。因此,我希望查询包含全局的字符串名称和 C 代码以在符号表中查找该字符串以找到其地址,然后通过 TCP 套接字将其值发送回。当然,符号表一定不能被剥离。那么 C 程序甚至可以找到自己的符号表,并且是否有一个库接口用于查找给定名称的符号?这是一个使用 gcc 构建的 ELF 可执行 C 程序。

【问题讨论】:

  • 你想要远程调试。在互联网上搜索“远程调试 linux”。很多链接。
  • @selbie:这很难做到。

标签: c linux gcc elf symbol-tables


【解决方案1】:

这类特性的总称是“reflection”,它不是C语言的一部分。

如果这是出于调试目的,并且您希望能够远程检查 C 程序的整个状态、检查任何变量、启动和停止其执行等等,您可以考虑 GDB remote debugging

GDB 提供了调试嵌入式系统时经常使用的“远程”模式。 远程操作是当 GDB 在一台机器上运行并且程序正在运行时 调试在另一个上运行。 GDB 可以与远程“存根”通信 它通过串行或 TCP/IP 理解 GDB 协议。一个存根程序 可以通过链接到提供的适当存根文件来创建 GDB,实现通信的目标端 协议。或者,可以使用 gdbserver 进行远程调试 无需以任何方式更改程序。

【讨论】:

  • 这是用于生产的,我希望能够浏览任何符号而无需维护查找表...毕竟,该信息已经在符号表中。
【解决方案2】:

这实际上相当容易。您使用 dlopen / dlsym 来访问符号。为了使其工作,符号必须存在于动态符号表中。有多个符号表!

#include <dlfcn.h>
#include <stdio.h>

__attribute__((visibility("default")))
const char A[] = "Value of A";

__attribute__((visibility("hidden")))
const char B[] = "Value of B";

const char C[] = "Value of C";

int main(int argc, char *argv[])
{
    void *hdl;
    const char *ptr;
    int i;

    hdl = dlopen(NULL, 0);
    for (i = 1; i < argc; ++i) {
        ptr = dlsym(hdl, argv[i]);
        printf("%s = %s\n", argv[i], ptr);
    }
    return 0;
}

要将所有符号添加到动态符号表中,请使用-Wl,--export-dynamic。如果要从符号表中删除大多数符号(推荐),请设置 -fvisibility=hidden,然后使用 __attribute__((visibility("default"))) 或其他方法之一显式添加所需的符号。

~ $ gcc dlopentest.c -Wall -Wextra -ldl ~ $ ./a.out A B C A =(空) B =(空) C =(空) ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic ~ $ ./a.out A B C A = A 的值 B =(空) C = C 的值 ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic -fvisibility=hidden ~ $ ./a.out A B C A = A 的值 B =(空) C =(空)

安全

请注意,有很多不良行为的余地。

$ ./a.out printf printf = ▯▯▯▯ (垃圾)

如果您希望这样做是安全的,您应该创建一个允许符号的白名单。

【讨论】:

  • 将非字符串(可能未终止)数据或函数作为字符串打印是危险的。否则我认为没有必要将符号列入白名单。
  • @R..:嗯,我认为该变量是用于读/写的,直到我更彻底地阅读了这个问题。已更新。
  • 这是我想要的一半......问题是我希望能够做出有意义的回应而不管符号的类型。 IE。如果 A 是“long”,B 是“char”,C 是“char *”怎么办?我需要访问符号类型及其地址。
  • @JimKleck:符号没有类型,所以这或多或少是不可能的。您要么必须使用调试信息进行编译并解析它(大量工作),要么只是将类型编码到单独的表中,此时您也可以对指针进行编码,因此使用 ELF 没有意义如果您需要类型,请使用符号表。类型只在编译期间真正存在,编译器会删除类型信息,只留下原始字节。
【解决方案3】:

文件:reflect.c

#include <stdio.h>
#include "reflect.h"

struct sym_table_t gbl_sym_table[1] __attribute__((weak)) = {{NULL, NULL}};

void * reflect_query_symbol(const char *name)
{
    struct sym_table_t *p = &gbl_sym_table[0];

    for(; p->name; p++) {
        if(strcmp(p->name, name) == 0) {
            return p->addr;
        }
    }
    return NULL;
}

文件:reflect.h

#include <stdio.h>

struct sym_table_t {
    char *name;
    void *addr;
};

void * reflect_query_symbol(const char *name);

文件:main.c

只需#include "reflect.h" 并调用 reflect_query_symbol

示例:

#include <stdio.h>
#include "reflect.h"

void foo(void)
{
    printf("bar test\n");
}

int uninited_data;

int inited_data = 3;

int main(int argc, char *argv[])
{
    int i;
    void *addr;

    for(i=1; i<argc; i++) {
        addr = reflect_query_symbol(argv[i]);
        if(addr) {
            printf("%s lay at: %p\n", argv[i], addr);
        } else {
            printf("%s NOT found\n", argv[i], addr);
        }
    }

    return 0;
}

文件:生成文件

objs = main.o reflect.o

main: $(objs)
        gcc -o $@ $^
        nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
        gcc -c .reflect.real.c -o .reflect.real.o
        gcc -o $@ $^ .reflect.real.o
        nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c
        gcc -c .reflect.real.c -o .reflect.real.o
        gcc -o $@ $^ .reflect.real.o

【讨论】:

  • 您只需要编写显示“reflect.c”和“reflect.h”两个文件并修改您的“Makefile”,您将获得符号名称和符号对应地址的表.
  • 我想要符号类型,或者至少是大小,以及地址。看起来 nm 的“-S”标志可以做到这一点。我是否需要在最终的可执行文件上运行 nm(因为我有多个 .o)以获得 gbl_sym_table 的正确大小,然后再次运行 nm 以填充地址?然后最后重建 reflect.o 并重新链接以将其全部放入可执行文件中?
  • 实际上,为什么要使用 nm 和 makefile 魔法呢? nm 程序必须使用一些 API 来访问符号表,我希望该 API 直接在我的程序中使用。
  • @JimKleck,在这种情况下查看 libbfd.h 头文件。
  • @JimKleck,是的,例如,您可以使用 nm 的“-S”标志来获取符号的大小,并在 struct sym_table_t 中添加一个成员来存储大小。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-04
  • 2023-03-08
  • 1970-01-01
相关资源
最近更新 更多