调用dlopen有时损坏类变量致程序崩溃,原因及解决方法?
问题
尝试使用dlopen()加载库时,该调用有时(非总是)会损坏类变量,进而导致程序触发段错误。
场景伪代码
class MyClass { public: int MyVar; void Print() { printf("Simply breakpoint\n"); }; void LoadLibrary() { dlopen("/usr/lib/x86_64-linux-gnu/libavcodec.so.58.54.100", RTLD_LAZY); }; MyClass() { MyVar = 12345; printf("MyVar address %p\n", &MyVar); Print(); LoadLibrary(); }; } void main() { MyClass obj; }
GDB调试步骤
gdb MyApp break Print run
程序在Print断点停下时,输出MyVar地址:
MyVar address 0x7fff900bc2bc
设置硬件观察点并继续运行:
watch *0x7fff900bc2bc cont
触发意外写入的回溯信息:
Thread 1 "MyApp" hit Hardware watchpoint 2: *0x7fff900bc2bc Old value = 12345 New value = 32767 memmove () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:356 356 ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: No such file or directory. (gdb) backtrace #0 memmove () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:356 #1 0x00007ffff7fde759 in _dl_map_object_deps (map=map@entry=0x7fff90145110, preloads=preloads@entry=0x0, npreloads=npreloads@entry=0, trace_mode=trace_mode@entry=0, open_mode=open_mode@entry=-2147483648) at dl-deps.c:446 #2 0x00007ffff7fe4db0 in dl_open_worker (a=a@entry=0x7fffa6fd80f0) at dl-open.c:571 #3 0x00007ffff53dd928 in __GI__dl_catch_exception (exception=<optimized out>, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:208 #4 0x00007ffff7fe460a in _dl_open (file=0x42d8ee0 "/usr/lib/x86_64-linux-gnu/libavcodec.so.58.54.100", mode=-2147483646, caller_dlopen=<optimized out>, nsid=-2, argc=2, argv=0x7fffffffea88, env=0x54037d0) at dl-open.c:837 #5 0x00007ffff57bc34c in dlopen_doit (a=a@entry=0x7fffa6fd8310) at dlopen.c:66 #6 0x00007ffff53dd928 in __GI__dl_catch_exception (exception=exception@entry=0x7fffa6fd82b0, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:208 #7 0x00007ffff53dd9f3 in __GI__dl_catch_error (objname=0x7fff900d8770, errstring=0x7fff900d8778, mallocedp=0x7fff900d8768, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:227 #8 0x00007ffff57bcb59 in _dlerror_run (operate=operate@entry=0x7ffff57bc2f0 <dlopen_doit>, args=args@entry=0x7fffa6fd8310) at dlerror.c:170 #9 0x00007ffff57bc3da in __dlopen (file=<optimized out>, mode=<optimized out>) at dlopen.c:87 #10 0x000000000209ec5b in MyClass::LoadLibrary() () .......
注:无法直接动态链接libavcodec,因为第三方库已静态链接该库但不含所需VAAPI功能,直接动态链接会引发符号冲突,因此选择手动dlopen()加载并通过dlsym()获取函数指针。
原因与解决方案
核心原因
从堆栈回溯可知,内存损坏发生在动态链接器处理库依赖的memmove调用中,最可能的原因是栈内存空间不足导致的越界覆盖:
- 你在类构造函数中调用
dlopen,此时MyClass的实例obj存储在主线程的栈上。 dlopen内部的动态链接过程(依赖解析、符号表处理等)会占用大量栈空间,当栈剩余空间不足以容纳这些操作时,就会覆盖栈上已有的变量(比如MyVar)。- 问题偶发是因为程序运行时的栈可用空间受多种因素影响,比如前期函数调用的栈深度、环境变量等,只有当栈空间临界不足时才会触发。
解决办法
- 延迟加载库,避开构造函数:不要在类的构造函数中调用
dlopen,将库加载逻辑移到构造函数执行完成之后(比如main中实例化对象后再调用LoadLibrary),或者改用类的初始化方法触发加载。这样栈上的类变量不会被动态链接器的栈操作覆盖。 - 将类实例分配到堆内存:用
new MyClass()创建对象,堆内存不受栈空间限制,动态链接器的栈操作不会影响堆上的MyVar。 - 限制库符号可见性:调用
dlopen时添加RTLD_LOCAL标志,阻止加载的库符号污染全局符号表,减少动态链接器的处理负载,同时降低符号冲突风险(配合dlsym获取函数指针完全兼容):dlopen("/usr/lib/x86_64-linux-gnu/libavcodec.so.58.54.100", RTLD_LAZY | RTLD_LOCAL); - 临时调整栈大小:Linux下可通过
ulimit -s <更大值>增大程序栈空间,但这只是临时规避方案,栈空间仍有上限,不推荐作为长期解决办法。
内容的提问来源于Stack Exchange,提问作者nckm
相关产品推荐
相关产品推荐

