There are times when you want to wrap a library function in order to provide some additional functionality. A common example of this is wrapping the standard library’s malloc() and free() so that you can easily track memory allocations in your program. While there are several techniques for wrapping library functions, one well-known method is using dlsym() with RTLD_NEXT to locate the wrapped function’s address so that you can correctly forward calls to it.


Problem

So what can go wrong? Let’s look at an example:

LibWrap.h

void* memAlloc(size_t s);
// Allocate a memory block of size 's' bytes.
void memDel(void* p);
// Free the block of memory pointed to by 'p'.

LibWrap.c

#define _GNU_SOURCE
#include <dlfcn.h>
#include "LibWrap.h" static void* malloc(size_t s) {
// Wrapper for standard library's 'malloc'.
// The 'static' keyword forces all calls to malloc() in this file to resolve
// to this functions.
void* (*origMalloc)(size_t) = dlsym(RTLD_NEXT,"malloc");
return origMalloc(s);
} static void free(void* p) {
// Wrapper for standard library's 'free'.
// The 'static' keyword forces all calls to free() in this file to resolve
// to this functions.
void (*origFree)(void*) = dlsym(RTLD_NEXT,"free");
origFree(p);
} void* memAlloc(size_t s) {
return malloc(s);
// Call the malloc() wrapper.
} void memDel(void* p) {
free(p);
// Call the free() wrapper.
}

Main.c

#include <malloc.h>
#include "LibWrap.h" int main() {
struct mallinfo beforeMalloc = mallinfo();
printf("Bytes allocated before malloc: %d\n",beforeMalloc.uordblks); void* p = memAlloc(57);
struct mallinfo afterMalloc = mallinfo();
printf("Bytes allocated after malloc: %d\n",afterMalloc.uordblks); memDel(p);
struct mallinfo afterFree = mallinfo();
printf("Bytes allocated after free: %d\n",afterFree.uordblks); return 0;
}

First compile LibWrap.c into a shared library:

$ gcc -Wall -Werror -fPIC -shared -o libWrap.so LibWrap.c

Next compile Main.c and link it against the libWrap.so that we just created:

$ gcc -Wall -Werror -o Main Main.c ./libWrap.so -ldl

Time to run the program!

$ ./Main
Bytes allocated before malloc: 0
Bytes allocated after malloc: 80
Bytes allocated after free: 0

So far, so good. No surprises. We allocated a bunch of memory and then freed it. The statistics returned by mallinfo() confirm this.

Out of curiosity, let’s look at ldd output for the application binary we created.

$ ldd Main
linux-vdso.so.1 => (0x00007fff1b1fe000)
./libWrap.so (0x00007fe7d2755000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe7d2542000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe7d217c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe7d2959000)

Take note of the relative placement of libWrap.so with respect to libc.so.6libWrap.socomes before libc.so.6. Remember this. It will be important later.

Now for fun, let’s re-compile Main.c with libc.so.6 explicitly specified on the command-line and coming before libWrap.so:

$ gcc -Wall -Werror -o Main Main.c /lib/x86_64-linux-gnu/libc.so.6 ./libWrap.so -ldl

Re-run:

$ ./Main
Bytes allocated before malloc: 0
Bytes allocated after malloc: 80
Bytes allocated after free: 80

Uh oh, why are we leaking memory all of a sudden? We de-allocate everything we allocate, so why the memory leak?

It turns out that the leak is occurring because we are not actually forwarding malloc() and free() calls to libc.so.6‘s implementations. Instead, we are forwarding them to malloc() and free() inside ld-linux-x86-64.so.2!

“What are you talking about?!” you might be asking.

Well, it just so happens that ld-linux-x86-64.so.2, which is the dynamic linker/loader, has its own copy of malloc() and free(). Why? Because ld-linux has to allocate memory from the heap before it loads libc.so.6. But the version of malloc/free that ld-linuxhas does not actually free memory!

[RTLD_NEXT] will find the next occurrence of a function in the search order after the current library. This allows one to provide a wrapper around a function in another shared library.But why does libWrap.so forward calls to ld-linux instead of libc? The answer comes down to how dlsym() searches for symbols when RTLD_NEXT is specified. Here’s the relevant excerpt from the dlsym(3) man page:— dlsym(3)

To understand this better, take a look at ldd output for the new Main binary:

$ ldd Main
linux-vdso.so.1 => (0x00007fffe1da0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32c2e91000)
./libWrap.so (0x00007f32c2c8f000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f32c2a8a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f32c3267000)

Unlike earlierlibWrap.so comes after libc.so.6. So when dlsym() is called inside libWrap.so to search for functions, it skips libc.so.6 since it precedes libWrap.so in the search order list. That means the searches continue through to ld-linux-x86-64.so.2where they find linker/loader’s malloc/free and return pointers to those functions. And so, libWrap.so ends up forwading calls to ld-linux instead of libc!

The answer is unfortunately no. At OptumSoft, we recently encountered this very same memory leak with a binary compiled using the standard ./configure && make on x86-64 Ubuntu 14.04.1 LTS. For reasons we don’t understand, the linking order for the binary was such that using dlsym() with RTLD_NEXT to lookup malloc/free resulted in pointers to implementations inside ld-linux. It took a ton of effort and invaluable help from Mozilla’s rr tool to root-cause the issue. After the whole ordeal, we decided to write a blog post about this strange behavior in case someone else encounters it in the future.At this point you might be wondering: We ran a somewhat funky command to build our application and then encountered a memory leak due to weird library linking order caused by said command. Isn’t this whole thing a silly contrived scenario?


Solution

If you find dlsym() with RTLD_NEXT returning pointers to malloc/free inside ld-linux, what can you do?

For starters, you need to detect that a function address indeed does belong to ld-linuxusing dladdr():

void* func = dlsym(RTLD_NEXT,"malloc");
Dl_info dlInfo;
if(!dladdr(func,&dlInfo)) {
// dladdr() failed.
}
if(strstr(dlInfo.dli_fname,"ld-linux")) {
// 'malloc' is inside linker/loader.
}

Once you have figured out that a function is inside ld-linux, you need to decide what to do next. Unfortunately, there is no straightforward way to continue searching for the same function name in all other libraries. But if you know the name of a specific library in which the function exists (e.g. libc), you can use dlopen() and dlsym() to fetch the desired pointer:

void* handle = dlopen("libc.so.6",RTLD_LAZY);
// NOTE: libc.so.6 may *not* exist on Alpha and IA-64 architectures.
if(!handle) {
// dlopen() failed.
}
void* func = dlsym(handle,"free");
if(!func) {
// Bad! 'free' was not found inside libc.
}

Summary

  • One can use dlsym() with RTLD_NEXT to implement wrappers around malloc() and free().
  • Due to unexpected linking behavior, dlsym() when using RTLD_NEXT can return pointers to malloc/free implementations inside ld-linux (dynamic linker/loader). Using ld-linux‘s malloc/free for general heap allocations leads to memory leaks because that particular version of free() doesn’t actually release memory.
  • You can check if an address returned by dlsym() belongs to ld-linux via dladdr(). You can also lookup a function in a specific library using dlopen() and dlsym().

From: http://optumsoft.com/dangers-of-using-dlsym-with-rtld_next/

[转] Dangers of using dlsym() with RTLD_NEXT的更多相关文章

  1. Android so 文件进阶<二> 从dlsym()源码看android 动态链接过程

    0x00  前言 这篇文章其实是我之前学习elf文件关于符号表的学习笔记,网上也有很多关于符号表的文章,怎么说呢,感觉像是在翻译elf文件格式的文档一样,千篇一律,因此把自己的学习笔记分享出来.dls ...

  2. 加载动态链接库——dlopen dlsym dlclose

    DLOPEN DLMOPEN DLCLOSE NAME     dlclose, dlopen, dlmopen - 打开/关闭共享对象 SYNOPSIS #include <dlfcn.h&g ...

  3. 采用dlopen、dlsym、dlclose加载动态链接库【总结】(转)

    1.前言 为了使程序方便扩展,具备通用性,可以采用插件形式.采用异步事件驱动模型,保证主程序逻辑不变,将各个业务已动态链接库的形式加载进来,这就是所谓的插件.linux提供了加载和处理动态链接库的系统 ...

  4. dlopen、dlsym和dlclose的使用

    在dlopen()函数以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym()的调用进程.使用dlclose()来卸载打开的库. dlopen: dlopen() The function ...

  5. 采用dlopen、dlsym、dlclose加载动态链接库【总结】

    摘自http://www.cnblogs.com/Anker/p/3746802.html 采用dlopen.dlsym.dlclose加载动态链接库[总结]   1.前言 为了使程序方便扩展,具备通 ...

  6. gcc编译出现dlopen、dlerror、dlsym、dlcolse的解决方法

      ➜  test_sqlite3 gcc *.c -I . -o xixi -pthread      /tmp/cckGKTrr.o: In function `unixDlOpen': sqli ...

  7. 【转】采用dlopen、dlsym、dlclose加载动态链接库

    1.前言 为了使程序方便扩展,具备通用性,可以采用插件形式.采用异步事件驱动模型,保证主控制逻辑不变,将各个业务以动态链接库的形式加载进来,这就是所谓的插件.linux提供了加载和处理动态链接库的系统 ...

  8. 用dlopen,dlsym加载动态链接库.so中函数

    代码如下 static void *findSymbol(const char *path, const char *symbol) { void *handle = dlopen(path, RTL ...

  9. the example of dlsym

    void *handle; int i, (*fptr)(int); /* open the needed object */ handle = dlopen("/usr/home/me/l ...

随机推荐

  1. #9 //[SDOI2017]新生舞会

    题解: 分数规划+费用流 常数巨大开o2加inline加register还是不行 我也不知道为什么 代码: #include <bits/stdc++.h> using namespace ...

  2. Scala入门教程---《chang哥教你一天搞定Scala》

    <chang哥教你一天搞定Scala> /** * <chang哥教你一天搞定Scala> * scala是一门多范式编程语言,集成了面向对象编程和函数式编程等多种特性. * ...

  3. 【Java】 剑指offer(12) 机器人的运动范围

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 地上有一个m行n列的方格.一个机器人从坐标(0, 0)的格子开始移 ...

  4. Linux系统之常用文件搜索命令

    (一)常用文件搜索命令 (1)which命令 (2)find命令 (3)locate (4)updatedb (5)grep (6)man (7)whatis (一)常用文件搜索命令 (1)which ...

  5. Mybatis Generator xml格式配置

    Mybatis Generator可以使用Maven方式和Java方法,使用Maven这里是配置文件: <?xml version="1.0" encoding=" ...

  6. js 停止事件冒泡 阻止浏览器的默认行为(比如阻止超连接 # )

      在前端开发工作中,由于浏览器兼容性等问题,我们会经常用到“停止事件冒泡”和“阻止浏览器默认行为”. 1..停止事件冒泡 JavaScript代码 //如果提供了事件对象,则这是一个非IE浏览器if ...

  7. android studio 查看预览所有屏幕分辨率下的显示

    你可以打开在窗口的右侧预览面板设置上的布局.你可以通过修改面板顶部面板改变各种选项来修改预览,包括预览设备,主题,平台版本等等,可以同时预览多个设备上布局,可以从“设备”选项中下拉”预览所有屏幕的尺寸 ...

  8. 安卓 运行、调试 配置 android Run/debug configurations

    android  运行.调试 配置 android  Run/debug configurations 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq. ...

  9. 4572: [Scoi2016]围棋 轮廓线DP KMP

    国际惯例的题面:这种题目显然DP了,看到M这么小显然要状压.然后就是具体怎么DP的问题.首先我们可以暴力状压上一行状态,然后逐行转移.复杂度n*3^m+3^(m*2),显然过不去. 考虑状态的特殊性, ...

  10. Unsupported major.minor version ,

      一.错误现象: 当改变了jdk版本时,在编译java时,会遇到Unsupported major.minor version错误. 错误信息如下 : Unsupported major.minor ...