gcc/g++使用自定義的同名函數(shù)覆蓋C庫函數(shù)

前言

其實(shí)這問題以前就想過,每次都沒有深究到底沙热。原因在于無論是哪本Linux C編程的書侣滩,基本都會(huì)使用可靠語義的signal函數(shù)來覆蓋相應(yīng)的庫函數(shù)。
比如在《Unix網(wǎng)絡(luò)編程》中是如下定義的:對(duì)被SIGALRM以外的信號(hào)中斷的系統(tǒng)調(diào)用自動(dòng)重啟涡真,并且不阻塞其他的信號(hào)分俯。(雖然信號(hào)掩碼是空,但是POSIX保證被捕獲的信號(hào)在其信號(hào)處理函數(shù)運(yùn)行期間總是阻塞的)
但是書中并未提及具體怎么覆蓋庫函數(shù)的定義哆料, 畢竟對(duì)于不同的編譯器來說做法不同缸剪,這里僅針對(duì)gcc而言。

靜態(tài)鏈接VS動(dòng)態(tài)鏈接

注:想直接看結(jié)論可以忽略本部分的內(nèi)容剧劝。
簡(jiǎn)單來說橄登,鏈接即把可重定位目標(biāo)文件組合成最終的可執(zhí)行目標(biāo)文件(下文均以“程序”一詞代替)。而可重定向目標(biāo)文件中有一個(gè)符號(hào)表讥此,其中有一些未被解析的符號(hào)引用拢锹,比如源文件中聲明了一個(gè)函數(shù),但未給出其具體定義萄喳。
這時(shí)鏈接器就會(huì)在其他目標(biāo)文件中查找是否有對(duì)應(yīng)的符號(hào)定義卒稳。
比如有下列源文件

// main.c
void foo();
int main() {
    foo();
    return 0;
}

可以看到main.c中只包含foo的聲明,而沒有定義他巨,因此直接編譯main.c會(huì)報(bào)錯(cuò)充坑。如果提供一個(gè)foo.c編譯而成的靜態(tài)庫libfoo.a(編譯過程如下)

// foo.c
#include <stdio.h>
void foo() { puts("foo"); }
$ gcc -c foo.c 
$ ar -rcs libfoo.a foo.o

那么就可以進(jìn)行鏈接了,gcc編譯過程如下

$ gcc main.c libfoo.a

這個(gè)過程中染突,首先編譯源碼main.c得到一個(gè)可重定位目標(biāo)文件捻爷,其中符號(hào)表中包含未解析的符號(hào)引用foo,此時(shí)鏈接器記錄下來份企,然后在后面的可重定位目標(biāo)文件(靜態(tài)庫)中查找是否含有foo的符號(hào)定義也榄,若找到則匹配,之后不再查找定義。
比如現(xiàn)在給出另一個(gè)定義了foo函數(shù)的庫libfoo2.a甜紫,源碼如下降宅,編譯過程同libfoo.a

// foo2.c
#include <stdio.h>
void foo() { puts("foo2"); }

現(xiàn)在分別按照不同的順序進(jìn)行鏈接,運(yùn)行程序囚霸,觀察結(jié)果

$ gcc main.c libfoo.a libfoo2.a 
$ ./a.out 
foo
$ gcc main.c libfoo2.a libfoo.a 
$ ./a.out 
foo2

印證了剛才的結(jié)論腰根,不存在什么后面的覆蓋了前面的行為。
OK拓型,那么問題來了额嘿,stdio.h中只有puts函數(shù)的聲明,卻沒有定義吨述。這就是動(dòng)態(tài)庫了岩睁,可以用ldd命令查看程序調(diào)用的動(dòng)態(tài)庫

$ ldd a.out 
    linux-vdso.so.1 =>  (0x00007fff78b02000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe770f5a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fe771324000)

libc.so.6即C標(biāo)準(zhǔn)庫(動(dòng)態(tài)庫),放在特定目錄下揣云,然后通過gcc的-l選項(xiàng)指定鏈接的動(dòng)態(tài)庫捕儒,符號(hào)定義的具體內(nèi)容不會(huì)放入最終的程序中,而是記錄符號(hào)定義所在動(dòng)態(tài)庫路徑邓夕,在程序運(yùn)行時(shí)進(jìn)行查找刘莹。優(yōu)點(diǎn)是簡(jiǎn)化了程序體積,缺點(diǎn)是第一次調(diào)用動(dòng)態(tài)鏈接的函數(shù)時(shí)會(huì)比較費(fèi)時(shí)焚刚。
鏈接時(shí)点弯,C標(biāo)準(zhǔn)庫不需要額外選項(xiàng)就可以進(jìn)行動(dòng)態(tài)鏈接,只有特地加上-static選項(xiàng)時(shí)才不進(jìn)行動(dòng)態(tài)鏈接矿咕,而是去靜態(tài)鏈接C標(biāo)準(zhǔn)庫的靜態(tài)庫抢肛。

更多細(xì)節(jié)部分可以參考《深入理解計(jì)算機(jī)系統(tǒng)》(即CSAPP)第七章
庫函數(shù)一般是進(jìn)行動(dòng)態(tài)鏈接

如何覆蓋庫函數(shù)

使用gcc選項(xiàng)no-builtin,在gcc的manpage中可以看到相關(guān)說明(這里不貼出來了)碳柱,大致就是gcc對(duì)于某些內(nèi)置函數(shù)會(huì)有底層優(yōu)化捡絮,比自己實(shí)現(xiàn)同樣的功能,能產(chǎn)生體積更小莲镣,速度更快的底層代碼福稳。開啟這個(gè)選項(xiàng),則默認(rèn)不使用系統(tǒng)的優(yōu)化函數(shù)瑞侮,而使用自定義的函數(shù)的圆。
比如我們來自定義printf(只是示例,并不是還原功能)

// printf.c
#include <unistd.h>
#include <string.h>

int printf(const char* format, ...) {
    write(STDOUT_FILENO, "my printf\n", 10);
    write(STDOUT_FILENO, format, strlen(format));
    return 0;
}
// main.c
#include <stdio.h>

int main() {
    printf("hello\n");
    return 0;
}

觀察不同編譯方式下的結(jié)果

$ gcc -c printf.c 
$ gcc main.c printf.o -fno-builtin
$ ./a.out 
my printf
hello
$ gcc main.c printf.o
$ ./a.out 
hello

對(duì)于像signal這樣的未給予優(yōu)化的函數(shù)(畢竟僅僅是系統(tǒng)調(diào)用的包裝)半火,直接靜態(tài)鏈接即可越妈。

// signal.c
#include <stdio.h>
#include <signal.h>  // 假設(shè)signal函數(shù)的定義調(diào)用了sigaction等函數(shù)

typedef void Sigfunc(int);

Sigfunc* signal(int signo, Sigfunc* func) {
    printf("%d\n", signo);
    return func;
}
// main.c
#include <signal.h>

int main() {
    signal(SIGINT, SIG_DFL);
    return 0;
}
$ gcc -c signal.c 
$ gcc main.c signal.o
$ ./a.out 
2

另外,還可以使用宏定義的方式來替換庫函數(shù)钮糖,比如

#define printf my_printf
int my_printf(const char* format, ...)
{
    // 具體實(shí)現(xiàn)
}

但不推薦這種做法梅掠,因?yàn)楹晏鎿Q是在編譯之前進(jìn)行的,最終程序中的符號(hào)信息并不是printf而是my_printf,而且stdio.h中對(duì)printf的聲明也失去了意義瓤檐,因?yàn)閷?shí)際調(diào)用的是my_printf
使用前一種方法娱节,就可以在不需要修改現(xiàn)有代碼的基礎(chǔ)上挠蛉,調(diào)用自己對(duì)庫函數(shù)的重寫版本。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肄满,一起剝皮案震驚了整個(gè)濱河市谴古,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稠歉,老刑警劉巖掰担,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怒炸,居然都是意外死亡带饱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門阅羹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勺疼,“玉大人,你說我怎么就攤上這事捏鱼≈绰” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵导梆,是天一觀的道長(zhǎng)轨淌。 經(jīng)常有香客問我,道長(zhǎng)看尼,這世上最難降的妖魔是什么递鹉? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮狡忙,結(jié)果婚禮上梳虽,老公的妹妹穿的比我還像新娘。我一直安慰自己灾茁,他們只是感情好窜觉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著北专,像睡著了一般禀挫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拓颓,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天语婴,我揣著相機(jī)與錄音,去河邊找鬼。 笑死砰左,一個(gè)胖子當(dāng)著我的面吹牛匿醒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缠导,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼廉羔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了僻造?” 一聲冷哼從身側(cè)響起憋他,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎髓削,沒想到半個(gè)月后竹挡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡立膛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年揪罕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宝泵。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耸序,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲁猩,到底是詐尸還是另有隱情坎怪,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布廓握,位于F島的核電站搅窿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏隙券。R本人自食惡果不足惜男应,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望娱仔。 院中可真熱鬧沐飘,春花似錦、人聲如沸牲迫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盹憎。三九已至筛峭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陪每,已是汗流浹背影晓。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工镰吵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挂签。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓疤祭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饵婆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子画株,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容