# 7.13 库打桩机制

Linux 链接器支持一个很强大的技术，称为库打桩（library interpositioning），它允许你截获对共享库函数的调用，取而代之执行自己的代码。使用打桩机制，你可以追踪对某个特殊库函数的调用次数，验证和追踪它的输入和输出值，或者甚至把它替换成一个完全不同的实现。

下面是它的基本思想：给定一个需要打桩的目标函数，创建一个包装函数，它的原型与目标函数完全一样。使用某种特殊的打桩机制，你就可以欺骗系统调用包装函数而不是目标函数了。包装函数通常会执行它自己的逻辑，然后调用目标函数，再将目标函数的返回值传递给调用者。

打桩可以发生在编译时、链接时或当程序被加载和执行的运行时。要研究这些不同的机制，我们以图 7-20a 中的示例程序作为运行例子。它调用 C 标准库（libc.so）中的 malloc 和 free 函数。对 malloc 的调用从堆中分配一个 32 字节的块，并返回指向该块的指针。对 free 的调用把块还回到堆，供后续的 malloc 调用使用。我们的目标是用打桩来追踪程序运行时对 malloc 和 free 的调用。

## 7.13.1 编译时打桩

图 7-20 展示了如何使用 C 预处理器在编译时打桩。mymalloc.c 中的包装函数（图 7-20c）调用目标函数，打印追踪记录，并返回。本地的 malloc.h 头文件（图 7-20b）指示预处理器用对相应包装函数的调用替换掉对目标函数的调用。

{% tabs %}
{% tab title="code/link/interpose/int.c" %}

```c
#include <stdio.h>
#include <malloc.h>

int main()
{
    int *p = malloc(32);
    free(p);
    return(0);
}
```

{% endtab %}
{% endtabs %}

> a) 示例程序 int.c

{% tabs %}
{% tab title="code/link/interpose/malloc.h" %}

```c
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)

void *mymalloc(size_t size);
void myfree(void *ptr);
```

{% endtab %}
{% endtabs %}

> b) 本地 malloc.h 文件

{% tabs %}
{% tab title="code/link/interpose/mymalloc.c" %}

```c
#ifdef COMPILETIME
#include <stdio.h>
#include <malloc.h>

/* malloc wrapper function */
void *mymalloc(size_t size)
{
    void *ptr = malloc(size);
    printf("malloc(%d)=%p\n",
           (int)size, ptr);
    return ptr;
}

/* free wrapper function */
void myfree(void *ptr)
{
    free(ptr);
    printf("free(%p)\n", ptr);
}
#endif
```

{% endtab %}
{% endtabs %}

> c) mymalloc.c 中的包装函数
>
> 图 7-20 用 C 预处理进行编译时打桩

像下面这样编译和链接这个程序：

```bash
linux> gcc -DCOMPILETIME -c mymalloc.c
linux> gcc -I. -o intc int.c mymalloc.o
```

运行这个程序会得到如下的追踪信息：

```c
linux> ./intc
malloc(32)=0x9ee010
free(0x9ee010)
```

## 7.13.2 链接时打桩

Linux 静态链接器支持用 **--wrap f** 标志进行链接时打桩。这个标志告诉链接器，把对符号 **f** 的引用解析成 **wrap\_f**（前缀是两个下划线），还要把对符号 **\_\_real\_f**（前缀是两个下划线）的引用解析为 **f**。图 7-21 给出我们示例程序的包装函数。

{% tabs %}
{% tab title="code/link/interpose/mymalloc.c" %}

```c
#ifdef LINKTIME
#include <stdio.h>

void *__real_malloc(size_t size);
void __real_free(void *ptr);

/* malloc wrapper function */
void *__wrap_malloc(size_t size)
{
    void *ptr = __real_malloc(size); /* Call libc malloc */
    printf("malloc(%d) = %p\n", (int)size, ptr);
    return ptr;
}

/* free wrapper function */
void __wrap_free(void *ptr)
{
    __real_free(ptr); /* Call libc free */
    printf("free(%p)\n", ptr);
}
#endif
```

{% endtab %}
{% endtabs %}

> 图 7-21 用 **--wrap** 标志进行链接时打桩

用下述方法把这些源文件编译成可重定位目标文件：

```bash
linux> gcc -DLINKTIME -c mymalloc.c
linux> gcc -c int.c
```

然后把目标文件链接成可执行文件：

```bash
linux> gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o
```

**-Wl,option** 标志把 **option** 传递给链接器。**option** 中的每个逗号都要替换为一个空格。所以 **-Wl,--wrap,malloc** 就把 **--wrap malloc** 传递给链接器，以类似的方式传递 **-Wl,--wrap,free**。

运行该程序会得到如下追踪信息：

```bash
linux> ./intl
malloc(32) = 0x18cf010
free(0x18cf010)
```

## 7.13.3 运行时打桩

编译时打桩需要能够访问程序的源代码，链接时打桩需要能够访问程序的可重定位对象文件。不过，有一种机制能够在运行时打桩，它只需要能够访问可执行目标文件。这个很厉害的机制基于动态链接器的 LD\_PRELOAD 环境变量。

如果 LD\_PRELOAD 环境变量被设置为一个共享库路径名的列表（以空格或分号分隔），那么当你加载和执行一个程序，需要解析未定义的引用时，动态链接器（LD-LINUX.SO）会先搜索 LD\_PRELOAD 库，然后才搜索任何其他的库。有了这个机制，当你加载和执行任意可执行文件时，可以对任何共享库中的任何函数打桩，包括 libc.so。

图 7-22 展示了 malloc 和 free 的包装函数。每个包装函数中，对 dlsym 的调用返回指向目标 libc 函数的指针。然后包装函数调用目标函数，打印追踪记录，再返回。

{% tabs %}
{% tab title="code/link/interpose/mymalloc.c" %}

```c
#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

/* malloc wrapper function */
void *malloc(size_t size)
{
    void *(*mallocp)(size_t size);
    char *error;

    mallocp = dlsym(RTLD_NEXT, "malloc"); /* Get address of libc   malloc */ 
    if ((error = dlerror()) != NULL) { 
        fputs(error, stderr);
        exit(1);
    }
    char *ptr = mallocp(size); /* Call libc malloc */
    printf("malloc(%d) = %p\n", (int)size, ptr);
    return ptr;
}

/* free wrapper function */
void free(void *ptr)
{
    void (*freep)(void *) = NULL;
    char *error;

    if (!ptr)
    return;

    freep = dlsym(RTLD_NEXT, "free"); /* Get address of libc free */
    if ((error = dlerror()) != NULL) {
        fputs(error, stderr);
        exit(1);
    }
    freep(ptr); /* Call libc free */
    printf("free(%p)\n", ptr);
}
#endif
```

{% endtab %}
{% endtabs %}

> 图 7-22 用 LD\_PRELOAD 进行运行时打桩

下面是如何构建包含这些包装函数的共享库的方法：

```
linux> gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
```

这是如何编译主程序：

```
linux> gcc -o intr int.c
```

下面是如何从 bash shell 中运行这个程序：

```bash
linux> LD_PRELOAD="./mymalloc.so" ./intr
malloc(32) = 0x1bf7010
free(0x1bf7010)
```

下面是如何在 csh 或 tcsh 中运行这个程序：

```bash
linux> (setenv LD_PRELOAD "./mymalloc.so"; ./intr; unsetenv LD_PRELOAD)
malloc(32) = 0x2157010
free(0x2157010)
```

请注意，你可以用 LD\_PRELOAD 对任何可执行程序的库函数调用打桩！

```bash
linux> LD_PRELOAD="./mymalloc.so" /usr/bin/uptime
malloc(568) = 0x21bb010
free(0x21bb010)
malloc(15) = 0x21bb010
malloc(568) = 0x21bb030
malloc(2255) = 0x21bb270
free(0x21bb030)
malloc(20) = 0x21bb030
malloc(20) = 0x21bb050
malloc(20) = 0x21bb070
malloc(20) = 0x21bb090
malloc(20) = 0x21bb0b0
malloc(384) = 0x21bb0d0
20:47:36 up 85 days, 6:04, 1 user, load average: 0.10, 0.04, 0.05
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hansimov.gitbook.io/csapp/part2/ch07-linking/7.13-library-interpositioning.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
