记录一种在C语言中的打桩实现及原理
Posted 资质平庸的程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一种在C语言中的打桩实现及原理相关的知识,希望对你有一定的参考价值。
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语言中的打桩实现及原理的主要内容,如果未能解决你的问题,请参考以下文章
一种在C语言中用 System V ucontext 实现的协程切换