<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>

          Linux中對(duì)【庫(kù)函數(shù)】的調(diào)用進(jìn)行跟蹤的 3 種【插樁】技巧

          共 5343字,需瀏覽 11分鐘

           ·

          2022-01-13 13:40

          作 ?者:道哥,10+年嵌入式開(kāi)發(fā)老兵,專注于:C/C++、嵌入式、Linux

          關(guān)注下方公眾號(hào),回復(fù)【書籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。

          目錄

          • 什么是插樁?

          • 插樁示例代碼分析

          • 在編譯階段插樁

          • 鏈接階段插樁

          • 執(zhí)行階段插樁

          別人的經(jīng)驗(yàn),我們的階梯!

          什么是插樁?

          在稍微具有一點(diǎn)規(guī)模的代碼中(C 語(yǔ)言),調(diào)用第三方動(dòng)態(tài)庫(kù)中的函數(shù)來(lái)完成一些功能,是很常見(jiàn)的工作場(chǎng)景。

          假設(shè)現(xiàn)在有一項(xiàng)任務(wù):需要在調(diào)用某個(gè)動(dòng)態(tài)庫(kù)中的某個(gè)函數(shù)的之前和之后,做一些額外的處理工作。

          這樣的需求一般稱作:插樁,也就是對(duì)于一個(gè)指定的目標(biāo)函數(shù),新建一個(gè)包裝函數(shù),來(lái)完成一些額外的功能。

          在包裝函數(shù)中去調(diào)用真正的目標(biāo)函數(shù),但是在調(diào)用之前或者之后,可以做一些額外的事情。

          比如:統(tǒng)計(jì)函數(shù)的調(diào)用次數(shù)、驗(yàn)證函數(shù)的輸入?yún)?shù)是否合法等等。

          關(guān)于程序插樁的官方定義,可以看一下【百度百科】中的描述:

          1. 程序插樁,最早是由J.C. Huang 教授提出的。

          2. 它是在保證被測(cè)程序原有邏輯完整性的基礎(chǔ)上在程序中插入一些探針(又稱為“探測(cè)儀”,本質(zhì)上就是進(jìn)行信息采集的代碼段,可以是賦值語(yǔ)句或采集覆蓋信息的函數(shù)調(diào)用)。

          3. 通過(guò)探針的執(zhí)行并拋出程序運(yùn)行的特征數(shù)據(jù),通過(guò)對(duì)這些數(shù)據(jù)的分析,可以獲得程序的控制流和數(shù)據(jù)流信息,進(jìn)而得到邏輯覆蓋等動(dòng)態(tài)信息,從而實(shí)現(xiàn)測(cè)試目的的方法。

          4. 根據(jù)探針插入的時(shí)間可以分為目標(biāo)代碼插樁和源代碼插樁。

          這篇文章,我們就一起討論一下:在 Linux 環(huán)境下的 C 語(yǔ)言開(kāi)發(fā)中,可以通過(guò)哪些方法來(lái)實(shí)現(xiàn)插樁功能。

          插樁示例代碼分析

          示例代碼很簡(jiǎn)單:

          ├── app.c
          └── lib
          ├── rd3.h
          └── librd3.so

          假設(shè)動(dòng)態(tài)庫(kù)librd3.so是由第三方提供的,里面有一個(gè)函數(shù):int rd3_func(int, int);

          // lib/rd3.h

          #ifndef _RD3_H_
          #define _RD3_H_
          extern int rd3_func(int, int);
          #endif

          在應(yīng)用程序app.c中,調(diào)用了動(dòng)態(tài)庫(kù)中的這個(gè)函數(shù):

          app.c代碼如下:

          #include 
          #include
          #include "rd3.h"

          int main(int argc, char *argv[])
          {
          int result = rd3_func(1, 1);
          printf("result = %d \n", result);
          return 0;
          }

          編譯:

          $ gcc -o app app.c -I./lib -L./lib -lrd3 -Wl,--rpath=./lib
          1. -L./lib: 指定編譯時(shí),在 lib 目錄下搜尋庫(kù)文件。

          2. -Wl,--rpath=./lib: 指定執(zhí)行時(shí),在 lib 目錄下搜尋庫(kù)文件。

          生成可執(zhí)行程序:app,執(zhí)行:

          $ ./app
          result = 3

          示例代碼足夠簡(jiǎn)單了,稱得上是helloworld的兄弟版本!

          在編譯階段插樁

          對(duì)函數(shù)進(jìn)行插樁,基本要求是:不應(yīng)該對(duì)原來(lái)的文件(app.c)進(jìn)行額外的修改。

          由于app.c文件中,已經(jīng)include "rd3.h"了,并且調(diào)用了其中的rd3_func(int, int)函數(shù)。

          所以我們需要新建一個(gè)假的 "rd3.h" 提供給app.c,并且要把函數(shù)rd3_func(int, int)"重導(dǎo)向"到一個(gè)包裝函數(shù),然后在包裝函數(shù)中去調(diào)用真正的目標(biāo)函數(shù),如下圖所示:

          "重導(dǎo)向"函數(shù):可以使用宏來(lái)實(shí)現(xiàn)。

          包裝函數(shù):新建一個(gè)C文件,在這個(gè)文件中,需要 #include "lib/rd3.h",然后調(diào)用真正的目標(biāo)文件。

          完整的文件結(jié)構(gòu)如下:

          ├── app.c
          ├── lib
          │?? ├── librd3.so
          │?? └── rd3.h
          ├── rd3.h
          └── rd3_wrap.c

          最后兩個(gè)文件是新建的:rd3.h, rd3_wrap.c,它們的內(nèi)容如下:

          // rd3.h

          #ifndef _LIB_WRAP_H_
          #define _LIB_WRAP_H_

          // 函數(shù)“重導(dǎo)向”,這樣的話 app.c 中才能調(diào)用 wrap_rd3_func
          #define rd3_func(a, b) wrap_rd3_func(a, b)

          // 函數(shù)聲明
          extern int wrap_rd3_func(int, int);

          #endif
          // rd3_wrap.c

          #include
          #include

          // 真正的目標(biāo)函數(shù)
          #include "lib/rd3.h"

          // 包裝函數(shù),被 app.c 調(diào)用
          int wrap_rd3_func(int a, int b)
          {
          // 在調(diào)用目標(biāo)函數(shù)之前,做一些處理
          printf("before call rd3_func. do something... \n");

          // 調(diào)用目標(biāo)函數(shù)
          int c = rd3_func(a, b);

          // 在調(diào)用目標(biāo)函數(shù)之后,做一些處理
          printf("after call rd3_func. do something... \n");

          return c;
          }

          app.c 和 rd3_wrap.c一起編譯:

          $ gcc -I./ -L./lib -Wl,--rpath=./lib -o app app.c rd3_wrap.c -lrd3

          頭文件的搜索路徑不能錯(cuò):必須在當(dāng)前目錄下搜索rd3.h,這樣的話,app.c中的#include "rd3.h" 找到的才是我們新增的那個(gè)頭文件 rd3.h。

          所以在編譯指令中,第一個(gè)選項(xiàng)就是 -I./,表示在當(dāng)前目錄下搜尋頭文件。

          另外,由于在rd3_wrap.c文件中,使用#include "lib/rd3.h"來(lái)包含庫(kù)中的頭文件,因此在編譯指令中,就不需要指定到lib 目錄下去查找頭文件了。

          編譯得到可執(zhí)行程序app,執(zhí)行一下:

          $ ./app 
          before call rd3_func. do something...
          after call rd3_func. do something...
          result = 3

          完美!

          鏈接階段插樁

          Linux 系統(tǒng)中的鏈接器功能是非常強(qiáng)大的,它提供了一個(gè)選項(xiàng):--wrap f,可以在鏈接階段進(jìn)行插樁

          這個(gè)選項(xiàng)的作用是:告訴鏈接器,遇到f符號(hào)時(shí)解析成__wrap_f,在遇到__real_f符號(hào)時(shí)解析成f,正好是一對(duì)!

          我們就可以利用這個(gè)屬性,新建一個(gè)文件rd3_wrap.c,并且定義一個(gè)函數(shù)__wrap_rd3_func(int, int),在這個(gè)函數(shù)中去調(diào)用__real_rd3_func函數(shù)。

          只要在編譯選項(xiàng)中加上-Wl,--wrap,rd3_func, 編譯器就會(huì):

          1. 把 app.c 中的 rd3_func 符號(hào),解析成 __wrap_rd3_func,從而調(diào)用包裝函數(shù);

          2. 把 rd3_wrap.c 中的 __real_rd3_func 符號(hào),解析成 rd3_func,從而調(diào)用真正的函數(shù)。

          這幾個(gè)符號(hào)的轉(zhuǎn)換,是由鏈接器自動(dòng)完成的!

          按照這個(gè)思路,一起來(lái)測(cè)試一下。

          文件目錄結(jié)構(gòu)如下:

          .
          ├── app.c
          ├── lib
          │?? ├── librd3.so
          │?? └── rd3.h
          ├── rd3_wrap.c
          └── rd3_wrap.h

          rd3_wrap.h是被app.c引用的,內(nèi)容如下:

          #ifndef _RD3_WRAP_H_
          #define _RD3_WRAP_H_
          extern int __wrap_rd3_func(int, int);
          #endif

          rd3_wrap.c的內(nèi)容如下:

          #include 
          #include

          #include "rd3_wrap.h"

          // 這里不能直接飲用 lib/rd3.h 中的函數(shù)了,而要由鏈接器來(lái)完成解析。
          extern int __real_rd3_func(int, int);

          // 包裝函數(shù)
          int __wrap_rd3_func(int a, int b)
          {
          // 在調(diào)用目標(biāo)函數(shù)之前,做一些處理
          printf("before call rd3_func. do something... \n");

          // 調(diào)用目標(biāo)函數(shù),鏈接器會(huì)解析成 rd3_func。
          int c = __real_rd3_func(a, b);

          // 在調(diào)用目標(biāo)函數(shù)之后,做一些處理
          printf("after call rd3_func. do something... \n");

          return c;
          }

          rd3_wrap.c中,不能直接去 include "rd3.h",因?yàn)?code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">lib/rd3.h中的函數(shù)聲明是int rd3_func(int, int);,沒(méi)有__real前綴。

          編譯一下:

          $ gcc -I./lib -L./lib -Wl,--rpath=./lib -Wl,--wrap,rd3_func -o app app.c rd3_wrap.c -lrd3

          注意:這里的頭文件搜索路徑仍然設(shè)置為-I./lib,是因?yàn)?span style="color:LightSeaGreen;">app.cinclude了這個(gè)頭文件。

          得到可執(zhí)行程序app,執(zhí)行:

          $ ./app
          before call rd3_func. do something...
          before call rd3_func. do something...
          result = 3

          完美!

          執(zhí)行階段插樁

          編譯階段插樁,新建的文件rd3_wrap.c是與app.c一起編譯的,其中的包裝函數(shù)名是wrap_rd3_func。

          app.c中通過(guò)一個(gè)宏定義實(shí)現(xiàn)函數(shù)的"重導(dǎo)向"rd3_func --> wrap_rd3_func

          我們還可以直接"霸王硬上弓":在新建的文件rd3_wrap.c中,直接定義rd3_func函數(shù)。

          然后在這個(gè)函數(shù)中通過(guò)dlopen, dlsym系列函數(shù)來(lái)動(dòng)態(tài)的打開(kāi)真正的動(dòng)態(tài)庫(kù),查找其中的目標(biāo)文件,然后調(diào)用真正的目標(biāo)函數(shù)。

          當(dāng)然了,這樣的話在編譯app.c時(shí),就不能連接lib/librd3.so文件了。

          按照這個(gè)思路繼續(xù)實(shí)踐!

          文件目錄結(jié)構(gòu)如下:

          ├── app.c
          ├── lib
          │?? ├── librd3.so
          │?? └── rd3.h
          └── rd3_wrap.c

          rd3_wrap.c文件的內(nèi)容如下(一些錯(cuò)誤檢查就暫時(shí)忽略了):

          #include 
          #include
          #include

          // 庫(kù)的頭文件
          #include "rd3.h"

          // 與目標(biāo)函數(shù)簽名一致的函數(shù)類型
          typedef int (*pFunc)(int, int);

          int rd3_func(int a, int b)
          {
          printf("before call rd3_func. do something... \n");

          //打開(kāi)動(dòng)態(tài)鏈接庫(kù)
          void *handle = dlopen("./lib/librd3.so", RTLD_NOW);

          // 查找?guī)熘械哪繕?biāo)函數(shù)
          pFunc pf = dlsym(handle, "rd3_func");

          // 調(diào)用目標(biāo)函數(shù)
          int c = pf(a, b);

          // 關(guān)閉動(dòng)態(tài)庫(kù)句柄
          dlclose(handle);

          printf("after call rd3_func. do something... \n");
          return c;
          }

          編譯包裝的動(dòng)態(tài)庫(kù)

          $ gcc -shared -fPIC -I./lib -o librd3_wrap.so rd3_wrap.c

          得到包裝的動(dòng)態(tài)庫(kù): librd3_wrap.so

          編譯可執(zhí)行程序,需要鏈接包裝庫(kù) librd3_wrap.so

          $ gcc -I./lib -L./ -o app app.c -lrd3_wrap -ldl

          得到可執(zhí)行程序app,執(zhí)行:

          $ ./app 
          before call rd3_func. do something...
          after call rd3_func. do something...
          result = 3

          完美!


          ------ End ------

          文中的測(cè)試代碼,已經(jīng)放在網(wǎng)盤了。

          在公眾號(hào)【IOT物聯(lián)網(wǎng)小鎮(zhèn)】后臺(tái)回復(fù)關(guān)鍵字:220109,即可獲取下載地址。

          原創(chuàng)不易,請(qǐng)支持一下道哥,把文章分享給更多的嵌入式小伙伴,謝謝!


          推薦閱讀

          【1】《Linux 從頭學(xué)》系列文章

          【2】C語(yǔ)言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹

          【3】原來(lái)gdb的底層調(diào)試原理這么簡(jiǎn)單

          【4】?jī)?nèi)聯(lián)匯編很可怕嗎?看完這篇文章,終結(jié)它!

          其他系列專輯:精選文章應(yīng)用程序設(shè)計(jì)、物聯(lián)網(wǎng)、 C語(yǔ)言。

          星標(biāo)公眾號(hào),第一時(shí)間看文章!


          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  久久99精品久久久久久水蜜桃 | 黄片免费下载 | 成人伊人AV | 91视频最新网址 | 欧美后门菊门交3p视频 |