1、編譯隱藏的過程
假設(shè)有一個源文件:
#include <stdio.h>
#pragma pack(2)
#define N 666
#define PR_D(x) printf(#x" = %d\n", x )
#define CONNECT(a,b) (a##b)
#ifdef N
#define VERSION "2.0"
#else
#define VERSION "0.1"
#endif
int ijk;
int main(void)
{
printf("(%s,%s,%s,%d)\n", __TIME__, __FILE__, __FUNCTION__, __LINE__); /* print __LINE__ */
printf("%s\n", VERSION); // print version
PR_D(ijk);
printf("%d\n", CONNECT(i,jk) );
return 0;
}
通常用 gcc
完整的編譯命令是 gcc hello.c -o hello
上面的完整編譯可以分解為 預(yù)處理策菜、編譯酒贬、匯編、鏈接锭吨。
預(yù)處理可以用命令 cpp hello.c > hello.i
或者 gcc -E hello.c -o hello.i
零如,
$ gcc -E hello.c
...
# 2 "hello.c" 2
#pragma pack(2)
# 14 "hello.c"
# 14 "hello.c"
int ijk;
int main(void)
{
printf("(%s,%s,%s,%d)\n", "07:03:49", "hello.c", __FUNCTION__, 18);
printf("%s\n", "2.0");
printf("ijk"" = %d\n", ijk );
printf("%d\n", (ijk) );
return 0;
}
可以看到:
#include
被展開了,頭文件內(nèi)容太多不放在這里了祸憋。
#define
被展開了夺衍,除了__FUNCTION__
喜命,其他都替換了。
#if
和 #ifdef
等條件編譯被展開了壁榕,VERSION 已經(jīng)替換成 "2.0" 了牌里。
注釋已經(jīng)被刪除了务甥。
#pragma
會保留喳篇,在后面編譯階段處理。
添加了行號麸澜、文件名等標識,以便下一個過程編譯可以產(chǎn)生調(diào)試用的行號编矾,以及如果編譯報錯時可以打印行號馁害。
如果代碼里面有太多條件編譯,不知道哪個定義了凹蜈,哪個沒定義時炉媒,可以查看 gcc -E
的信息,或者直接使用#pragma message
:
#ifdef N
#pragma message("N defined!")
#else
#pragma message("N undeclared静尼!")
#endif
編譯階段的命令是 gcc -S hello.i -o hello.s
匯編段的命令是 as hello.s -o hello.o
或者 gcc -c hello.s -o hello.o
查看 .c 代碼的匯編代碼可以用 gcc -S -g -o hello.s hello.c
查看 .o文件的匯編代碼可以用 objdump -S hello.o > hello.s
2鼠渺、靜態(tài)鏈接
如果一些函數(shù)很通用,其他項目也要用拦盹,可以抽出來,單獨做成庫恬口,這樣就不用寫重復(fù)代碼了沼侣。
生成靜態(tài)庫:
gcc -c xxx.c -o xxx.o
ar -rcs libxxx.a xxx.o
鏈接:
gcc main.o -L . -lxxx -o s.out
可以查看靜態(tài)庫包文件中含了哪些文件:
$ ar -t libxxx.a
xxx.o
或
ar tv /usr/lib/gcc/x86_64-linux-gnu/8/libgcc.a
ar -x
可以從 *.a 解壓出 *.o文件:
ar -x libxxx.a
靜態(tài)鏈接的缺點:
一是浪費空間,每個可執(zhí)行程序中都有一份所有需要的目標文件的副本养铸;
二是更新比較麻煩,每當(dāng)庫函數(shù)的代碼修改了兔甘,不僅要重新編譯靜態(tài)庫鳞滨,還需要重新鏈接所有用到該庫的項目,以形成新的可執(zhí)行程序太援。
優(yōu)點就是執(zhí)行的時候速度快一些。
3仙蛉、動態(tài)鏈接
可執(zhí)行文件較小碱蒙,在程序運行時才將它們鏈接在一起形成一個完整的程序,不會在內(nèi)存中存在多份副本哀墓。
生成動態(tài)庫的命令:
gcc -c xxx.c -o xxx.o
gcc -fPIC -shared xxx.o -o libxxx.so
鏈接的命令和靜態(tài)是一樣的喷兼,不過動態(tài)鏈接后,運行時可能會報錯
error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory
因為找不到該動態(tài)庫吠各,需要把庫文件所在目錄添加到環(huán)境變量:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
查詢一個可執(zhí)行文件依賴哪些動態(tài)庫:
$ ldd a.out
linux-vdso.so.1 (0x00007ffd04588000)
libxxx.so => ./libxxx.so (0x00007f3989050000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3988c5f000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3989454000)
查看某個函數(shù)是否在某個庫文件中
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 |grep xxx_1
$ nm -D libxxx.so |grep xxx_1
000000000000065a T xxx_1
除了 nm
命令勉抓,還可以使用 readelf
、 objdump
或 strings
命令纵散,這些命令都屬于 GNU binutils 工具集隐圾。:
$ readelf -a libxxx.so |grep xxx_1
8: 000000000000065a 31 FUNC GLOBAL DEFAULT 12 xxx_1
55: 000000000000065a 31 FUNC GLOBAL DEFAULT 12 xxx_1
$ objdump -tT libxxx.so |grep xxx_1
000000000000065a g F .text 000000000000001f xxx_1
000000000000065a g DF .text 000000000000001f Base xxx_1
$ strings libxxx.so |grep xxx_1
xxx_1
xxx_1
xxx_1
比如我們在嵌入式編程,改了一個函數(shù)硕盹,想看看效果叨咖,又不想完整編譯啊胶、重新升級垛贤,這時候替換板子上的 .so 文件會比較快,這時候就需要確認一下這個函數(shù)屬于哪一個庫文件某饰。
在 /usr/lib/
善绎、 /usr/local/lib/
或 /lib/
目錄有很多 *.a 和 *.so 文件,可以用這些命令來測試玩一玩炬守。