1. 程式人生 > 其它 >CSAPP CH7連結的應用:靜動態庫製作與神奇的庫打樁機制

CSAPP CH7連結的應用:靜動態庫製作與神奇的庫打樁機制

目錄

建立靜態庫

/* addvec.c */
/* $begin addvec */
int addcnt = 0;

void addvec(int *x, int *y,
	    int *z, int n) 
{
    int i;

    addcnt++;

    for (i = 0; i < n; i++)
	z[i] = x[i] + y[i];
}
/* $end addvec */

/* multvec.c */
/* $begin multvec */
int multcnt = 0;

void multvec(int *x, int *y, 
	     int *z, int n) 
{
    int i;

    multcnt++;

    for (i = 0; i < n; i++)
	z[i] = x[i] * y[i];
}
/* $end multvec */


我們建立一個關於矩陣的靜態庫,使用AR工具:

AR工具簡介,我們使用tldr檢視簡介的介紹

lion@ubuntu:~/csapp/code/link$ tldr ar

  ar

  Create, modify, and extract from archives (.a, .so, .o).
  More information: https://manned.org/ar.

  - Extract all members from an archive:
    ar -x path/to/file.a

  - List the members of an archive:
    ar -t path/to/file.a

  - Replace or add files to an archive:
    ar -r path/to/file.a path/to/file1.o path/to/file2.o

  - Insert an object file index (equivalent to using ranlib):
    ar -s path/to/file.a

  - Create an archive with files and an accompanying object file index:
    ar -rs path/to/file.a path/to/file1.o path/to/file2.o

執行指令

$ ar rcs libvector.a addvec.o multvec.o

這樣完成製作了一個名為libvector的靜態庫函式。

接下來是主函式:

/* main2.c */
/* $begin main2 */
#include <stdio.h>
#include "vector.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main() 
{
    addvec(x, y, z, 2);
    printf("z = [%d %d]\n", z[0], z[1]);
    return 0;
}
/* $end main2 */

先建立可重定向檔案 main2.o

$ gcc -c main2.c 

然後進行連結

$ gcc -static -o prog2c main2.o ./libvector.a 
或
$ gcc -static -o prog2c main2.o -L. -lvector
 注意命名要以lib開頭

這樣就生成了prog2c的可執行目標檔案。

建立動態庫

同理,先使用gcc對原始碼製作.so檔案

$ gcc -shared -fpic -o libvector.so addvec.c multvec.c

-shard 表明製作動態庫 -fpic指明需要與位置無關的目標檔案

$ gcc -o prog2l main2.c ./libvector.so

庫打樁機制

/* 
 * hello.c - Example program to demonstrate different ways to
 *           interpose on the malloc and free functions.
 *
 * Note: be sure to compile unoptimized (-O0) so that gcc won't
 * optimize away the calls to malloc and free.
 */
/* $begin interposemain */
#include <stdio.h>
#include <malloc.h>

int main()
{
    int *p = malloc(32);
    free(p);
    return(0); 
}
/* $end interposemain */

/* Local malloc header file */
/* $begin mallocheader */
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)

void *mymalloc(size_t size);
void myfree(void *ptr);
/* $end mallocheader */
/* 
 * mymalloc.c - Examples of run-time, link-time, and compile-time
 *              library interpositioning.
 */

/*
 * Run-time interpositioning of malloc and free based 
 * on the dynamic linker's (ld-linux.so) LD_PRELOAD mechanism
 * 
 * Example (Assume a.out calls malloc and free):
 *   linux> gcc -Wall -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl
 *
 *   bash> (LD_PRELOAD="./mymalloc.so" ./a.out)	
 *   ...or 
 *   tcsh> (setenv LD_PRELOAD "./mymalloc.so"; ./a.out; unsetenv LD_PRELOAD)
 */
/* $begin interposer */
#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
/* $end interposer */

/* 
 * Link-time interposition of malloc and free using the static
 * linker's (ld) "--wrap symbol" flag.
 * 
 * Compile the executable using "-Wl,--wrap,malloc -Wl,--wrap,free".
 * This tells the linker to resolve references to malloc as
 * __wrap_malloc, free as __wrap_free, __real_malloc as malloc, and
 * __real_free as free.
 */
/* $begin interposel */
#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
/* $end interposel */

/*
 * Compile-time interpositioning of malloc and free using the C
 * preprocessor. A local malloc.h file defines malloc and free as
 * wrappers mymalloc and myfree respectively.
 */
/* $begin interposec */
#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
/* $end interposec */

編譯時打樁:

lion@ubuntu:~/csapp/code/link/interpose$ gcc -DCOMPILETIME -c mymalloc.c
lion@ubuntu:~/csapp/code/link/interpose$ gcc -I. -o intc int.c mymalloc.o
lion@ubuntu:~/csapp/code/link/interpose$ ./intc 
malloc(32)=0x55f3e2c092a0
free(0x55f3e2c092a0)

其中-DCOMPILETIME 也就是 -D引數使用COMPILETIME來選擇程式碼段, -I 指定編譯打樁(前處理器在搜尋系統目錄之前會在當前目錄中查詢malloc.h

連結時打樁

編譯可重定向目標檔案

$ gcc -c int.c
$ gcc -DLINKTIME -c mymalloc.c

然後連結

$ gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o

其中, W1後的引數一直到空格會被傳遞給連結器,其中逗號會被替換成空格,後面同理。 --wrap標誌知道連結器進行連結時打樁

執行

lion@ubuntu:~/csapp/code/link/interpose$ ./intl 
malloc(32) = 0x55abc29192a0
free(0x55abc29192a0)  可見完成了對malloc和free的打樁

執行時打樁

編譯時打樁需要能訪問程式的原始碼,連結時打樁需要能訪問程式的可重定向檔案,執行時打樁只需要可以訪問可執行目標檔案 .so檔案。

製作執行時打樁的共享庫

$ gcc -Wall -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl

編譯主程式,這裡,可見編譯主檔案時,不需要任何關於動態庫的資訊。

$ gcc -o intr int.c

執行時需要指定動態庫的路徑,然而這裡出現了問題:

lion@ubuntu:~/csapp/code/link/interpose$ LD_PRELOAD="./mymalloc.so" ./intr 
Segmentation fault (core dumped)

人給的現成的程式碼都能出問題?為什麼會這樣呢?

執行時打樁的printf與malloc迴圈呼叫debug

通過gdb除錯,發現

#0  0x00007ffff78591c3 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7861849 in printf () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff7bd582e in malloc (size=1024) at badmalloc.c:19
#3  0x00007ffff7879185 in _IO_file_doallocate () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007ffff78874c4 in _IO_doallocbuf () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x00007ffff7886828 in _IO_file_overflow () from /lib/x86_64-linux-gnu/libc.so.6
#6  0x00007ffff78851bd in _IO_file_xsputn () from /lib/x86_64-linux-gnu/libc.so.6
#7  0x00007ffff7859201 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#8  0x00007ffff7861849 in printf () from /lib/x86_64-linux-gnu/libc.so.6
#9  0x00007ffff7bd582e in malloc (size=1024) at badmalloc.c:19
#10 0x00007ffff7879185 in _IO_file_doallocate () from /lib/x86_64-linux-gnu/libc.so.6
#11 0x00007ffff78874c4 in _IO_doallocbuf () from /lib/x86_64-linux-gnu/libc.so.6
#12 0x00007ffff7886828 in _IO_file_overflow () from /lib/x86_64-linux-gnu/libc.so.6
#13 0x00007ffff78851bd in _IO_file_xsputn () from /lib/x86_64-linux-gnu/libc.so.6
#14 0x00007ffff7859201 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#15 0x00007ffff7861849 in printf () from /lib/x86_64-linux-gnu/libc.so.6
#16 0x00007ffff7bd582e in malloc (size=1024) at badmalloc.c:19
#17 0x00007ffff7879185 in _IO_file_doallocate () from /lib/x86_64-linux-gnu/libc.so.6
#18 0x00007ffff78874c4 in _IO_doallocbuf () from /lib/x86_64-linux-gnu/libc.so.6
#19 0x00007ffff7886828 in _IO_file_overflow () from /lib/x86_64-linux-gnu/libc.so.6
#20 0x00007ffff78851bd in _IO_file_xsputn () from /lib/x86_64-linux-gnu/libc.so.6
#21 0x00007ffff7859201 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#22 0x00007ffff7861849 in printf () from /lib/x86_64-linux-gnu/libc.so.6
#23 0x00007ffff7bd582e in malloc (size=1024) at badmalloc.c:19
#24 0x00007ffff7879185 in _IO_file_doallocate () from /lib/x86_64-linux-gnu/libc.so.6
#25 0x00007ffff78874c4 in _IO_doallocbuf () from /lib/x86_64-linux-gnu/libc.so.6
---Type <return> to continue, or q <return> to quit---

從#9到#2(呼叫關係要倒著看):
malloc->printf->vfprintf->_IO_file_xsputn->_IO_file_overflow->_IO_doallocbuf->_IO_file_doallocate->malloc
我們的malloc函式中呼叫了printf函式,printf函式又呼叫了我們的malloc函式,malloc函式又會呼叫printf函式……這產生了一個呼叫死迴圈,呼叫層次足夠深,棧就溢位了。

那麼如何打破這個死迴圈呢?首先要儘量避免在自己寫的malloc函式中呼叫其他標準庫函式,畢竟不清楚標準庫函式的內部實現機制。但是為了輸出一些資訊,printf函式還是要保留的,那麼怎麼辦呢?首先考慮單執行緒的情況,如果在我們自己寫的malloc函式中發生了迴圈呼叫自己malloc的情況,唯一的可能就是printf呼叫了malloc。我們可以設定一個靜態計數變數count,每次完成執行malloc函式後將count清零,每次進入malloc函式後count自增1,如果count=1,說明現在呼叫棧上只有對自定義malloc函式的一次呼叫,這時可以呼叫printf輸出資訊;如果count=2,說明此時呼叫棧上對malloc函式發生了第二次呼叫,即一個malloc函式還沒有執行完,就又進行了一次malloc函式呼叫,我們認為這個問題出在printf上,此時我們就不再呼叫printf了。那麼多執行緒情況呢?這個使用__thread修飾符將靜態變數設定為thread local的就可以了。最後的malloc函式程式碼如下:

void *malloc(size_t size)
{
    static __thread int print_times = 0;
    print_times++;
    void *(*mallocp)(size_t size);
    char *error;

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

lion@ubuntu:~/csapp/code/link/interpose$ LD_PRELOAD="./mymalloc.so" ./intr
malloc(32) = 0x560aaff2c2a0
free(0x560aaff2c2a0)

此時,程式可以正確執行。

使用LD_PRELOAD對任意可執行程式呼叫執行時打樁

lion@ubuntu:~/csapp/code/link/interpose$ LD_PRELOAD="./mymalloc.so" /usr/bin/uptime 
malloc(37) = 0x5649964a12d0
malloc(472) = 0x5649964a1710
malloc(4096) = 0x5649964a18f0
malloc(1024) = 0x5649964a2900
free(0x5649964a2900)
free(0x5649964a1710)
free(0x5649964a18f0)
malloc(472) = 0x5649964a1710
malloc(1024) = 0x5649964a2900
free(0x5649964a2900)
free(0x5649964a1710)
malloc(472) = 0x5649964a1710
malloc(4096) = 0x5649964a18f0
malloc(1024) = 0x5649964a2900
free(0x5649964a2900)
free(0x5649964a1710)
free(0x5649964a18f0)
malloc(5) = 0x5649964a18f0
free(0x5649964a18f0)
malloc(120) = 0x5649964a1910
malloc(12) = 0x5649964a18f0
malloc(776) = 0x5649964a1990
malloc(112) = 0x5649964a1ca0
malloc(1336) = 0x5649964a1d20
malloc(216) = 0x5649964a2260
malloc(432) = 0x5649964a2340
malloc(104) = 0x5649964a2500
malloc(88) = 0x5649964a2570
malloc(120) = 0x5649964a25d0
malloc(168) = 0x5649964a2650
malloc(104) = 0x5649964a2700
malloc(80) = 0x5649964a2770
malloc(192) = 0x5649964a27d0
malloc(12) = 0x5649964a28a0
malloc(12) = 0x5649964a28c0
malloc(12) = 0x5649964a28e0
malloc(12) = 0x5649964a2d10
malloc(12) = 0x5649964a2d30
malloc(12) = 0x5649964a2d50
malloc(12) = 0x5649964a2d70
malloc(12) = 0x5649964a2d90
malloc(12) = 0x5649964a2db0
malloc(12) = 0x5649964a2dd0
malloc(12) = 0x5649964a2df0
malloc(12) = 0x5649964a2e10
malloc(12) = 0x5649964a2e30
malloc(34) = 0x5649964a2e50
malloc(10) = 0x5649964a2e80
malloc(15) = 0x5649964a2ea0
malloc(472) = 0x5649964a1710
malloc(4096) = 0x5649964a2ec0
malloc(1757) = 0x5649964a3ed0
free(0x5649964a2ec0)
free(0x5649964a1710)
malloc(20) = 0x5649964a2ec0
malloc(20) = 0x5649964a2ee0
malloc(20) = 0x5649964a2f00
malloc(20) = 0x5649964a2f20
malloc(20) = 0x5649964a2f40
malloc(12) = 0x5649964a2f60
malloc(271) = 0x5649964a2f80
free(0x5649964a2df0)
free(0x5649964a2e30)
malloc(12) = 0x5649964a2e30
malloc(12) = 0x5649964a2df0
free(0x5649964a2f80)
free(0x5649964a2f60)
malloc(384) = 0x5649964a30a0
malloc(12) = 0x5649964a2f60
malloc(271) = 0x5649964a2f80
free(0x5649964a2e30)
free(0x5649964a2df0)
malloc(12) = 0x5649964a2df0
malloc(12) = 0x5649964a2e30
free(0x5649964a2f80)
free(0x5649964a2f60)
 04:17:18 up  9:29,  1 user,  load average: 0.02, 0.04, 0.04

總結

庫打樁是Linux連結器的一個很強大的功能,截獲對庫函式的呼叫。跟蹤呼叫次數等等,也可以用來驗證輸入輸出,或者將其替換成一個完全不同的實現。可以想象利用此技術可以做到的非常有趣的事情。

CSAPP 第七章 連結