我正在尝试编写一个小型调试实用程序,为此,我需要获取函数/全局变量地址,并给出其名称。这是内置的调试实用程序,这意味着调试实用程序将从要调试的代码中运行,或者用简单的话说我无法解析可执行文件。
现在有一种众所周知的方法吗?我的计划是将.debug_ *部分加载到内存中[我计划通过ld脚本中的廉价技巧进行处理]
.data { *(。数据) __sym_start =。; (调试_ 的); __sym_end =。; }
现在我必须解析该部分以获取我需要的信息,但我不确定这是可行的还是有问题 - 这只是理论。但它似乎也是太多的工作:-)有一个简单的方法。或者,如果有人能够预先告诉我为什么我的方案不起作用,那么它也会有所帮助。
先谢谢, 亚历克斯。
答案 0 :(得分:0)
如果您在使用dlopen(3)
和dlsym(3)
(如Linux)的系统下运行,您应该能够:
char thing_string[] = "thing_you_want_to_look_up";
void * handle = dlopen(NULL, RTLD_LAZY | RTLD_NOLOAD);
// you could do RTLD_NOW as well. shouldn't matter
if (!handle) {
fprintf(stderr, "Dynamic linking on main module : %s\n", dlerror() );
exit(1);
}
void * addr = dlsym(handle, thing_string);
fprintf(stderr, "%s is at %p\n", thing_string, addr);
我不知道为其他系统执行此操作的最佳方法,这可能不适用于静态变量和函数。如果您有兴趣使用它们,C ++符号名称将被修改。
要将此扩展为适用于共享库,您可以从/proc/self/maps
获取当前加载的库的名称,然后将库文件名传递到dlopen
,但如果库已经存在,则可能会失败已重命名或删除。
可能还有其他几种更好的方法可以解决这个问题。
/* name_addr.h */
struct name_addr {
const char * sym_name;
const void * sym_addr;
};
typedef struct name_addr name_addr_t;
void * sym_lookup(cost char * name);
extern const name_addr_t name_addr_table;
extern const unsigned name_addr_table_size;
/* name_addr_table.c */
#include "name_addr.h"
#define PREMEMBER( X ) extern const void * X
#define REMEMBER( X ) { .sym_name = #X , .sym_addr = (void *) X }
PREMEMBER(strcmp);
PREMEMBER(printf);
PREMEMBER(main);
PREMEMBER(memcmp);
PREMEMBER(bsearch);
PREMEMBER(sym_lookup);
/* ... */
const name_addr_t name_addr_table[] =
{
/* You could do a #include here that included the list, which would allow you
* to have an empty list by default without regenerating the entire file, as
* long as your compiler only warns about missing include targets.
*/
REMEMBER(strcmp),
REMEMBER(printf),
REMEMBER(main),
REMEMBER(memcmp),
REMEMBER(bsearch),
REMEMBER(sym_lookup);
/* ... */
};
const unsigned name_addr_table_size = sizeof(name_addr_table)/sizeof(name_addr_t);
/* name_addr_code.c */
#include "name_addr.h"
#include <string.h>
void * sym_lookup(cost char * name) {
unsigned to_go = name_addr_table_size;
const name_addr_t *na = name_addr_table;
while(to_to) {
if ( !strcmp(name, na->sym_name) ) {
return na->sym_addr;
}
na++;
to_do--;
}
/* set errno here if you are using errno */
return NULL; /* Or some other illegal value */
}
如果您这样做,链接器将在布局完成后为您填写地址。如果您为表中列出的所有符号都包含头文件,那么在编译表文件时就不会收到警告,但只需将它们全部设为extern void *
就可以了。编译器警告你所有这些(它可能会,但不一定)。
您可能还希望按名称对符号进行排序,这样您就可以使用列表的二进制搜索而不是遍历它。
您应该注意,如果表中的成员没有被程序另外引用(例如,如果表中有sqrt
的条目,但没有调用它),那么链接器将会希望(需要)将这些功能链接到您的图像中。这可能会让它爆炸。
此外,如果您正在利用全局优化,那么这个表可能会降低效率,因为编译器会认为列出的所有函数都可以通过此列表中的指针访问,并且它无法看到所有调用分。
将静态函数放在此列表中并不简单。您可以通过将表更改为动态并在运行时从每个模块中的函数执行此操作,或者可能通过在表所在的目标文件中生成新部分来执行此操作。如果您使用的是gcc:
#define SECTION_REMEMBER(X) \
static const name_addr_t _name_addr##X = \
{.sym_name= #X , .sym_addr = (void *) X } \
__attribute__(section("sym_lookup_table" ) )
并在每个 .c 文件的末尾添加这些列表,其中包含您要从该文件中记住的所有符号。这将需要链接器工作,以便链接器知道如何处理这些成员,但是您可以通过查看它所在的部分的开头和结尾遍历列表(我不知道该怎么做)这个,但我知道它可以做到并且不太困难)。但是,这将使排序列表更加困难。另外,我并不完全确定将.sym_name
初始化为字符串文字的地址不会导致将字符串填入此部分,但我认为不会。如果确实如此,那将会破坏事情。
您仍然可以使用objdump获取目标文件(可能是elf)包含的符号列表,然后根据您感兴趣的符号对其进行过滤,然后重新生成表中所列成员的表文件。