Linux C 程序的編譯與運(yùn)行

摘自《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ò)程如下圖所示:

生產(chǎn)可執(zhí)行文件的編譯步驟.jpg

? 圖 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ù)組。

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

  1. -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.omultvec.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.oaddvec.omultvec.o 兩個(gè)目標(biāo)文件歸檔到 libvector.a 這個(gè)靜態(tài)庫(kù)中(其中參數(shù)中的 c 表示 create, r 表示 replace)。使用 ar -t libvector.a命令可以確認(rèn)其中有兩個(gè)文件 addvec.omultvec.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)方式:

  1. gdb <program> : 使用 gdb 啟動(dòng)程序 <program>。gdb 將 <program> 裝入系統(tǒng)形成子進(jìn)程。
  2. 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)程。
  3. 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)存:

  1. 寄存器: 使用 i r 查看寄存器, 使用 info all-registers 可顯示更多寄存器內(nèi)容。如果只想查看一個(gè)寄存器的內(nèi)容,使用 p $REG

  2. 變量與內(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í)才將程序中斷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容