1 场景

考虑一个由多个模块组成的工程,如

.
└── wsw├── m1├── m2└── ...

假设 m2 中的接口会调用 m1 中的接口。

当要初步验证 m2 中各接口功能时,由于 m1 中各接口的运行往往依赖 m1 模块上下文(而在验证 m2 时往往不想参与 m1 模块的流程),此时需跳过或用其他接口模拟 m1 接口所产生的数据。

一种直接的方法是编写与 m1 中函数同名的函数,并指定 m2 链接新的接口。

若原工程管理比较复杂,不太好将 m1 从工程文件中中拆分出去,则可选择用“打桩”的方式跳过 m1 中的接口。

                                      _m1_f
m2_fn              m1_fn            +->+--------+
+----------+    +->+------------+ ② |  |        |
|   ...    |    |  | jmp _m1_fn |---+  |   .    |
+----------+  ① |  +------------+      |   .    |
|call m1_fn|----+  |    ...     |      |   .    |
+----------+<--+                       +--------+
|   ...    |   |                  ③    | return |+-----------------------+--------+

上图只表示“打桩”后程序执行的大体逻辑流程,不代表各函数在进程中的实际位置和实际执行过程。

2 原理

正如前一节图中所示,实现“打桩”的一种方式是修改函数头部为一条跳转指令,该跳转指令跳转到桩函数(_m1_f)中。下面大体分析下其可行原理。

[1] 修改代码段即函数头部(基于OS的应用程序可通过具 level0 权限的系统调用如 mprotect完成)

[2]在接口开始处添加跳转指令不会破坏函数栈帧关系

+=======+----------------
|       |      ↑
|       |    stack
|       |      ↓
+=======+----------------
|.......|
+=======+----------------
|m2_fn()|     ↑         ↑
+=======+m2_snippets    |
|.......|     ↓         |
+=======+-----------    |
|m1_fn()|     ↑         |
+=======+m1_snippets  .text
|.......|     ↓         |
+=======+-----------    |
|_m1_f()|     ↑         |
+=======+_m1_snippets   |
|.......|     ↓         |
+=======+-----------    |
|.......|               ↓
+=======+----------------
进程空间+=======+----------------------------------
|   .   |m2_fn 调 _m1_f         ↑
|   .   |栈中备份PC             |m2_fn 栈帧
|   .   |修改PC跳转_m1_f        |
|m1_fn()|                       ↓
+-------+----------------------------------
|   .   |m1_fn return 时        ↑
|   .   |弹出栈中备份PC         |
|   .   |返回到调用 m1_fn() 处  |m1_fn 栈帧
|return |                       ↓
+=======+----------------------------------
|.......|
+=======+----------------  <--- running
|m2_fn()|     ↑         ↑
+=======+m2_snippets    |
|.......|     ↓         |
+=======+-----------    |
|m1_fn()|     ↑         |
+=======+m1_snippets  .text
|.......|     ↓         |
+=======+-----------    |
|_m1_f()|     ↑         |
+=======+_m1_snippets   |
|.......|     ↓         |
+=======+-----------    |
|.......|               ↓
+=======+----------------
函数运行时维护的栈帧+=======+-----------------------------------
|   .   |m2_fn 调 m1_fn       ↑
|   .   |栈中备份PC            |m2_fn 栈帧
|   .   |修改PC跳转m1_fn       |
|m1_fn()|                     ↓
+-------+-----------------------------------
|   .   |m1_fn 开始语句为      ↑
|   .   |跳转到 _m1_f 处       |
|   .   |不会运行其内开辟       | m1_fn 无栈帧
|   .   |栈帧的语句            ↓
+-------+-----------------------------------
|   .   |_m1_f return时       ↑
|   .   |弹出m2_fn栈中备份PC   | _m1_f 栈帧
|   .   |返回到调m1_fn处       |
| return|跟在m1_fn中return一样 ↓
+=======+-----------------------------------
|.......|
+=======+
打桩对栈帧无破坏力

3 实现

略加体验一下吧。

3.1 做一些跨平台的封装

/*** wsw_mprotect.h* snippets on modify protections.** lxr, 2020.08 */
#ifndef _WSW_MPROTECT_H_INCLUDED_
#define _WSW_MPROTECT_H_INCLUDED_#include <sys/mman.h>#define WSW_MPROTECT_RX             (PROT_READ | PROT_EXEC)
#define WSW_MPROTECT_RWX            (PROT_READ | PROT_WRITE | PROT_EXEC)
#define wsw_mprotect(a, len, prot)  mprotect(a, len, prot)#endif /* _WSW_MPROTECT_H_INCLUDED_ *//*** wsw_page.h* snippets on memory page.** lxr, 2020.011 */
#ifndef _WSW_PAGE_H_INCLUDED_
#define _WSW_PAGE_H_INCLUDED_#ifdef WSW_HAS_GETPAGESIZE
#include <unistd.h>
#define wsw_getpagesize()   getpagesize()#else
#define wsw_getpagesize()   (4096)
#endif#endif /* _WSW_PAGE_H_INCLUDED_ */

3.2 组装打桩接口

/*** wsw_stub.h* snippets on stub.** lxr, 2020.11 */#ifndef _WSW_STUB_H_INCLUDED_
#define _WSW_STUB_H_INCLUDED_#include <stdlib.h>
#include "wsw_main.h"/* if the jmp instruction changed,then the len follows the change. */
#define WSW_STUB_JMPQ       0xe9
#define WSW_STUB_JMPQ_LEN   (1)
#define WSW_STUB_JMPQ_OPLEN (4)
#define WSW_STUB_LEN        (WSW_STUB_JMPQ_LEN + WSW_STUB_JMPQ_OPLEN)/** * instr|operands* +----+-+-+-+-+* |jmpq| | | | |* +----+-+-+-+-+* 0    1 2 3 4 5* if the jmp instruction changed,* then type of operand for jmp follows the changed. */
typedef WSW_UINT32_T                WSW_STUB_JMPQ_OP_T;
typedef struct wsw_stub_manage_s    WSW_STUB_MAN_S;extern WSW_STUB_MAN_S *
wsw_stub_init(size_t stub_len);static wsw_inline void
wsw_stub_deinit(WSW_STUB_MAN_S *stubm)
{free(stubm);return ;
}extern int
wsw_stub_reset(WSW_STUB_MAN_S *stub_m);extern int
wsw_stub_set(WSW_STUB_MAN_S *stub_m, void *fn, void *stub_fn);#endif /* _WSW_STUB_H_INCLUDED_ *//*** wsw_stub.c* snippets on stub.** lxr, 2020.11 */#include "wsw_stub.h"#define WSW_STUB_MAN_SSIZE  sizeof(WSW_STUB_MAN_S)static unsigned _wsw_stub_pagesize;struct wsw_stub_manage_s {void   *fn;char   *stub;size_t  len;
};static wsw_inline WSW_UINTPTR_T
wsw_alignpage(WSW_UINTPTR_T v)
{unsigned pg;pg = _wsw_stub_pagesize;return (v & ~((WSW_UINTPTR_T) (pg - 1u)));
}static wsw_inline int
wsw_mprotect_write(void *fn)
{int      ret;void    *ap;size_t   pg;pg = _wsw_stub_pagesize;ap = (void *)wsw_alignpage((WSW_UINTPTR_T) fn);ret = wsw_mprotect(ap, pg, WSW_MPROTECT_RWX);WSW_IF_EXPS_RETURN(WSW_ERR_SYSCALL == ret, ret);return WSW_ERR_NONE;
}static wsw_inline int
wsw_mprotect_recovery(void *fn)
{int      ret;void    *ap;size_t   pg;pg = _wsw_stub_pagesize;ap = (void *)wsw_alignpage((WSW_UINTPTR_T) fn);ret = wsw_mprotect(ap, pg, WSW_MPROTECT_RX);WSW_IF_EXPS_RETURN(WSW_ERR_SYSCALL == ret, ret);return WSW_ERR_NONE;
}WSW_STUB_MAN_S *
wsw_stub_init(size_t stub_len)
{WSW_STUB_MAN_S *m;m = wsw_calloc(WSW_STUB_LEN + stub_len);WSW_IF_EXPS_RETURN(!m, NULL);m->len  = stub_len;m->stub = wsw_memb(m) + WSW_STUB_MAN_SSIZE;_wsw_stub_pagesize = wsw_getpagesize();return m;
}int
wsw_stub_set(WSW_STUB_MAN_S *stub_m, void *fn, void *stub_fn)
{int     ret;void   *opaddr;size_t  jmpq_op_len;WSW_IF_EXPS_RETURN(!stub_m || !fn || !stub_fn, WSW_ERR_BADPARAM);ret = wsw_mprotect_write(fn);WSW_IF_EXPS_RETURN(WSW_ERR_NONE != ret, ret);stub_m->fn  = fn;jmpq_op_len = stub_m->len;memcpy(stub_m->stub, fn, jmpq_op_len);*((WSW_UINT8_T *) fn) = WSW_STUB_JMPQ;opaddr = wsw_memb(fn) + WSW_STUB_JMPQ_LEN;/* 注释见后文 */*((WSW_STUB_JMPQ_OP_T *) opaddr) =             \wsw_memb(stub_fn) - wsw_memb(fn) - WSW_STUB_LEN;(void) wsw_mprotect_recovery(fn);return WSW_ERR_NONE;
}int
wsw_stub_reset(WSW_STUB_MAN_S *stub_m)
{int      ret;void    *fn;WSW_IF_EXPS_RETURN(!stub_m, WSW_ERR_BADPARAM);fn = stub_m->fn;ret = wsw_mprotect_write(fn);WSW_IF_EXPS_RETURN(WSW_ERR_NONE != ret, ret);memcpy(fn, stub_m->stub, stub_m->len);(void) wsw_mprotect_recovery(fn);return WSW_ERR_NONE;
}

对 WSW_STUB_JMPQ 操作数的注释说明。

  +------------+|    ...     |
0 +============+-------------------------------|jmpq offset |                             ↑
5 +------------+-------                      ||     .      | |                           ||     .      | | offset = stub_fn - fn - 5 |fn
x +============+ |                           ||    ...     | ↓                           ↓
0 +============+-------------------------------|     .      |   ↑|     .      |stub_fn|     .      |   ↓
y +============+-------
.text

3.3 调用接口体验

static void
test_stub_fn(void)
{fprintf(stderr, "%s\n", __func__);return ;
}void
test_fn(void)
{fprintf(stderr, "%s\n", __func__);return ;
}int main(void)
{int             i, ret;WSW_STUB_MAN_S *m;m = wsw_stub_init(WSW_STUB_LEN);if (!m) return -1;ret = wsw_stub_set(m, test_fn, test_stub_fn);if (WSW_ERR_NONE != ret) {wsw_stub_deinit(m);return -1;}test_fn();wsw_stub_reset(m);test_fn();wsw_stub_deinit(m);return 0;
}

运行输出:

test_stub_fn
test_fn

记录一种在C语言中的打桩实现及原理相关推荐

  1. java 三种错误类型 区别_请列举至少三种在java语言中发生“严重错误”的情况...

    [简答题]自已编写一个自定义非整数异常类,来处理一个异常 [填空题]捕获异常时,可以把catch捕获的异常对象( ),使上层try-catch结构继续处理该异常事件;也可以把异常对象转换为其它异常对象 ...

  2. 【C语言进阶深度学习记录】三十三 C语言中动态内存分配

    如何在程序运行的时候动态给程序分配内存? 文章目录 1 动态内存分配的意义 1.1 C语言中如何动态申请内存空间 1.2 malloc和free的用法 1.3 calloc与realloc 1.31 ...

  3. 【C语言进阶深度学习记录】十四 C语言中 三目运算符和逗号表达式

    文章目录 1 三目运算符 1.1 三目运算符的返回类型的代码案例分析 2 逗号表达式 2.1 逗号表达式代码案例分析 2.2 如何用一行代码实现 strlen函数 3 总结 1 三目运算符 三目运算符 ...

  4. 【C语言进阶深度学习记录】二十七 C语言中字符串的相等比较

    文章目录 1 字符串的相等比较 1.1 代码分析 1 字符串的相等比较 如果有字符串s1 = "Hello"; s2 = "Hello" ; 在我们看来s1与s ...

  5. 记录一种多个按钮中每次只能选中一个的实现方式

    阐述 本文旨在说明实现的思路. 当窗口创建很多QPushButton或者QPushButton的子类的时候,若要实现在众多的按钮中,每次被点击的按钮被选中,下一次点击另一个按钮的时候,上一个被选中的按 ...

  6. 【C语言进阶深度学习记录】三十五 程序中的堆、栈以及静态存储区(数据区)

    学习交流加 个人qq: 1126137994 个人微信: liu1126137994 学习交流资源分享qq群: 962535112 在我之前学习底层的知识的时候,也写过相关的内容.可以对比的学习:[软 ...

  7. gRPC 在 Go 语言中的安装与简单实践

    现在非常流行微服务,而 RPC 框架是微服务中不可或缺的一环,gRPC 是其中一个非常出色的 RPC 框架,所以借此机会来记录一下 gRPC 在 Go 语言中的安装使用以及运用. PS.刚弄好 WSL ...

  8. JAVA语言异常_Java语言中常用异常类EoFException是用来处理( )异常的类_学小易找答案...

    [填空题]Java语言中常用异常类ClassNotFoundException是用来处理 ( )的异常的类 [填空题]Java语言声明 ( ) 类为会产生"严重错误"的类 [简答题 ...

  9. Linux结构体变量报错,C语言中的结构体

    用户自己建立自己的结构体类型 1.  定义和使用结构体变量 (1).结构体的定义 C语言允许用户自己建立由不同类型数据组成的组合型的数据结构,它称为结构体. (2).声明一个结构体类型的一般形式为: ...

最新文章

  1. 如何解决问题:程序无法正常启动(0xc0000022)
  2. 第16天学习Java的笔记(标准类,Scanner)
  3. 正则表达式实现手机号中间4位数隐藏或者只显示末尾四位数
  4. JavaScript数据类型:Typeof解释
  5. 双显示器N卡安装ubuntu驱动以及解决办法
  6. C/C++ Native 包大小测量
  7. 华为nova7保密柜_华为nova8系列发布 Vlog视频旗舰3299元起
  8. Nginx在嵌入式系统中的应用
  9. Cesium加载OSGB数据
  10. matlab rand求圆周率,MATLAB做投针实验求圆周率
  11. 线性代数系列(八)--线性代数和图论
  12. Uml工具StarUML破解
  13. Nutch简介(转3)
  14. linux系统玩什么游戏,linux系统可以玩什么网游
  15. 从网站细节入手提高易用性
  16. 根据php经纬度百度地图打点,PHP使用百度地图获取指定地址坐标:经纬度(图文+视频)...
  17. echarts柱状图改变标签的位置及柱状图颜色
  18. Mysql出租车轨迹的分析_一种基于出租车轨迹数据的交通出行共现现象的可视化分析方法与流程...
  19. SUSE史上首位女性CEO Melissa Di Donato,不止有“三把火”
  20. 平行泊车系统路径规划(5)

热门文章

  1. 关于3dtiles的一些理解
  2. EMQTT benchmark测试
  3. anaconda spyder使用技巧
  4. SOME/IP协议详解「2.1.1·SOME/IP的格式头」
  5. ios afn html解析,iOS开发关于使用AFN遇到的问题总结
  6. Hbase原理与实践(学习笔记一:基本概念):
  7. 南京大学计算机专业课,南京大学计算机专业厉害吗?
  8. MATLAB的基本知识
  9. AI赋能:Optibus推出新的人工智能工具帮助规划城市交通路线
  10. Websphere MQ Everyplace 和 Websphere MQ集成实践