工程师必备:C/C 单元测试万能插桩工具
研发效能是一个涉及面很广的话题,它涵盖了软件交付的整个生命周期,涉及产品、架构、开发、测试、运维,每个环节都可能影响顺畅、高质量地持续有效交付。在腾讯安全平台部实际研发与测试工作中我们发现,代码插桩隔离是单元测试工作中的一个强需求,然而业界现有 C/C 插桩工具由于使用上的局限性,运行效率和体验仍有很大改善空间。本文介绍了团队基于研效优化实践而自研的动态插桩工具,旨在实现单元测试的轻量化运行,提高代码覆盖率,从而助力研发团队的效能提升。
问题&思路
目前存在的 C/C 插桩工具,基本上都有各种使用上的局限,比如流行的 gmock,只能对 C 的虚函数进行插桩替换,针对非虚函数,则需要先对被测代码进行改造;同时对于系统接口,C 风格的第三方库代码,也无能为力。
如果可以绕开编译器,直接从底层入手,比如做机器指令修改,则可以不受语法及编译器的束缚,直接达到目的,这样在使用中就 几乎不受限制。
原理
C/C 语言编译后的可执行体,其实就是一个个的函数实现,每个函数的开头就是它的入口。一个函数 A 调用另一个函数 B,就是代码在执行过程中,控制流从函数 A 的某处跳到了函数 B 的开头,所以如果想用一个新的函数 C 取代函数 B,可以在函数 B 的开头用机器码的形式写入如下等价逻辑:
MOVQ ADDRESS_OF_C %RAX //将函数C的地址放到寄存器RAX
JMPQ *RAX //无条件跳转到RAX所指向的位置
这样,当控制流从函数 A 进入函数 B 的开始位置的时候,即会执行上述代码,从而直接跳转到 C 的开头处。其最终效果,是所有对函数 B 的调用,都如同直接调用了函数 C。
基于上述原理,被插桩的代码包括第三方库,如 MySql、其他同事未完成的模块、甚至是操作系统的 API 接口,如 read、select 等;
同时,桩函数不仅可以模拟原函数的返回值,实际上它作为一个普通的 C 函数,对原函数有完全的操作能力,比如可以访问传递给原函数调用真实的参数、C 成员变量(针对对成员函数的模拟),给定任意的返回值,访问全局变量、对调用进行计数等。
实际实现中,考虑到不同测试用例间的互不干扰,除了能执行函数替换,还需要在执行完一个测试时还原现场。这些具体细节可以直接参考代码。
使用
对全局函数插桩
原始函数:
int global(int a, int b) {return a b;
}
对应的桩函数:
int fake_global(int a, int b) {//校验参数正确性,确定被测代码传入了正确的值assert(a == 3);assert(b == 2);//给一个返回值,配合被测代码走特定分支return a - b;
}
插桩示例:
assert(global(3, 2) == 5);//通过mock调用,完成函数动态替换
assert(0 == mock(&global, &fake_global));//调用mock后的函数,可以看到返回值变了
assert(global(3, 2) == 1);//结束mock
reset();//函数行为恢复
assert(global(3, 2) == 5);
对普通成员函数插桩
被测代码:
class A {
public:int member(int a) {return a;}static int static_member(int a) {return 200;}virtual int virtual_member() {return 400;}
};
桩函数:
int fake_member(A *pTihs, int a) {//由于是对成员函数插桩,这里需要这个this指针参数return --a;
}
插桩示例:
A a;
assert(a.member(100) == 101);mock(&A::member, fake_member);
assert(a.member(100) == 99);reset();assert(a.member(100) == 101);
对静态成员函数插桩
桩函数:
int fake_static_member() {//静态函数不需要this指针return 300;
}
插桩示例:
assert(A::static_member(200) == 200);mock(&A::static_member, fake_static_member);
assert(A::static_member(100) == 300);reset();assert(A::static_member(200) == 200);
对虚函数插桩
桩函数:
int fake_virtual_member(A *pThis) {//虚函数同普通的成员函数由于,同样需要this指针return 500;
}
插桩示例:
A a;
assert(a.virtual_member() == 400);//虚函数mock需要多传一个相关类的对象,任意一个对象即可,跟实际代码中的对象没有关系
A a_obj;
mock(&A::virtual_member, fake_virtual_member, &a_obj);
assert(a.virtual_member() == 500);reset();
assert(a.virtual_member() == 400);
对系统及第三方库函数插桩
桩函数:
int fake_write(int, char*, int) {return 100;
}
插桩示例:
//直接写入一个无效的文件描述符,会失败
assert(write(5, "hello", 5) == -1);//来一个假的wirte
mock(write, fake_write);
//模拟调用成功
assert(write(5, "hello", 5) == 100);reset();assert(write(5, "hello", 5) == -1);
可以看到,对系统函数的 mock,其实跟普通的全局函数并无两样,第三方库函数也是同理。
使用限制&注意事项
目前支持 X86_64 平台上的 Linux、MacOS 系统,如有需求,Windows 和其它硬件平台,如 X86_32、ARM,也可在短期内支持。
MacOS 下,需要在执行前对单测可执行文件做以下修改:
printf '\x07' | dd of= bs=1 seek=160 count=1 conv=notrunc
显然,这种方法对内联函数无效,不过对于单元测试来说,可以关闭内联,同时也建议关闭其它编译器优化。
可以使用-fno-access-control 编译你的测试代码,可以使 g 关闭 c 成员的访问控制(即 protected 及 private 不再生效)。
项目地址
https://github.com/wangyongfeng5/lmock
结语
持续改进是研效工具平台发展的必经之路,欢迎感兴趣的同学与我们交流探讨,共同助力测试效能的优化。
声明:
本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。
工程师必备:C/C 单元测试万能插桩工具相关推荐
- 效能优化实践:C/C++单元测试万能插桩工具
作者:mannywang,腾讯安全平台后台开发 研发效能是一个涉及面很广的话题,它涵盖了软件交付的整个生命周期,涉及产品.架构.开发.测试.运维,每个环节都可能影响顺畅.高质量地持续有效交付.在腾讯安 ...
- 【字节码插桩】AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )
文章目录 一." 字节码插桩 " 技术简介 二.AspectJ 插桩工具 三.ASM 插桩工具 一." 字节码插桩 " 技术简介 性能优化 , 插件化 , 热修 ...
- java 插桩 工具_一个基于Eclipse的通用Java程序插桩工具.pdf
第38卷第7期 计算机科学 V01.38NO.7 Science 2011 2011年7月 Computer July 一个基于Eclipse的通用Java程序插桩工具 郑晓梅 (南京中医药大学信息技 ...
- linux静态插桩工具PEBIL
文章目录 引言 论文学习 摘要及简介 设计与实现 插桩代码效率 实验结果及其他 具体使用 引言 PEBIL是San Diego Supercomputer Center某实验室研发的工具,用来对ELF ...
- 网页算法动态插桩工具(Js逆向)
网页算法动态插桩工具(Js逆向) 一. 功能 通过 anyproxy工具拦截浏览器的请求 并通过AST 获取相关加密字段的函数 进行插桩 输出相关的内容 目前 测试 通过 的算法 有 AES DES ...
- 软件工程师必备的技能 | 单元测试
关注.星标公众号,直达精彩内容 来源:保罗的酒吧 前言 测试是软件开发过程中一个必须的环节,测试确保软件的质量符合预期. 对于工程师自己来说,单元测试也是提升自信心的一种方式. 直接交付没有经过测试的 ...
- android 插桩工具,Android Asm 插桩 教学项目
AndroidAutoTrack 本项目主要就是给大家一个参考学习的demo而已,主要是打算简化学习gradle插件的成本,以及对于android transform的一次抽象,将增量更新等等进行一次 ...
- intel 插桩工具 pin 介绍
目录 Pin 介绍
- 吹爆系列:Android 插桩之美,全面掌握~
作者:阿明的小蝴蝶 插桩 插桩是什么?你在开发中有用过插桩的技术吗? 所谓的插桩就是在代码编译期间修改已有的代码或者生成新代码. 插桩具体在编译的哪个流程介入呢? 插桩的作用与场景 代码生成 代码监控 ...
最新文章
- 华为:憧憬6G,共同定义6G
- 汇编环境搭建(vs2010(2012)+masm32)
- grep+awk+sort+wc实战
- Java培训教程之JDBC URL结构分析
- python多进程和多线程使用场景_Python36 多线程、多进程的使用场景
- 《Linux内核分析》第三周笔记 构造一个简单的Linux系统MenuOS
- SpringBoot学习(一)初识SpringBoot、第一个SpringBoot程序
- 超级精简版/超精简/懂你版IDM 6.25 build 23推荐
- 引擎提示Alias HeroDB跟游戏引擎启动异常怎么解决?
- XP系统时间同步和开启WindowsTime服务
- vue下拉框数据填充
- 城里人看呆!没想到现在景区都这么会玩了
- 在网页项目中集成扫码枪设备,实现二维码扫码识别实战
- 备份恢复Lesson 08. Using RMAN-Encrypted Backups
- MEMOS 技术支持
- CANopen DS402 驱动电机运动控制模式
- 【销售】如何做好网络安全产品的销售
- word2vec的cbow
- 反向放大器的缺点-运算放大器-运放
- actionscript3.0视频教程合集