研发效能是一个涉及面很广的话题,它涵盖了软件交付的整个生命周期,涉及产品、架构、开发、测试、运维,每个环节都可能影响顺畅、高质量地持续有效交付。在腾讯安全平台部实际研发与测试工作中我们发现,代码插桩隔离是单元测试工作中的一个强需求,然而业界现有 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 单元测试万能插桩工具相关推荐

  1. 效能优化实践:C/C++单元测试万能插桩工具

    作者:mannywang,腾讯安全平台后台开发 研发效能是一个涉及面很广的话题,它涵盖了软件交付的整个生命周期,涉及产品.架构.开发.测试.运维,每个环节都可能影响顺畅.高质量地持续有效交付.在腾讯安 ...

  2. 【字节码插桩】AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

    文章目录 一." 字节码插桩 " 技术简介 二.AspectJ 插桩工具 三.ASM 插桩工具 一." 字节码插桩 " 技术简介 性能优化 , 插件化 , 热修 ...

  3. java 插桩 工具_一个基于Eclipse的通用Java程序插桩工具.pdf

    第38卷第7期 计算机科学 V01.38NO.7 Science 2011 2011年7月 Computer July 一个基于Eclipse的通用Java程序插桩工具 郑晓梅 (南京中医药大学信息技 ...

  4. linux静态插桩工具PEBIL

    文章目录 引言 论文学习 摘要及简介 设计与实现 插桩代码效率 实验结果及其他 具体使用 引言 PEBIL是San Diego Supercomputer Center某实验室研发的工具,用来对ELF ...

  5. 网页算法动态插桩工具(Js逆向)

    网页算法动态插桩工具(Js逆向) 一. 功能 通过 anyproxy工具拦截浏览器的请求 并通过AST 获取相关加密字段的函数 进行插桩 输出相关的内容 目前 测试 通过 的算法 有 AES DES ...

  6. 软件工程师必备的技能 | 单元测试

    关注.星标公众号,直达精彩内容 来源:保罗的酒吧 前言 测试是软件开发过程中一个必须的环节,测试确保软件的质量符合预期. 对于工程师自己来说,单元测试也是提升自信心的一种方式. 直接交付没有经过测试的 ...

  7. android 插桩工具,Android Asm 插桩 教学项目

    AndroidAutoTrack 本项目主要就是给大家一个参考学习的demo而已,主要是打算简化学习gradle插件的成本,以及对于android transform的一次抽象,将增量更新等等进行一次 ...

  8. intel 插桩工具 pin 介绍

    目录 Pin 介绍

  9. 吹爆系列:Android 插桩之美,全面掌握~

    作者:阿明的小蝴蝶 插桩 插桩是什么?你在开发中有用过插桩的技术吗? 所谓的插桩就是在代码编译期间修改已有的代码或者生成新代码. 插桩具体在编译的哪个流程介入呢? 插桩的作用与场景 代码生成 代码监控 ...

最新文章

  1. 华为:憧憬6G,共同定义6G
  2. 汇编环境搭建(vs2010(2012)+masm32)
  3. grep+awk+sort+wc实战
  4. Java培训教程之JDBC URL结构分析
  5. python多进程和多线程使用场景_Python36 多线程、多进程的使用场景
  6. 《Linux内核分析》第三周笔记 构造一个简单的Linux系统MenuOS
  7. SpringBoot学习(一)初识SpringBoot、第一个SpringBoot程序
  8. 超级精简版/超精简/懂你版IDM 6.25 build 23推荐
  9. 引擎提示Alias HeroDB跟游戏引擎启动异常怎么解决?
  10. XP系统时间同步和开启WindowsTime服务
  11. vue下拉框数据填充
  12. 城里人看呆!没想到现在景区都这么会玩了
  13. 在网页项目中集成扫码枪设备,实现二维码扫码识别实战
  14. 备份恢复Lesson 08. Using RMAN-Encrypted Backups
  15. MEMOS 技术支持
  16. CANopen DS402 驱动电机运动控制模式
  17. 【销售】如何做好网络安全产品的销售
  18. word2vec的cbow
  19. 反向放大器的缺点-运算放大器-运放
  20. actionscript3.0视频教程合集

热门文章

  1. HOW-TO:具有MySQL的JEE应用程序中具有集群功能的Quartz Scheduler
  2. 定制基元和DTO的(反)序列化和验证
  3. html嵌入war_WAR文件与具有嵌入式服务器的Java应用程序
  4. 在Java错误产生之前对其进行处理的新方法
  5. Cactoos中的面向对象的声明式输入/输出
  6. java线程死锁_Java并发:隐藏线程死锁
  7. spring dao层注解_Spring– DAO和服务层
  8. 使用Hystrix DSL创建弹性骆驼应用程序
  9. Java EE 7发布–反馈和新闻报道
  10. 在MySQL数据库上使用Quartz Scheduler入门