摘自《Linux GNU C 程序觀(guān)察》 羅秋明 著, 清華大學(xué)出版社
1. 編譯的各階段
C 程序 從源代碼到可執(zhí)行文件的全過(guò)程包括: 預(yù)處理、編譯、匯編、鏈接等步驟。GCC(GNU Compiler Collection)編譯系統(tǒng)將先后調(diào)用預(yù)處理器 cpp、 編譯器 cc、 匯編器 as 和鏈接器 ld 逐步處理,最終生成可執(zhí)行文件。假設(shè)有 hello.c 源程序,相應(yīng)過(guò)程如下圖所示:

? 圖 1 生成可執(zhí)行文件的編譯步驟
一個(gè)廣義的編譯過(guò)程包括以下幾個(gè)步驟:
(1)預(yù)處理:展開(kāi)頭文件內(nèi)容以及宏定義符號(hào)等。
(2)編譯:狹義的編譯階段,將預(yù)處理后的源代碼轉(zhuǎn)換成匯編代碼。
(3)匯編:將匯編代碼轉(zhuǎn)換成機(jī)器碼(.o 目標(biāo)文件)。此時(shí)的目標(biāo)文件稱(chēng)為可重定位的目標(biāo)文件,我們可以使用 objdump -d hello.o 得到反匯編代碼。查看反匯編代碼可以發(fā)現(xiàn),此時(shí)各節(jié)的代碼都是以 0 地址作為起始,但是當(dāng)它們鏈接到一起時(shí),才會(huì)放置到不重疊的地址空間。
(4)鏈接:將一個(gè)或多個(gè)機(jī)器碼(.o 目標(biāo)文件)生產(chǎn)可執(zhí)行文件。
有時(shí),GCC 也稱(chēng)編譯驅(qū)動(dòng)器,因?yàn)樗鶕?jù)命令行選項(xiàng)而調(diào)用其它工具來(lái)完成所指派的任務(wù)。例如,預(yù)處理實(shí)際上是通過(guò) cpp 工具來(lái)完成的,編譯實(shí)際上是通過(guò) cc 工具來(lái)完成,匯編實(shí)際上通過(guò) as 工具完成的,而鏈接是通過(guò) ld 工具完成的。
2. GCC 的基本用法
前面已經(jīng)嘗試了用 GCC 編譯工具以及 -E、-S、-c 選項(xiàng)進(jìn)行預(yù)處理、編譯生成匯編程序以及編譯生成目標(biāo)代碼的操作,并使用 -o 指定輸出文件名。
下面學(xué)習(xí) GCC 的基本用法。gcc --help 的輸出如下:
[root@localhost liyang]# gcc --help
Usage: gcc [options] file...
Options:
-pass-exit-codes Exit with highest error code from a phase
--help Display this information
--target-help Display target specific command line options
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]
Display specific types of command line options
(Use '-v --help' to display command line options of sub-processes)
--version Display compiler version information
-dumpspecs Display all of the built in spec strings
-dumpversion Display the version of the compiler
-dumpmachine Display the compiler's target processor
-print-search-dirs Display the directories in the compiler's search path
-print-libgcc-file-name Display the name of the compiler's companion library
-print-file-name=<lib> Display the full path to library <lib>
-print-prog-name=<prog> Display the full path to compiler component <prog>
-print-multiarch Display the target's normalized GNU triplet, used as
a component in the library path
-print-multi-directory Display the root directory for versions of libgcc
-print-multi-lib Display the mapping between command line options and
multiple library search directories
-print-multi-os-directory Display the relative path to OS libraries
-print-sysroot Display the target libraries directory
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers
-Wa,<options> Pass comma-separated <options> on to the assembler
-Wp,<options> Pass comma-separated <options> on to the preprocessor
-Wl,<options> Pass comma-separated <options> on to the linker
-Xassembler <arg> Pass <arg> on to the assembler
-Xpreprocessor <arg> Pass <arg> on to the preprocessor
-Xlinker <arg> Pass <arg> on to the linker
-save-temps Do not delete intermediate files
-save-temps=<arg> Do not delete intermediate files
-no-canonical-prefixes Do not canonicalize paths when building relative
prefixes to other gcc components
-pipe Use pipes rather than intermediate files
-time Time the execution of each subprocess
-specs=<file> Override built-in specs with the contents of <file>
-std=<standard> Assume that the input sources are for <standard>
--sysroot=<directory> Use <directory> as the root directory for headers
and libraries
-B <directory> Add <directory> to the compiler's search paths
-v Display the programs invoked by the compiler
-### Like -v but options quoted and commands not executed
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
-pie Create a position independent executable
-shared Create a shared library
-x <language> Specify the language of the following input files
Permissible languages include: c c++ assembler none
'none' means revert to the default behavior of
guessing the language based on the file's extension
Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by gcc. In order to pass
other options on to these processes the -W<letter> options must be used.
For bug reporting instructions, please see:
<http://bugzilla.redhat.com/bugzilla>.
下面學(xué)習(xí)更多的選項(xiàng)和用法。
附: gcc -O1 -O2 -O3 -Os -Ofast -Og的作用
2.1 C 語(yǔ)言標(biāo)準(zhǔn)
默認(rèn)情況下,GCC 編譯程序用的是 C 語(yǔ)言的 GNU “方言” —— GNU 實(shí)現(xiàn)的 C 語(yǔ)言的超集, 稱(chēng)之為 GNU C。 GNU C 集成了 C 語(yǔ)言官方 ANSI/ISO 標(biāo)準(zhǔn)和 GNU 對(duì) C 語(yǔ)言的一些擴(kuò)展,比如內(nèi)嵌函數(shù)和變長(zhǎng)數(shù)組。
- -std:
如果需要控制使用標(biāo)準(zhǔn) C 還是 GNU C,可使用 -std 選項(xiàng):
(1) -std = c99 或 -std=iso9899: 1999 :使用 C99;
(2)-std = c11 或 -std=iso9899: 2011: 使用 C11;
(3)-std = gnu90、-std = gnu99 或 -std = gnu11 : 附帶 GNU 擴(kuò)展的 C 語(yǔ)言便準(zhǔn)可用這三種選項(xiàng)來(lái)指定。如果不指定 C語(yǔ)言版本,那么默認(rèn)使用 GNU C11.
- -ansi/ -pedantic:
-ansi 選項(xiàng)禁止哪些與 ANSI/ISO 標(biāo)準(zhǔn)沖突的 GNU 擴(kuò)展特性。
同時(shí)使用 -ansi 和 -pedantic 會(huì)導(dǎo)致 GCC 拒絕所有的 GNU C 擴(kuò)展,而不單單是那些不兼容與 ANSI/ISO 標(biāo)準(zhǔn)的。這有助于編寫(xiě)遵循 ANSI/ISO 標(biāo)準(zhǔn)的可移植的程序。
2.2 庫(kù)的使用
前人的代碼常以庫(kù)的形式提供,使我們不用重復(fù)造輪子。
2.2.1 庫(kù)的頭文件
使用庫(kù)函數(shù),需要得到該函數(shù)的參數(shù)和返回值的正確類(lèi)型聲明,則必須包含相應(yīng)的頭文件。頭文件的搜索路徑在 2.3.1 節(jié)會(huì)介紹。
2.2.2 靜態(tài)庫(kù)
主要介紹如何使用 GCC 工具創(chuàng)建并使用靜態(tài)庫(kù)。假設(shè)有三個(gè)代碼:一個(gè)是主程序 main-lib.c,另有兩個(gè)輔助函數(shù) ---- 計(jì)算向量加法的 addvec.c 和 計(jì)算向量點(diǎn)乘的 multvec.c 。代碼略。其中 addvec.c 和 multvec.c 的代碼將形成靜態(tài)庫(kù),以供其它程序調(diào)用。所以還需一個(gè)庫(kù)函數(shù)的頭文件,如下 vector.h 所示:
// vector.h
void addvec(int * x, int * y, int * z, int n);
void multvec(int * x, int * y, int * z, int n);
為了將向量加法和點(diǎn)乘的代碼變成庫(kù),首先使用 gcc -Og -c addvec.c multvec.c 生成 addvec.o 和 multvec.o 兩個(gè)目標(biāo)文件,使用 file 命令可以看出它們都是可重定位的目標(biāo)文件。
[root@localhost liyang]# gcc -Og -c addvec.c multvec.c
[root@localhost liyang]# ls *.o
addvec.o multvec.o
[root@localhost liyang]# file multvec.o addvec.o
multvec.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
addvec.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
然后,通過(guò) ar rcs libvector.a addvec.o multvec.o 將 addvec.o 和 multvec.o 兩個(gè)目標(biāo)文件歸檔到 libvector.a 這個(gè)靜態(tài)庫(kù)中(其中參數(shù)中的 c 表示 create, r 表示 replace)。使用 ar -t libvector.a命令可以確認(rèn)其中有兩個(gè)文件 addvec.o 和 multvec.o 。
[root@localhost liyang]# ar rcs libvector.a addvec.o multvec.o
[root@localhost liyang]# ar -t libvector.a
addvec.o
multvec.o
創(chuàng)建了靜態(tài)庫(kù) libvector.a 后,就可嘗試在程序中使用了。執(zhí)行 gcc -Og -c main-lib.c 編譯主程序 main-lib.c 生成 main-lib.o 目標(biāo)文件,然后使用 gcc main-lib.o libvector.a -o main-lib生成可執(zhí)行文件 main-lib 并運(yùn)行, 執(zhí)行輸出結(jié)果是“z = [4 6]”。
[root@localhost liyang]# gcc -Og -c main-lib.c
[root@localhost liyang]# ls *.o
addvec.o main-lib.o multvec.o
[root@localhost liyang]# gcc main-lib.o libvector.a -o main-lib
[root@localhost liyang]# file main-lib
main-lib: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=9aac7ca82154e7f6f6f30cc56e5c8a99e5875f96, not stripped
[root@localhost liyang]# ./main-lib
z = [4 6]
此時(shí)執(zhí)行 objdump -d main-lib查看 main-lib的反編譯結(jié)果, 可見(jiàn) addvec.c 的代碼已進(jìn)入到 000000000040056f 開(kāi)始的地址空間了。使用 objdump -d addvec.o 查看 addvec.o 的反匯編代碼,發(fā)現(xiàn)兩者幾乎一致。而 multvec.o 中的 multvec.c 的代碼則沒(méi)有進(jìn)入到可執(zhí)行文件中。
[root@localhost liyang]# objdump -d main-lib
main-lib: file format elf64-x86-64
..........
000000000040052d <main>:
40052d: 48 83 ec 08 sub $0x8,%rsp
400531: b9 02 00 00 00 mov $0x2,%ecx
400536: ba 48 10 60 00 mov $0x601048,%edx
40053b: be 34 10 60 00 mov $0x601034,%esi
400540: bf 3c 10 60 00 mov $0x60103c,%edi
400545: e8 25 00 00 00 callq 40056f <addvec>
40054a: 8b 15 fc 0a 20 00 mov 0x200afc(%rip),%edx # 60104c <__TMC_END__+0x4>
400550: 8b 35 f2 0a 20 00 mov 0x200af2(%rip),%esi # 601048 <__TMC_END__>
400556: bf 20 06 40 00 mov $0x400620,%edi
40055b: b8 00 00 00 00 mov $0x0,%eax
400560: e8 ab fe ff ff callq 400410 <printf@plt>
400565: b8 00 00 00 00 mov $0x0,%eax
40056a: 48 83 c4 08 add $0x8,%rsp
40056e: c3 retq
000000000040056f <addvec>:
40056f: b8 00 00 00 00 mov $0x0,%eax
400574: eb 12 jmp 400588 <addvec+0x19>
400576: 4c 63 c0 movslq %eax,%r8
400579: 46 8b 0c 86 mov (%rsi,%r8,4),%r9d
40057d: 46 03 0c 87 add (%rdi,%r8,4),%r9d
400581: 46 89 0c 82 mov %r9d,(%rdx,%r8,4)
400585: 83 c0 01 add $0x1,%eax
400588: 39 c8 cmp %ecx,%eax
40058a: 7c ea jl 400576 <addvec+0x7>
40058c: f3 c3 repz retq
40058e: 66 90 xchg %ax,%ax
........
[root@localhost liyang]# objdump -d addvec.o
addvec.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <addvec>:
0: b8 00 00 00 00 mov $0x0,%eax
5: eb 12 jmp 19 <addvec+0x19>
7: 4c 63 c0 movslq %eax,%r8
a: 46 8b 0c 86 mov (%rsi,%r8,4),%r9d
e: 46 03 0c 87 add (%rdi,%r8,4),%r9d
12: 46 89 0c 82 mov %r9d,(%rdx,%r8,4)
16: 83 c0 01 add $0x1,%eax
19: 39 c8 cmp %ecx,%eax
1b: 7c ea jl 7 <addvec+0x7>
1d: f3 c3 repz retq
如果不指出所使用的靜態(tài)庫(kù) libvector.a 而直接編譯 main-lib.c ,將提示 addvec 函數(shù)無(wú)定義。
[root@localhost liyang]# gcc main-lib.o -o main-lib
main-lib.o: In function `main':
main-lib.c:(.text+0x19): undefined reference to `addvec'
collect2: error: ld returned 1 exit status
2.2.3 動(dòng)態(tài)庫(kù)
使用靜態(tài)庫(kù)時(shí),所有被用到的目標(biāo)文件中的全部函數(shù)代碼都進(jìn)入到可執(zhí)行程序中。很多程序都共享的庫(kù)函數(shù),如果拷貝到各進(jìn)程,無(wú)疑會(huì)造成大量空間浪費(fèi)。動(dòng)態(tài)鏈接庫(kù)(又稱(chēng)動(dòng)態(tài)庫(kù)、共享庫(kù)),在啟動(dòng)或運(yùn)行時(shí)用到的動(dòng)態(tài)庫(kù)才映射到進(jìn)程空間,而且和其他進(jìn)程共享動(dòng)態(tài)鏈接庫(kù)的代碼。
動(dòng)態(tài)鏈接庫(kù)后面章節(jié)會(huì)專(zhuān)門(mén)討論。
使用gcc -shared -fPIC -o libvector.so addvec.c multvec.c命令將兩個(gè)源代碼編譯成動(dòng)態(tài)庫(kù)libvector.so。 其中參數(shù)-fPIC 就是指明生成位置無(wú)關(guān)代碼,這是動(dòng)態(tài)庫(kù)的必要屬性。-shared指明生成動(dòng)態(tài)庫(kù)。
[root@localhost liyang]# gcc -shared -fPIC -o libvector.so addvec.c multvec.c
[root@localhost liyang]# file libvector.so
libvector.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=b7418d2b111fd671186c64a452, not stripped
緊接著,可與靜態(tài)庫(kù)一樣,在 main-lib.c程序中引用動(dòng)態(tài)庫(kù),編譯時(shí)使用gcc -o main-lib-shared main-lib.c libvector.so命令,生成引用動(dòng)態(tài)庫(kù)的main-lib-shared可執(zhí)行文件。但是運(yùn)行時(shí),結(jié)果卻顯示無(wú)法找到對(duì)應(yīng)的libvector.so動(dòng)態(tài)庫(kù):
[root@localhost liyang]# gcc -o main-lib-shared main-lib.c libvector.so
[root@localhost liyang]# file main-lib-shared
main-lib-shared: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f22103b363b19e7bc4769c614d476355a70b4316, not stripped
[root@localhost liyang]# ./main-lib-shared
./main-lib-shared: error while loading shared libraries: libvector.so: cannot open shared object file: No such file or directory
這就涉及到 Linux 運(yùn)行環(huán)境中關(guān)于庫(kù)的搜索路徑的問(wèn)題。這在 2.3 節(jié) 搜索路徑會(huì)討論。
最后使用 ldd工具可以查看可執(zhí)行文件引用了哪些動(dòng)態(tài)庫(kù)??梢钥吹狡渲惺褂昧?libvector.so、標(biāo)準(zhǔn)C語(yǔ)言庫(kù) libc.so.6以及關(guān)于動(dòng)態(tài)鏈接的輔助庫(kù) /lib64/ld-linux-x86-64.so.2,還有系統(tǒng)調(diào)用相關(guān)的庫(kù) linux-vdso.so.1。
[root@localhost liyang]# ldd main-lib-shared
linux-vdso.so.1 => (0x00007ffd44f6b000)
libvector.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f699c0d1000)
/lib64/ld-linux-x86-64.so.2 (0x000055f687c65000)
2.3 搜索路徑
搜索路徑包括頭文件的搜索路徑(又稱(chēng)為 include 路徑)、庫(kù)的搜索路徑(又簡(jiǎn)稱(chēng)為搜索路徑或鏈接路徑)。
2.3.1 頭文件搜索路徑
GCC 默認(rèn)對(duì) #include <XXX.h>的頭文件按照如下先后順序在下面兩個(gè)目錄中搜索。
/usr/local/include
/usr/include
而對(duì)于 #include "XXX.h"的頭文件則會(huì)優(yōu)先在當(dāng)前目錄尋找,找不到則繼續(xù)在上述兩個(gè)目錄依次查找。
前面的例子,由于 vector.h 頭文件就是放在 main-lib.c 目錄下,并以 #include "vector.h" 的方式引用,所以它能被搜索到。如果將其放到 /tmp/my-lib 目錄下,則必須是用完整的路徑 #include "/tmp/my-lib/vector.h" 或者 使用 -I /tmp/my-lib 添加一個(gè) include 搜索路徑。
[root@localhost liyang]# mv vector.h /tmp/my-lib
[root@localhost liyang]# gcc -c main-lib.c
main-lib.c:2:20: fatal error: vector.h: No such file or directory
#include "vector.h"
^
compilation terminated.
[root@localhost liyang]# gcc -c main-lib.c -I /tmp/my-lib
頭文件的搜索路徑也可以添加到環(huán)境變量中,避免在命令行中出現(xiàn)較長(zhǎng)的頭文件路徑。例如 C 程序使用 C_INCLUDE_PATH 環(huán)境變量指定頭文件路徑,C++ 使用 CPLUS_INCLUDE_PATH 來(lái)指定搜索路徑。出于方便,可寫(xiě)入 /etc/profile 中。
[root@localhost liyang]# export C_INCLUDE_PATH=/tmp/my-lib
[root@localhost liyang]# echo $C_INCLUDE_PATH
/tmp/my-lib
[root@localhost liyang]# gcc -c main-lib.c
2.3.2 編譯時(shí)的庫(kù)搜索路徑
之前的示例,直接將靜態(tài)庫(kù)或動(dòng)態(tài)庫(kù)作為鏈接文件名輸入給 gcc 命令行。編譯時(shí)如果按照標(biāo)準(zhǔn)的庫(kù)文件使用方式,將使用 -lvector 參數(shù)來(lái)指定引用 vector 庫(kù)(而不必寫(xiě)出庫(kù)文件全名 libvector.a ),同時(shí)用 -L. 參數(shù)指出庫(kù)所在目錄是當(dāng)前目錄。但是執(zhí)行 gcc -static -I/tmp/my-lib -L. -lvector main-lib.c 卻報(bào)出了錯(cuò)誤,說(shuō)找不到 -lc,這是因?yàn)槲窗惭b C 語(yǔ)言的靜態(tài)庫(kù)導(dǎo)致的。使用 yum install -y glibc-static
[root@localhost liyang]# gcc -static -I/tmp/my-lib -L. -lvector main-lib.c
/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status
[root@localhost liyang]# yum install -y glibc-static
[root@localhost liyang]# gcc -static -I/tmp/my-lib -L. -lvector main-lib.c
/tmp/ccv9KblI.o: In function `main':
main-lib.c:(.text+0x19): undefined reference to `addvec'
collect2: error: ld returned 1 exit status
[root@localhost liyang]# gcc -static -I/tmp/my-lib main-lib.c -L. -lvector
[root@localhost liyang]# ./a.out
z = [4 6]
假設(shè)使用 64 位系統(tǒng),默認(rèn)情況下連接時(shí),將在以下目錄搜索庫(kù):
/usr/local/lib64
/usr/lib64
如果你將靜態(tài)庫(kù) libvector.a 移到 /tmp/my-lib 下,則需使用 -L/tmp/my-lib -lvector 指出搜索路徑。
[root@localhost liyang]# mv libvector.a /tmp/my-lib/
[root@localhost liyang]# gcc -static -I/tmp/my-lib main-lib.c -L. -lvector
/bin/ld: cannot find -lvector
collect2: error: ld returned 1 exit status
[root@localhost liyang]# gcc -static -I/tmp/my-lib main-lib.c -L/tmp/my-lib -lvector
[root@localhost liyang]# ./a.out
z = [4 6]
除了直接在命令行加入庫(kù)的搜索路徑,也可像頭文件那樣使用環(huán)境變量 LIBRARY_PATH 將目錄加入到庫(kù)文件的搜索路徑,使用 gcc -print-search-dirs 可以打印 gcc 的搜索路徑,這很有用。
[root@localhost liyang]# export LIBRARY_PATH=/tmp/my-lib
[root@localhost liyang]# gcc -static -I/tmp/my-lib main-lib.c -lvector
[root@localhost liyang]# gcc -print-search-dirs
install: /usr/lib/gcc/x86_64-redhat-linux/4.8.5/
programs: =/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/bin/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/bin/
libraries: =/tmp/my-lib/x86_64-redhat-linux/4.8.5/:/tmp/my-lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/x86_64-redhat-linux/4.8.5/:/lib/../lib64/:/usr/lib/x86_64-redhat-linux/4.8.5/:/usr/lib/../lib64/:/tmp/my-lib/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/lib/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
2.3.3 (運(yùn)行時(shí))動(dòng)態(tài)庫(kù)的載入路徑
2.2.3 節(jié)指出,在 main-lib.c程序中引用動(dòng)態(tài)庫(kù),編譯時(shí)使用gcc -o main-lib-shared main-lib.c libvector.so命令,成功生成引用動(dòng)態(tài)庫(kù)的main-lib-shared可執(zhí)行文件。但是實(shí)際運(yùn)行時(shí),結(jié)果卻顯示無(wú)法找到對(duì)應(yīng)的libvector.so動(dòng)態(tài)庫(kù):
[root@localhost liyang]# gcc -o main-lib-shared main-lib.c libvector.so
[root@localhost liyang]# file main-lib-shared
main-lib-shared: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f22103b363b19e7bc4769c614d476355a70b4316, not stripped
[root@localhost liyang]# ./main-lib-shared
./main-lib-shared: error while loading shared libraries: libvector.so: cannot open shared object file: No such file or directory
這是因?yàn)榫幾g機(jī)制和運(yùn)行機(jī)制中路徑搜索使用不同的信息。
-
LD_LIBRARY_PATH 環(huán)境變量
即使 libvector.so 動(dòng)態(tài)庫(kù)就在 main-lib-shared 同級(jí)目錄,也需告知運(yùn)行環(huán)境從哪里載入動(dòng)態(tài)庫(kù)。最簡(jiǎn)單的方式是使用 LD_LIBRARY_PATH 環(huán)境變量,也可寫(xiě)入 /etc/profile。
[root@localhost liyang]# gcc -o main-lib-shared -I/tmp/my-lib main-lib.c libvector.so [root@localhost liyang]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. [root@localhost liyang]# echo $LD_LIBRARY_PATH :. [root@localhost liyang]# ./main-lib-shared z = [4 6] -
ldconfig 管理工具
ldconfig 工具主要用途是建立動(dòng)態(tài)庫(kù)的搜索路徑的緩存 —— 在默認(rèn)搜索目錄 /lib 和 /usr/lib 以及動(dòng)態(tài)庫(kù)配置文件
/etc/ld.so.conf內(nèi)所列的目錄下,搜索除可共享的動(dòng)態(tài)鏈接庫(kù)(格式如lib*.so*) , 進(jìn)而創(chuàng)建出動(dòng)態(tài)裝入程序(ld.so)所需的鏈接緩存文件。緩存文件默認(rèn)存于 /etc/ld.so.cache, 此文件保存已排好序的動(dòng)態(tài)鏈接庫(kù)的名字列表。為了讓動(dòng)態(tài)鏈接庫(kù)為系統(tǒng)所共享,可運(yùn)行動(dòng)態(tài)鏈接庫(kù)的管理命令 /usr/bin/ldconfig。查看一下 /etc/ld.so.conf 文件的內(nèi)容,它只是將 /etc/ld.so.conf.d/ 目錄之下的所有 *.conf 文件包含進(jìn)來(lái)。而且每個(gè) *.conf 文件中只有相應(yīng)目錄的字符串。
[root@localhost liyang]# cat /etc/ld.so.conf include ld.so.conf.d/*.conf [root@localhost liyang]# ls /etc/ld.so.conf.d/ dyninst-x86_64.conf kernel-3.10.0-693.25.4.el7.x86_64.conf mariadb-x86_64.conf [root@localhost liyang]# cat /etc/ld.so.conf.d/mariadb-x86_64.conf /usr/lib64/mysql由于 動(dòng)態(tài)庫(kù) libvector.so 并沒(méi)有拷貝至系統(tǒng)默認(rèn)目錄中,也沒(méi)在 /etc/ld.so.conf 中添加信息,因此用ldconfig -p 顯示已建立緩存的庫(kù),是找不到 libvector.so 的。 使用 echo /tmp/my-lib > /etc/ld.so.conf.d/my-lib.conf 創(chuàng)建一個(gè) my-lib.conf, 接著運(yùn)行 ldconfig, 將 /tmp/my-lib 路徑加入到庫(kù)文件的路徑緩存中,再次執(zhí)行 ldconfig -p 就可以看到了。這種方式不需要設(shè)置 LD_LIBRARY_PATH 環(huán)境變量。
[root@localhost liyang]# mv libvector.so /tmp/my-lib [root@localhost liyang]# ldconfig -p | grep vector [root@localhost liyang]# ll /tmp/my-lib/* -rw-r--r-- 1 root root 2712 Sep 9 23:52 /tmp/my-lib/libvector.a -rwxr-xr-x 1 root root 7928 Sep 10 00:08 /tmp/my-lib/libvector.so -rw-r--r-- 1 root root 90 Sep 9 23:49 /tmp/my-lib/vector.h [root@localhost liyang]# cat /etc/ld.so.conf include ld.so.conf.d/*.conf [root@localhost liyang]# ll /etc/ld.so.conf.d/my-lib.conf -rw-r--r-- 1 root root 12 Sep 10 01:21 /etc/ld.so.conf.d/my-lib.conf [root@localhost liyang]# ll /etc/ld.so.conf.d/*.conf -rw-r--r--. 1 root root 19 Mar 6 2015 /etc/ld.so.conf.d/dyninst-x86_64.conf -r--r--r--. 1 root root 63 Apr 29 2018 /etc/ld.so.conf.d/kernel-3.10.0-693.25.4.el7.x86_64.conf -rw-r--r--. 1 root root 17 Nov 15 2016 /etc/ld.so.conf.d/mariadb-x86_64.conf -rw-r--r-- 1 root root 12 Sep 10 01:21 /etc/ld.so.conf.d/my-lib.conf [root@localhost liyang]# cat /etc/ld.so.conf.d/my-lib.conf /tmp/my-lib [root@localhost liyang]# ldconfig [root@localhost liyang]# ldconfig -p | grep vector libvector.so (libc6,x86-64) => /tmp/my-lib/libvector.so
2.4 編譯警告
2.4.1 -Wall
GCC 可使用 -Wall 參數(shù)提示所有發(fā)現(xiàn)的警告。-Wall 選項(xiàng)打開(kāi)所有最常用的編譯警告,建議在編譯生產(chǎn)環(huán)境的程序時(shí)都使用該選項(xiàng)。如果不想對(duì)所有警告都提示,可用具體的選項(xiàng)在編譯時(shí)發(fā)出警告。 -Wformat 選項(xiàng)提示 printf() 或 scanf() 函數(shù)中的格式化字符串的誤用; -Wunused 選項(xiàng),會(huì)對(duì)未被使用的變量發(fā)出警告;-Wimplicit 選項(xiàng)對(duì)未申明類(lèi)型的函數(shù)調(diào)用發(fā)出警告;-Wreturn-type 對(duì)函數(shù)返回值類(lèi)型不對(duì)的情況發(fā)出警告。
2.4.2 -Wall 范圍外的警告
-W : 通常和 -Wall 同時(shí)使用,對(duì)一些常用編程錯(cuò)誤發(fā)出警告,例如需要返回值但無(wú)返回值的函數(shù),或?qū)⒂蟹?hào)數(shù)和無(wú)符號(hào)數(shù)進(jìn)行比較。比如判斷無(wú)符號(hào)數(shù) 是否 <0, -Wall 通常不會(huì)發(fā)出警告,但 -W 則會(huì)。
-Wconversion : 警告可能引起意外結(jié)果的隱式轉(zhuǎn)換,比如 將 -1 賦給 一個(gè)無(wú)符號(hào)數(shù)。
-Wshadow : 用來(lái)警告同名變量覆蓋的情形。
-Wcast-qual : 用來(lái)警告對(duì)指針的轉(zhuǎn)換操作移除了某種類(lèi)型修飾符,比如 const。
-Wwrite-strings : 用來(lái)警告對(duì)字面量字符串的覆寫(xiě)。
2.4.3 -Werror
將警告轉(zhuǎn)變?yōu)殄e(cuò)誤,一遇到警告就停止編譯。
3. GDB 調(diào)試
為了讓 GDB 調(diào)試,可執(zhí)行文件需要附加額外的調(diào)試信息,因此在編譯的時(shí)候需要給 GCC 傳遞 -g 參數(shù)。例如 gcc -g demo-gdb.c -o demo-gdb編譯代碼生成帶有調(diào)試信息的可執(zhí)行文件 demo-gdb。
gdb 有 3 種啟動(dòng)方式:
-
gdb <program>: 使用 gdb 啟動(dòng)程序<program>。gdb 將 <program> 裝入系統(tǒng)形成子進(jìn)程。 -
gdb <program> <PID>: 指定正在運(yùn)行的進(jìn)程的 PID,GDB 會(huì)自動(dòng) attach 上去,并調(diào)試該進(jìn)程。亦可用gdb -p <PID>調(diào)試正在運(yùn)行的進(jìn)程。這不會(huì)創(chuàng)建新進(jìn)程。 -
gdb <program> core: 用 GDB 同時(shí)調(diào)試一個(gè)可執(zhí)行程序和 core 文件。這會(huì)根據(jù)可執(zhí)行程序和 core dump 文件創(chuàng)建新進(jìn)程。
直接運(yùn)行代碼:
(gdb) r
Starting program: /home/liyang/demo-gdb/demo-gdb
result[1-100] = 4950
result[1-200] = 19900
[Inferior 1 (process 16149) exited with code 026]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64
單步調(diào)試:
首先用 start 啟動(dòng)調(diào)試,然后用 n(next) 或 s (step) 逐條指令執(zhí)行。其中 n 不會(huì)進(jìn)入 C 函數(shù)內(nèi)部, 而 s 則會(huì)。另外 , si 和 ni 是匯編級(jí)別的斷定定位, si 會(huì)進(jìn)入?yún)R編和 C 函數(shù)的內(nèi)部,ni 不會(huì)進(jìn)入函數(shù)內(nèi)部。
(gdb) start
Temporary breakpoint 2 at 0x400563: file demo-gdb.c, line 16.
Starting program: /home/liyang/demo-gdb/demo-gdb
Temporary breakpoint 2, main () at demo-gdb.c:16
16 long result =0;
(gdb) s
18 for (i=0; i< 100; i++)
(gdb) s
20 result += i;
斷點(diǎn):
下面使用 b 21 和 b func 命令分別在程序第 21 行和 func() 函數(shù)入口處設(shè)置斷點(diǎn),然后用 r 開(kāi)始執(zhí)行程序到第 1 個(gè)斷電,再用 c(完整命令是 continue ) 繼續(xù)運(yùn)行到第 2 個(gè)斷點(diǎn), 最后使用 c 運(yùn)行到結(jié)尾。使用 i b 可以查看所有斷點(diǎn)及其編號(hào)。 使用 d (delete) 斷點(diǎn)編號(hào),可刪除對(duì)應(yīng)的斷點(diǎn)。
(gdb) b 21
Breakpoint 1 at 0x400587: file demo-gdb.c, line 21.
(gdb) b func
Breakpoint 2 at 0x400534: file demo-gdb.c, line 5.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400587 in main at demo-gdb.c:21
2 breakpoint keep y 0x0000000000400534 in func at demo-gdb.c:5
(gdb) r
Starting program: /home/liyang/demo-gdb/demo-gdb
Breakpoint 1, main () at demo-gdb.c:23
23 printf("result[1-100] = %ld\n", result);
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64
(gdb) l
18 for (i=0; i< 100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %ld\n", result);
24 printf("result[1-200] = %d\n", func(200));
25 }
26
(gdb) c
Continuing.
result[1-100] = 4950
Breakpoint 2, func (n=200) at demo-gdb.c:5
5 int sum=0,i;
(gdb) c
Continuing.
result[1-200] = 19900
[Inferior 1 (process 29446) exited with code 026]
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400587 in main at demo-gdb.c:21
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000400534 in func at demo-gdb.c:5
breakpoint already hit 1 time
條件斷點(diǎn):
如果需要再特定條件下讓程序停下來(lái)檢查,則需使用條件斷點(diǎn)(catchpoint)。例如在程序第 19 行只有當(dāng)循環(huán)到 i == 20 的時(shí)候才停下來(lái)檢查,可以使用 b 19 if i==20創(chuàng)建條件斷點(diǎn)。
(gdb) i b
No breakpoints or watchpoints.
(gdb) b 19 if i==20
Breakpoint 1 at 0x400574: file demo-gdb.c, line 19.
(gdb) b 21
Breakpoint 2 at 0x400587: file demo-gdb.c, line 21.
(gdb) b func
Breakpoint 3 at 0x400534: file demo-gdb.c, line 5.
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400574 in main at demo-gdb.c:19
stop only if i==20
2 breakpoint keep y 0x0000000000400587 in main at demo-gdb.c:21
3 breakpoint keep y 0x0000000000400534 in func at demo-gdb.c:5
(gdb) r
Starting program: /home/liyang/demo-gdb/demo-gdb
Breakpoint 1, main () at demo-gdb.c:20
20 result += i;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64
(gdb) p i
$1 = 20
(gdb) p result
$2 = 190
可用 condition 命令設(shè)置斷點(diǎn)的條件,為普通無(wú)條件斷點(diǎn)增加條件,例如, condition 3 a > 5 為 3 號(hào)斷點(diǎn)設(shè)置條件 a > 5。 如果將條件設(shè)置為空, 如 condition 3 可以刪除 3 號(hào)條件斷點(diǎn)的條件而變成普通斷點(diǎn)。
ignore 指令會(huì)忽略若干次斷點(diǎn),例如 ignore 3 10 會(huì)忽略前 10 次執(zhí)行到 3 號(hào)斷點(diǎn)。ignore 不僅對(duì) breakpoint, 也對(duì) catchpoint 和后面的 watchpoint 有效。
查看變量和內(nèi)存:
寄存器: 使用
i r查看寄存器, 使用 info all-registers 可顯示更多寄存器內(nèi)容。如果只想查看一個(gè)寄存器的內(nèi)容,使用p $REG-
變量與內(nèi)存單元:
p/? 變量名可以按照指定的格式輸出變量的值:x 按十六進(jìn)制顯示變量 t 按二進(jìn)制顯示變量 d 按十進(jìn)制顯示變量 a 按十六進(jìn)制顯示變量 u 按十六進(jìn)制顯示無(wú)符號(hào)整數(shù) c 按字符格式顯示變量 o 按八進(jìn)制顯示變量 f 按浮點(diǎn)數(shù)格式顯示變量 如需查看內(nèi)存單元,可以使用
p address或 使用 x 命令(對(duì)應(yīng) examine),具體格式為x/nfu address- n 表示顯示數(shù)據(jù)的個(gè)數(shù),是個(gè)正整數(shù)
- f 表示顯示的格式,如上表所示,如果是指令地址,格式為 i ;
- u 表示從當(dāng)前地址往后請(qǐng)求的字節(jié)數(shù),默認(rèn)是 4 個(gè)字節(jié)。如果指定的話(huà),b 表示單字節(jié),h 表示雙字節(jié), w 表示四個(gè)字節(jié),g 表示八個(gè)字節(jié)。
例如,
x/4xh 0x400574表示從 0x400574 開(kāi)始按十六進(jìn)制的形式,打印 4 個(gè) 雙字節(jié)數(shù)據(jù)。監(jiān)控點(diǎn)(watchpoint):
使用 watch 命令對(duì)變量進(jìn)行監(jiān)控,僅在其值發(fā)生變化時(shí)才將程序中斷。