如何通过ctypes获取C共享库中函数的实际内存地址?
获取共享库中get_value函数的实际内存地址
我明白你想拿到get_value函数真正的内存地址,而不是动态链接过程中PLT(过程链接表)的入口地址——这在Linux动态链接环境里确实容易让人困惑,毕竟动态加载的共享库会有一层地址跳转的机制。下面给你几种可行的方案,从简单到进阶:
方法一:直接复用库内部输出的地址
你已经调用了print_pointer_to_get_value(),这个函数在共享库内部打印的&get_value就是函数的实际内存地址——因为共享库内部调用自身函数时,不会走PLT跳转,直接引用的就是函数的真实位置。
你只需要把输出的地址值复制出来,在你的加载程序里转换成函数指针使用就行:
#include <dlfcn.h> #include <stdio.h> typedef int (*get_value_t)(void); int main() { void* handle = dlopen("./shared.so", RTLD_NOW); if (!handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } // 调用库函数输出实际地址 void (*print_ptr)(void) = dlsym(handle, "print_pointer_to_get_value"); if (print_ptr) print_ptr(); // 把输出的地址(比如0x7fxxxxxx1234)转换成函数指针 get_value_t actual_get_value = (get_value_t)0x7fxxxxxx1234; printf("实际get_value返回值:%d\n", actual_get_value()); dlclose(handle); return 0; }
这个方法简单直接,但需要手动复制地址,适合快速测试或者固定环境下使用。
方法二:用dladdr自动解析实际地址
如果你想自动化获取,Linux提供了dladdr()函数,可以从函数的PLT地址反向解析出符号的实际地址。
当你用dlsym()拿到get_value的地址时,这个地址通常是PLT表的入口,而dladdr()能帮你穿透这层跳转,拿到真正的函数地址:
#include <dlfcn.h> #include <stdio.h> #include <link.h> typedef int (*get_value_t)(void); int main() { void* handle = dlopen("./shared.so", RTLD_NOW); if (!handle) { fprintf(stderr, "%s\n", dlerror()); return 1; } // 先拿到PLT地址 get_value_t plt_addr = dlsym(handle, "get_value"); if (!plt_addr) { fprintf(stderr, "%s\n", dlerror()); dlclose(handle); return 1; } // 解析实际地址 Dl_info info; if (dladdr(plt_addr, &info)) { if (info.dli_sname && !strcmp(info.dli_sname, "get_value")) { get_value_t actual_addr = (get_value_t)info.dli_saddr; printf("PLT入口地址:%p\n", plt_addr); printf("实际get_value地址:%p\n", actual_addr); printf("实际get_value返回值:%d\n", actual_addr()); } } else { fprintf(stderr, "dladdr解析失败:%s\n", dlerror()); } dlclose(handle); return 0; }
编译时记得链接dl库:gcc your_program.c -ldl
方法三:手动解析ELF符号表(进阶)
如果你想深入底层,也可以直接解析共享库的ELF符号表,通过加载基地址加上符号偏移量得到实际地址。这个方法适合理解动态链接的原理:
#include <dlfcn.h> #include <stdio.h> #include <link.h> #include <elf.h> #include <string.h> typedef int (*get_value_t)(void); static uintptr_t lib_base = 0; // 遍历已加载的共享库,找到目标库的基地址 int find_target_lib(struct dl_phdr_info *info, size_t size, void *data) { const char* target_name = (const char*)data; if (strstr(info->dlpi_name, target_name)) { lib_base = (uintptr_t)info->dlpi_addr; printf("共享库加载基地址:%p\n", (void*)lib_base); return 1; // 找到后停止遍历 } return 0; } int main() { // 查找shared.so的加载基地址 dl_iterate_phdr(find_target_lib, (void*)"shared.so"); if (lib_base == 0) { fprintf(stderr, "找不到shared.so\n"); return 1; } // 解析ELF头部和段表 Elf64_Ehdr* elf_hdr = (Elf64_Ehdr*)lib_base; Elf64_Shdr* section_hdr = (Elf64_Shdr*)(lib_base + elf_hdr->e_shoff); const char* str_table = NULL; Elf64_Sym* dyn_sym_table = NULL; size_t sym_count = 0; // 定位动态符号表和字符串表 for (int i = 0; i < elf_hdr->e_shnum; i++) { if (section_hdr[i].sh_type == SHT_DYNSYM) { dyn_sym_table = (Elf64_Sym*)(lib_base + section_hdr[i].sh_offset); sym_count = section_hdr[i].sh_size / sizeof(Elf64_Sym); } else if (section_hdr[i].sh_type == SHT_STRTAB && section_hdr[i].sh_link == elf_hdr->e_shstrndx) { str_table = (const char*)(lib_base + section_hdr[i].sh_offset); } } if (!dyn_sym_table || !str_table) { fprintf(stderr, "无法找到动态符号表或字符串表\n"); return 1; } // 遍历符号表找get_value get_value_t actual_addr = NULL; for (size_t i = 0; i < sym_count; i++) { if ((ELF64_ST_BIND(dyn_sym_table[i].st_info) == STB_GLOBAL) && (ELF64_ST_TYPE(dyn_sym_table[i].st_info) == STT_FUNC) && !strcmp(str_table + dyn_sym_table[i].st_name, "get_value")) { actual_addr = (get_value_t)(lib_base + dyn_sym_table[i].st_value); printf("从符号表解析的get_value实际地址:%p\n", actual_addr); printf("实际get_value返回值:%d\n", actual_addr()); break; } } if (!actual_addr) { fprintf(stderr, "符号表中找不到get_value\n"); } return 0; }
注意:这个代码是针对64位系统的,如果你用32位系统,需要把所有Elf64前缀换成Elf32。
内容的提问来源于stack exchange,提问作者user6369958
相关产品推荐
相关产品推荐

