perror() 和 dlerror()

本文解决了一个关于动态链接库加载的问题,详细介绍了如何正确使用dlopen和dlsym函数,并解释了perror与dlerror的区别,最终实现了从动态库中成功调用函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天同学问了个问题, 为什么包含了<dlfcn.h> 用dlopen 还提醒未定义

我拿源码来看了下

#include <stdlib.h>
#include <dlfcn.h>
#include "add.h"
#include <stdio.h>

typedef int(*P_FUNC)(int,int);

int main(){
    void *handler = dlopen("libtext.so",RTLD_NOW);
    P_FUNC add = dlsym(handler,"add");
    P_FUNC sub = dlsym(handler,"sub");
    //if(!sub) perror("sub dlsym"),exit(-1);
    P_FUNC mul = dlsym(handler,"mul");
    //if(!mul) perror("mul dlsym"),exit(-1);
    show(30,'+',20);
    printf("%d\n",add(30,20));
    show(30,'-',20);
    printf("%d\n",sub(30,20));
    show(30,'*',20);
    printf("%d\n",mul(30,20));
    dlclose(handler);
    return 0;

}


首先以上代码: 明确的错误是show() 函数没有 获取libtext.so动态库里的函数地址,并且所有dlsym 函数前都没有函数类型的强制转换.

更改如下

typedef int(*P_FUNC)(int,int);
typedef void (*P_SHOW)(int,char,int);		//new
int main(){
    void *handler = dlopen("libtext.so",RTLD_NOW);if(!handler) perror("dlopen"),exit(-1);
    P_FUNC add = (P_FUNC)dlsym(handler,"add");
    if(!add) perror("add dlsym"),exit(-1);
    P_FUNC sub = (P_FUNC)dlsym(handler,"sub");
    if(!sub) perror("sub dlsym"),exit(-1);
    P_FUNC mul = (P_FUNC)dlsym(handler,"mul");
    if(!mul) perror("mul dlsym"),exit(-1);
    P_SHOW show = (P_SHOW)dlsym(handler,"show");	//new
    //if(!show) perror("show dlsym"),exit(-1);
    show(30,'+',20);
    printf("%d\n",add(30,20));
    show(30,'-',20);
    printf("%d\n",sub(30,20));
    show(30,'*',20);
    printf("%d\n",mul(30,20));
    dlclose(handler);
    return 0;

}

接下来 继续编译 gcc main.c -ldl  没想到遇到了段错误, 并且终端输出了 dlopen: Success

if (!handler) {
		printf ("dlopen失败:%s\n",	dlerror());
		return -1;
	}

编译  结果输出的是

dlopenʧ�ܣ�libtext.so: cannot open shared object file: No such file or directory

打开动态库失败............果断忘了配置环境变量.............但是可以肯定的是 perror() 不能 代替dlerror()功能  ,稍后套路他俩的区别,先导入环境变量

$export LIBRARY_PATH=$LIBRARY_PATH:.
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

编译 运行 成功.


接下来我去查了 perror() 和 dlerror()的区别.

原来对动态链接库有特定的dlerror函数返回错误信息.

dlerror()
包含头文件:
#include <dlfcn.h>
函数原型:
char *dlerror(void);
函数描述:
动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。 from[ 百度百科]

perror()
包含头文件:
#include< stdio.h>
#include<stdlib.h>
函数原型:
void perror(const char *s)

函数描述:

perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。from[ 百度百科]

<think>我们正在处理一个嵌入式Linux系统中的需求:劫持malloc函数,并获取调用malloc的函数名源文件信息。这通常可以通过动态链接库劫持(如LD_PRELOAD)来实现,同时结合一些调试信息(如backtrace)来获取调用栈信息。步骤概述: 1.使用LD_PRELOAD劫持malloc:创建一个共享库,其中定义自己的malloc函数,这样程序在运行时就会调用我们的malloc而不是标准的malloc。2.在自定义的malloc中,使用backtracebacktrace_symbols函数来获取调用栈信息。这些函数可以提供函数地址,然后我们可以尝试将这些地址解析为函数名源文件位置。3.解析地址信息:通常,backtrace_symbols给出的信息是经过修饰的,我们可以使用dladdr函数来获取更详细的信息(如函数名文件路径),或者使用addr2line工具(但需要注意在嵌入式环境中可能没有这个工具,或者我们需要在代码中实现类似功能)。4.注意:获取源文件路径行号通常需要程序在编译时带有调试信息(-g选项)。具体实现步骤: 1.编写共享库代码(例如mymalloc.c):-定义malloc函数,并调用原始的malloc(通过dlsym获取)。-在自定义malloc中,使用backtrace获取调用栈地址数组。-使用dladdr解析每个地址,获取函数名共享库路径(但注意,dladdr不能直接给出源文件路径行号)。-为了获取源文件路径行号,我们需要使用addr2line,但这是在外部程序,我们可以考虑在代码中嵌入一个解析器(如使用libbfd),或者使用backtrace_symbols得到字符串后解析,但通常只能得到函数名,而得不到源文件行号。另一种方法是使用libunwind,它可能提供更详细的堆栈信息,包括行号(如果有调试信息的话)。但在嵌入式环境中,资源可能有限,我们需要权衡。考虑到嵌入式环境,我们可能无法使用复杂的库。因此,一个折中的方法是获取调用malloc的函数名(通过backtrace_symbols或dladdr),然后记录下这个函数名。至于源文件路径,如果没有调试信息,可能无法获取。 如果我们有调试信息,我们可以使用dladdr获取共享库的路径(即.so文件的路径),但源文件路径行号仍然需要额外的步骤(如使用addr2line离线解析)。 由于在运行时获取源文件行号比较困难,我们这里先实现获取函数名共享库路径(即包含该函数的共享库的路径)。示例代码:```c#define_GNU_SOURCE#include <stdio.h> #include<stdlib.h>#include <dlfcn.h> #include<execinfo.h> #include<unistd.h>//原始malloc函数的指针staticvoid*(*real_malloc)(size_t)= NULL; //初始化:获取原始mallocstaticvoid mtrace_init(void){real_malloc =dlsym(RTLD_NEXT, "malloc");if(real_malloc== NULL) {fprintf(stderr, "Error indlsym:%s\n",dlerror());}} //自定义mallocvoid*malloc(size_tsize){if (real_malloc ==NULL){mtrace_init();}void*ptr= real_malloc(size);//获取调用栈void *buffer[10];int nptrs= backtrace(buffer,10);char**strings= backtrace_symbols(buffer, nptrs);if (strings ==NULL){perror("backtrace_symbols");returnptr;}//打印调用栈信息(这里我们只关心调用malloc的那个函数,即栈中上一级)//注意:栈的第一层是当前malloc函数,第二层是调用malloc的地方if (nptrs >1) {//尝试解析第二层的地址Dl_info info;if (dladdr(buffer[1],&info)) {printf("malloccalled by: %s (%s)[%p]\n", info.dli_sname, info.dli_fname, buffer[1]);} else{printf("malloc calledby:[unknown] (%s)\n",strings[1]);}}else {printf("malloccalled by: [unknown]\n");}free(strings);returnptr;}``` 编译共享库:```bashgcc -shared -fPIC -o libmymalloc.somymalloc.c -ldl-rdynamic``` 使用: ```bashLD_PRELOAD=./libmymalloc.so your_program```注意事项: 1.编译目标程序时,需要加上-rdynamic选项(如果目标程序是我们自己编译的),这样可以让程序中的符号导出,以便backtrace_symbols能够解析出函数名。2.在嵌入式系统中,可能没有backtrace_symbols或dladdr,或者这些函数的功能有限。因此,我们需要确保嵌入式Linux环境提供了这些函数(通常它们位于glibc中)。 3.如果我们需要源文件的行号,那么就需要在目标程序编译时加入-g选项,并且使用addr2line工具。但是,在运行时调用addr2line效率较低,不适合频繁调用(如每次malloc都调用)。因此,可以考虑在调试时使用,或者记录下地址信息,然后在离线状态下使用addr2line解析。如果我们需要在自定义malloc中获取行号,一种方法是在运行时调用addr2line,但这会启动外部程序,性能开销大。另一种方法是使用libbfd(二进制文件描述库)来在程序中解析地址,但这样会增加程序的体积复杂性,在嵌入式系统中可能不适用。因此,在实际嵌入式系统中,我们可能只能获取到函数名共享库路径(对于动态链接的函数),对于静态链接的函数,可能只能通过backtrace_symbols得到字符串,然后解析出函数名(如果有的话)。 相关问题:1.如何在嵌入式Linux中劫持其他库函数(如free, calloc)? 2.如何在不使用LD_PRELOAD的情况下劫持malloc函数(例如通过修改链接器脚本或修改二进制文件)? 3.如何优化上述获取调用栈信息的性能,以减少对程序运行速度的影响?4.在嵌入式系统中,如果glibc不支持backtrace,有哪些替代方案可以获取调用栈信息? 5.如何将获取的地址信息离线解析为源文件行号?参考文献: [^1]:关于LD_PRELOAD函数劫持的基本原理。[^2]:关于dlsym的使用,我们在自定义malloc中通过dlsym获取原始malloc。[^3]:堆管理相关,这里我们自定义了malloc函数。 [^4]:动态库加载机制,我们使用LD_PRELOAD来加载我们的共享库。 [^5]:嵌入式Linux开发项目指导,这个需求属于嵌入式Linux开发中的调试技术。</think>### 在嵌入式Linux中劫持malloc并获取调用信息 #### 实现原理 通过`LD_PRELOAD`机制劫持标准库函数,结合反向追踪技术获取调用信息: 1. **动态库劫持**:使用`LD_PRELOAD`优先加载自定义库 2. **符号拦截**:在自定义库中实现同名`malloc`函数 3. **调用栈解析**:通过`backtrace``dladdr`获取调用者信息[^4] #### 实现步骤 ##### 1. 编写劫持库(malloc_hook.c) ```c #define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <execinfo.h> #include <unistd.h> // 原始malloc函数指针 static void* (*real_malloc)(size_t) = NULL; // 初始化获取真实malloc地址 __attribute__((constructor)) void init_malloc_hook() { real_malloc = dlsym(RTLD_NEXT, "malloc"); char* error = dlerror(); if (error) { fprintf(stderr, "dlsym error: %s\n", error); _exit(1); } } // 自定义malloc实现 void* malloc(size_t size) { void *ptr = real_malloc(size); // 调用原始malloc // 获取调用栈信息 void* callstack[10]; int frames = backtrace(callstack, 10); // 解析调用者信息(跳过本函数栈帧) if (frames > 1) { Dl_info info; if (dladdr(callstack[1], &info)) { printf("malloc(%zu) called by: %s [%s]\n", size, info.dli_sname ? info.dli_sname : "unknown", info.dli_fname ? info.dli_fname : "unknown"); } } return ptr; } ``` ##### 2. 编译动态库 ```bash gcc -shared -fPIC -o libmalloc_hook.so malloc_hook.c -ldl -rdynamic ``` **关键参数说明**: - `-rdynamic`:导出所有符号供`backtrace`解析 - `-ldl`:链接动态加载库 ##### 3. 在目标程序中应用劫持 ```bash LD_PRELOAD=./libmalloc_hook.so ./your_program ``` #### 输出示例 当目标程序调用`malloc()`时,将输出类似信息: ``` malloc(128) called by: main [/home/user/app/main] malloc(256) called by: init_data [libcore.so] ``` #### 技术要点 1. **符号优先级**:`LD_PRELOAD`使自定义函数优先于标准库加载[^4] 2. **地址解析**: - `backtrace()`获取调用栈地址数组 - `dladdr()`将地址解析为符号文件名[^2] 3. **静态函数处理**:需目标程序编译时添加`-rdynamic`导出符号 4. **性能影响**:频繁调用可能降低性能,建议仅用于调试 #### 嵌入式环境注意事项 1. **交叉编译**:使用目标平台的工具链编译劫持库 ```bash arm-linux-gnueabihf-gcc -shared -fPIC ... ``` 2. **调试信息**:目标程序需带调试符号(`-g`) 3. **精简实现**:嵌入式系统中可移除非关键日志减少开销 4. **替代方案**:资源受限环境可使用`__builtin_return_address(0)`获取直接调用者地址 #### 扩展应用 ```c // 获取源文件行号(需目标程序带调试信息) #include <libunwind.h> void print_call_info() { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); unw_word_t offset; char sym[256]; while (unw_step(&cursor) > 0) { unw_get_proc_name(&cursor, sym, sizeof(sym), &offset); printf(" -> %s+0x%lx\n", sym, offset); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值