Linux下库打桩机制分析 function Interposition

2024-02-20 14:32

本文主要是介绍Linux下库打桩机制分析 function Interposition,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0 引言

本文主要参考《深入理解计算机系统》(原书第三版)ch7.13。作为个人知识整理和后续参考使用。

库打桩(interposition)这个名词比较陌生,这是由Linux链接器所提供的技术,允许用户截获对共享库函数的调用,并执行自己的代码(当然是在普通权限下,管理员权限通常是禁止使用该技术的)。
使用打桩机制,可以追踪某个特殊库函数的调用次数、验证并追踪其输入输出,甚至把它替换成一个完全不同的实现。

2 基本原理

打桩机制的基本实现原理如下:
给定需要打桩的目标函数,常见一个wrapper函数,其原型和目标函数一致。
利用特殊的打桩机制,可以实现让系统调用你的wrapper函数而不是目标函数。
wrapper函数中通常会执行自己的逻辑,然后调用目标函数,再将目标函数的返回值传递给调用者。

打桩可以发生在编译时、链接时或者程序被加载执行的运行时。不同的阶段都有对应的打桩机制,也有其局限性。
下文将以c标准库中的mallocfree函数的打桩来说明不同打桩机制。基本目标是用打桩来追踪程序运行时对mallocfree的调用。

3 示例一:编译时打桩

编译时打桩说白就是将对目标函数的调用替换为对应wrapper的调用。实现方式很简单,通过指定编译指令来实现。
下面代码实现了一个demo,用于说明如何使用预处理器实现编译时打桩。包装函数实现如下:

// malloc.h
#ifndef COMPILE_TIME
#define malloc(size) mymalloc(size)
#define free(ptr) myfree(ptr)
#endifvoid * mymalloc(size_t size);
void myfree(void *ptr);
// mymalloc.cpp
#ifdef COMPILE_TIME
#include <stdio.h>
#include <malloc.h>// malloc wrapper function
void * mymalloc(size_t size) {void * ptr = ::malloc(size);printf("malloc %p size %u\n", ptr, size);return ptr;
}// free wrapper function
void myfree(void *ptr) {::free(ptr);printf("free %p\n", ptr);
}
#endif

wrapper函数中我们调用目标函数,并打印追踪记录。本地的malloc.h头文件用于替换系统的目标函数调用逻辑,将其切换到对应的包装函数中。
所有源码可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]
interposition-打桩\compile目录找到,可以使用下面函数指令编译代码:

gcc -DCOMPILE_TIME -c mymalloc.cpp
gcc -I. -o intpos main.cpp mymalloc.o

编译后执行可执行文件,就可以得到将对malloc/free的调用转到我们的wrapper函数中。

4 示例二:链接时打桩

Linux静态链接器支持用--wrap f标志进行链接时打桩。这个标志告诉链接器,把对符号f的引用解析成__wrap_f(前缀是两个下划线),还要对符号__real_f的引用解析成f。
我们的wrap函数实现如下:

#ifdef LINK_TIME
#include <stdio.h>extern "C" {
void * __real_malloc(size_t size);
void __real_free(void * ptr);// malloc wrapper function
void * __wrap_malloc(size_t size) {printf("%s enter %u\n", __FUNCTION__, size);void * ptr = __real_malloc(size);printf("malloc %p size %u\n", ptr, size);return ptr;
}// free wrapper function
void __wrap_free(void *ptr) {__real_free(ptr);printf("free %p\n", ptr);
}
}
#endif

所有源码可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]interposition-打桩\link目录找到使用下面命令编译:

gcc -DLINK_TIME -c mymalloc.cpp
gcc -c main.cpp
gcc -Wl,--wrap,malloc -Wl,--wrap,free -o intpos main.o mymalloc.o

注意这里是WLL小写,不是数字1Linker option)。-Wl,option标志把option传递给链接器。option中的每个逗号都会替换为一个空格。即-Wl,--wrap,malloc就是把--wrap malloc传递给链接器。上面编译必须分开,否则可能会出错。
通过链接器的命令打桩也可以实现我们的目的,但是有一个缺点,你需要重新连接所有需要监测的模块。

5 示例三:运行时打桩

编译时打桩需要访问程序的源代码,连接时打桩需要能够访问程序的可重定位的对象文件。不过运行时打桩仅需要访问可执行目标文件即可,它的基本原理是基于动态链接器的LD_PRELOAD环境变量的

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

我们的wrapper函数实现如下

#ifdef RUNTIME
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>// malloc wrapper function
void * malloc(size_t size) {printf("%s enter %u\n", __FUNCTION__, size);void *(* mallocp)(size_t size);char * error;// get address of libc mallocmallocp = dlsym(RTLD_NEXT, "malloc");if ((error = dlerror()) != NULL) {fputs(error, stderr);exit(1);}void * ptr = mallocp(size);printf("malloc %p size %u\n", ptr, (int)size);return ptr;
}// free wrapper function
void free(void *ptr) {void (* freep)(void *ptr);char * error;// get address of libc freefreep = dlsym(RTLD_NEXT, "free");if ((error = dlerror()) != NULL) {fputs(error, stderr);exit(1);}freep(ptr);printf("free %p\n", ptr);
}
#endif

所有源码可以在我的SampleCode[https://git.oschina.net/Tocy/SampleCode.git]interposition-打桩\runtime目录找到,需要通过下面指令编译:
gcc -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.cpp -ldl -fpermissive

主函数不做修改。但运行时需要使用下面指令:
LD_PRELOAD="./mymalloc.so" ./intpos
如此就可以达到预期的效果,监测对mallocfree函数的调用。

6 补充知识:malloc调试变量——__malloc_hook

如果单纯为了处理malloc/free的调用,可以参考下万能的manual
__malloc_hookglibc提供的malloc调试变量中的一个,详情参考MALLOC_HOOK
只要在代码中添加__malloc_hook= my_malloc_hook;语句,当前程序中关于的malloc调用都会使用my_malloc_hook函数,简单方便。但是这组调试变量不是线程安全的,很多新的编译器已经将该功能废弃。有兴趣的可以参考下。

这篇关于Linux下库打桩机制分析 function Interposition的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728535

相关文章

MySQL中的锁机制详解之全局锁,表级锁,行级锁

《MySQL中的锁机制详解之全局锁,表级锁,行级锁》MySQL锁机制通过全局、表级、行级锁控制并发,保障数据一致性与隔离性,全局锁适用于全库备份,表级锁适合读多写少场景,行级锁(InnoDB)实现高并... 目录一、锁机制基础:从并发问题到锁分类1.1 并发访问的三大问题1.2 锁的核心作用1.3 锁粒度分

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

Redis的持久化之RDB和AOF机制详解

《Redis的持久化之RDB和AOF机制详解》:本文主要介绍Redis的持久化之RDB和AOF机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述RDB(Redis Database)核心原理触发方式手动触发自动触发AOF(Append-Only File)核

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

关于MyISAM和InnoDB对比分析

《关于MyISAM和InnoDB对比分析》:本文主要介绍关于MyISAM和InnoDB对比分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录开篇:从交通规则看存储引擎选择理解存储引擎的基本概念技术原理对比1. 事务支持:ACID的守护者2. 锁机制:并发控制的艺