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 第七章 链接
CSAPP CH7链接的应用:静动态库制作与神奇的库打桩机制的更多相关文章
- iOS开发中静态库制作 之.a静态库制作及使用篇
iOS开发中静态库之".a静态库"的制作及使用篇 一.库的简介 1.什么是库? 库是程序代码的集合,是共享程序代码的一种方式 2.库的类型? 根据源代码的公开情况,库可以分为2种类 ...
- Anroid逆向学习从编写so到静动态调试分析arm的一次总结
Anroid逆向学习从编写so到静动态调试分析arm的一次总结 一.前言 最近跟着教我兄弟学逆向这篇教程学习Android逆向,在第七课后作业反复折腾了好几天,正好在折腾的时候对前面的学习总结一波,动 ...
- 技巧:Linux 动态库与静态库制作及使用详解
技巧:Linux 动态库与静态库制作及使用详解 标准库的三种连接方式及静态库制作与使用方法 Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 L ...
- 关于VS2013下制作和使用静态库和动态库
关于VS2013下制作和使用静态库和动态库 引言 什么是库:库是写好的现有的,成熟的,可以复用的代码. 所谓静态.动态是指链接.将一个程序编译成可执行程序的步骤: 静态库在链接阶段,会将汇编生成的目标 ...
- Linux下制作和使用静态库和动态库
概述 Linux操作系统支持的函数库分为静态库和动态库,动态库又称共享库.linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib. 静态函数库: 这类库的名字一般是libxxx. ...
- 原创 C++应用程序在Windows下的编译、链接(四)动态链接
4动态链接 4.1概述 在静态链接阶段,链接器为PE文件生成了导入表,导出表,符号表,并调整了Call指令后面的操作数,在程序调用的时候,能够直接地或者间接地定位到IAT中的某个位置,在PE文件中,该 ...
- Linux链接库四(多个库文件链接顺序问题)
最近在Linux下编程发现一个诡异的现象,就是在链接一个静态库的时候总是报错,类似下面这样的错误: (.text+0x13): undefined reference to `func' 关于unde ...
- Linux下库打桩机制分析 function Interposition
[时间:2017-08] [状态:Open] [关键词:linux, libray,打桩,interposition,函数替换,链接器,gcc,malloc,free] 0 引言 本文主要参考< ...
- 配置自己的OpenGL库,glew、freeglut库编译,库冲突解决(附OpenGL Demo程序)
平台:Windows7,Visual C++ 2010 1. 引言 实验室的一个项目,用到OpenGL进行实时绘制,还用到一些其他的库,一个困扰我很久的问题就是编译时遇到的各种符号未定义,符号重定义之 ...
随机推荐
- 消息队列 - mac上安装RabbitMq (转)
什么是RabbitMQ? RabbitMQ是由Erlang语言编写的实现了高级消息队列协议(AMQP)的开源消息代理软件(也称为面向消息的中间件).支持WIndows.Linux.MAC OS 操作系 ...
- git rebase git merge
Git rebase 使用方法 1. git checkout feature 2. git rebase master feature 相当于git rebase master + git chec ...
- 有关OPenCV的几个库函数的使用
转载请注明来源:https://www.cnblogs.com/hookjc/ 1) IplImage* cvCreateImage( CvSize size, int depth, int chan ...
- Ubuntu20.04 安装MongoDB及其基本使用
MongoDB 官方文档 本博客旨在自我学习使用,如有任何疑问请及时联系博主 基本介绍 MongoDB 是由C++语言编写并基于分布式文件存储的开源数据库. MongoDB 是一款介于关系数据库和非关 ...
- 学多少年才算“精通Java”?
我从毕业做程序员就开始用 Java,到现在已经工作快 20 年了.减去我做手游用 C++.Lua 的几年,再减去后来转管理写代码少的时间,我真正写 Java 代码的时间至少也在 10 年以上. 如果你 ...
- 树莓派PICO刷入MicroPython内核的两种方式
一.MicroPython简介 MicroPython 是 Python 3编程语言的精简高效实现,其中包括 Python 标准库的一小部分,并且经过优化,可在微控制器和受限环境中运行. 可以在 25 ...
- 八大排序算法之直接插入排序(InsertionSort)
常见的排序算法 今天复习[直接插入排序] 核心思想:有序数组中 找位置 -- 给无序数组第一个 找位置 ` public class InsertionSort { // 核心思想:有序数组中 找位置 ...
- 在k8s中使用性能分析神器:arthas
Arthas(阿尔萨斯)是阿里巴巴开源的性能分析神器. k8s中使用arthas的三种方式 [bak]https://www.cnblogs.com/uncleyong/p/15498842.html ...
- Flume学习记录
Flume 是 Cloudera 提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传 输的系统.Flume 基于流式架构,灵活简单. Flume基础架构 Agent Agent 是一个 JV ...
- 华为eNSP的防火墙(USG6000V)如何使用Web界面登入
文章目录 华为eNSP的防火墙(USG6000V)如何使用Web界面登入 前言 一.使用步骤 1.导入USG6000V的镜像包 总结 前言 在华为的eNSP的模拟器上如何使用Web界面去管理与使用模拟 ...