我有一个 linux C 程序来处理发送到 TCP 套接字(绑定到特定端口)的请求。我希望能够通过对该端口的请求来查询 C 程序的内部状态,但我不想硬编码可以查询哪些全局变量。因此,我希望查询包含全局的字符串名称和 C 代码以在符号表中查找该字符串以找到其地址,然后通过 TCP 套接字将其值发送回。当然,符号表一定不能被剥离。那么 C 程序甚至可以找到自己的符号表,并且是否有一个库接口用于查找给定名称的符号?这是一个使用 gcc 构建的 ELF 可执行 C 程序。
3 回答
这实际上相当容易。您使用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 ABC A =(空) B =(空) C =(空) ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic ~ $ ./a.out ABC A = A 的值 B =(空) C = C 的值 ~ $ gcc dlopentest.c -Wall -Wextra -ldl -Wl,--export-dynamic -fvisibility=hidden ~ $ ./a.out ABC A = A 的值 B =(空) C =(空)
安全
请注意,不良行为的空间很大。
$ ./a.out printf printf = ▯▯▯▯ (垃圾)
如果您希望这样做是安全的,您应该创建一个允许符号的白名单。
文件:反射.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);
文件: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
这种特性的总称是“反射”,它不是 C 的一部分。
如果这是出于调试目的,并且您希望能够远程检查 C 程序的整个状态、检查任何变量、启动和停止其执行等等,您可以考虑GDB 远程调试:
GDB 提供了调试嵌入式系统时经常使用的“远程”模式。远程操作是指 GDB 在一台机器上运行而被调试的程序在另一台机器上运行。GDB 可以通过串行或 TCP/IP 与理解 GDB 协议的远程“存根”通信。可以通过链接到 GDB 提供的适当存根文件来创建存根程序,这些存根文件实现了通信协议的目标端。或者,可以使用 gdbserver 远程调试程序,而无需以任何方式对其进行更改。