正在运行的 C 程序可以访问自己的符号表吗?



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

这实际上相当容易。 您可以使用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 = %sn", argv[i], ptr);
    }
    return 0;
}

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

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

安全

请注意,不良行为的空间很大。

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

如果您希望这是安全的,则应创建允许符号的白名单。

文件: 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;
}

文件: 反射.h

#include <stdio.h>
struct sym_table_t {
    char *name;
    void *addr;
};
void * reflect_query_symbol(const char *name);

文件: 主.C

只需 #include"Reflect.h"并致电reflect_query_symbol

例:

#include <stdio.h>
#include "reflect.h"
void foo(void)
{
    printf("bar testn");
}
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: %pn", argv[i], addr);
        } else {
            printf("%s NOT foundn", 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

这种特征的总称是"反射",它不是 C 的一部分。

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

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

最新更新