<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          效能優(yōu)化實踐 | C/C++單元測試萬能插樁工具

          共 4086字,需瀏覽 9分鐘

           ·

          2021-09-28 07:03

          來源 | 騰訊技術(shù)工程

          研發(fā)效能是一個涉及面很廣的話題,它涵蓋了軟件交付的整個生命周期,涉及產(chǎn)品、架構(gòu)、開發(fā)、測試、運維,每個環(huán)節(jié)都可能影響順暢、高質(zhì)量地持續(xù)有效交付。在騰訊安全平臺部實際研發(fā)與測試工作中我們發(fā)現(xiàn),代碼插樁隔離是單元測試工作中的一個強需求,然而業(yè)界現(xiàn)有 C/C++插樁工具由于使用上的局限性,運行效率和體驗仍有很大改善空間。本文介紹了團隊基于研效優(yōu)化實踐而自研的動態(tài)插樁工具,旨在實現(xiàn)單元測試的輕量化運行,提高代碼覆蓋率,從而助力研發(fā)團隊的效能提升。

          問題&思路

          目前存在的 C/C++插樁工具,基本上都有各種使用上的局限,比如流行的 gmock,只能對 C++的虛函數(shù)進行插樁替換,針對非虛函數(shù),則需要先對被測代碼進行改造;同時對于系統(tǒng)接口,C 風(fēng)格的第三方庫代碼,也無能為力。

          如果可以繞開編譯器,直接從底層入手,比如做機器指令修改,則可以不受語法及編譯器的束縛,直接達到目的,這樣在使用中就 幾乎不受限制

          原理

          C/C++語言編譯后的可執(zhí)行體,其實就是一個個的函數(shù)實現(xiàn),每個函數(shù)的開頭就是它的入口。一個函數(shù) A 調(diào)用另一個函數(shù) B,就是代碼在執(zhí)行過程中,控制流從函數(shù) A 的某處跳到了函數(shù) B 的開頭,所以如果想用一個新的函數(shù) C 取代函數(shù) B,可以在函數(shù) B 的開頭用機器碼的形式寫入如下等價邏輯:

          MOVQ ADDRESS_OF_C %RAX //將函數(shù)C的地址放到寄存器RAX
          JMPQ *RAX           //無條件跳轉(zhuǎn)到RAX所指向的位置

          這樣,當(dāng)控制流從函數(shù) A 進入函數(shù) B 的開始位置的時候,即會執(zhí)行上述代碼,從而直接跳轉(zhuǎn)到 C 的開頭處。其最終效果,是所有對函數(shù) B 的調(diào)用,都如同直接調(diào)用了函數(shù) C。

          基于上述原理,被插樁的代碼包括第三方庫,如 MySql、其他同事未完成的模塊、甚至是操作系統(tǒng)的 API 接口,如 read、select 等

          同時,樁函數(shù)不僅可以模擬原函數(shù)的返回值,實際上它作為一個普通的 C 函數(shù),對原函數(shù)有完全的操作能力,比如可以訪問傳遞給原函數(shù)調(diào)用真實的參數(shù)、C++成員變量(針對對成員函數(shù)的模擬),給定任意的返回值,訪問全局變量、對調(diào)用進行計數(shù)等

          實際實現(xiàn)中,考慮到不同測試用例間的互不干擾,除了能執(zhí)行函數(shù)替換,還需要在執(zhí)行完一個測試時還原現(xiàn)場。這些具體細節(jié)可以直接參考代碼。

          使用

          對全局函數(shù)插樁

          原始函數(shù):

          int global(int a, int b) {
              return a + b;
          }

          對應(yīng)的樁函數(shù):

          int fake_global(int a, int b) {
              //校驗參數(shù)正確性,確定被測代碼傳入了正確的值
              assert(a == 3);
              assert(b == 2);
              //給一個返回值,配合被測代碼走特定分支
              return a - b;
          }

          插樁示例:


          assert(global(3, 2) == 5);

          //通過mock調(diào)用,完成函數(shù)動態(tài)替換
          assert(0 == mock(&global, &fake_global));

          //調(diào)用mock后的函數(shù),可以看到返回值變了
          assert(global(3, 2) == 1);

          //結(jié)束mock
          reset();

          //函數(shù)行為恢復(fù)
          assert(global(3, 2) == 5);

          對普通成員函數(shù)插樁

          被測代碼:

          class A {
          public:
              int member(int a) {return ++a;}
              static int static_member(int a) {return 200;}
              virtual int virtual_member() {return 400;}
          };

          樁函數(shù):

          int fake_member(A *pTihs, int a) {
            //由于是對成員函數(shù)插樁,這里需要這個this指針參數(shù)
              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);

          對靜態(tài)成員函數(shù)插樁

          樁函數(shù):

          int fake_static_member() {
            //靜態(tài)函數(shù)不需要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);

          對虛函數(shù)插樁

          樁函數(shù):

          int fake_virtual_member(A *pThis) {
              //虛函數(shù)同普通的成員函數(shù)由于,同樣需要this指針
              return 500;
          }

          插樁示例:

          A a;
          assert(a.virtual_member() == 400);

          //虛函數(shù)mock需要多傳一個相關(guān)類的對象,任意一個對象即可,跟實際代碼中的對象沒有關(guān)系
          A a_obj;
          mock(&A::virtual_member, fake_virtual_member, &a_obj);
          assert(a.virtual_member() == 500);

          reset();
          assert(a.virtual_member() == 400);

          對系統(tǒng)及第三方庫函數(shù)插樁

          樁函數(shù):

          int fake_write(int, char*, int) {
              return 100;
          }

          插樁示例:

          //直接寫入一個無效的文件描述符,會失敗
          assert(write(5, "hello", 5) == -1);

          //來一個假的wirte
          mock(write, fake_write);
          //模擬調(diào)用成功
          assert(write(5, "hello", 5) == 100);

          reset();

          assert(write(5, "hello", 5) == -1);

          可以看到,對系統(tǒng)函數(shù)的 mock,其實跟普通的全局函數(shù)并無兩樣,第三方庫函數(shù)也是同理。

          使用限制&注意事項

          • 目前支持 X86_64 平臺上的 Linux、MacOS 系統(tǒng),如有需求,Windows 和其它硬件平臺,如 X86_32、ARM,也可在短期內(nèi)支持。
          • MacOS 下,需要在執(zhí)行前對單測可執(zhí)行文件做以下修改:
          printf '\x07' | dd of=<ut_executable> bs=1 seek=160 count=1 conv=notrunc
          • 顯然,這種方法對內(nèi)聯(lián)函數(shù)無效,不過對于單元測試來說,可以關(guān)閉內(nèi)聯(lián),同時也建議關(guān)閉其它編譯器優(yōu)化。
          • 可以使用-fno-access-control 編譯你的測試代碼,可以使 g++關(guān)閉 c++成員的訪問控制(即 protected 及 private 不再生效)。

          項目地址

          https://github.com/wangyongfeng5/lmock

          結(jié)語

          持續(xù)改進是研效工具平臺發(fā)展的必經(jīng)之路,歡迎感興趣的同學(xué)與我們交流探討,共同助力測試效能的優(yōu)化。

          瀏覽 32
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久久久久久久久久精 | 中文字幕黄色 | 色哟哟精品一区二区 | 大黑屌操逼网 | A片黄色电影 |