Linux下組譯工具及系統調用的深入分析

來源:互聯網
上載者:User

一)彙編來源程式分析:
編寫一個AT&T的組合語言程式,使程式在運行時睡眠10秒鐘

原始碼如下:

#include "sys/syscall.h"

.data
sleeptime:
.long   10,0

.text

.global _start
        .type _start, @function

_start:
        movl    $SYS_nanosleep, %eax
        movl    $sleeptime,     %ebx
        int     $0x80

        movl    $SYS_exit,      %eax
        movl    $0,             %ebx
        int     $0x80

 

1)組合語言載入了C的標頭檔

##############################################################
#include "sys/syscall.h"
##############################################################

載入了sys/syscall.h標頭檔,syscall.h中定義了系統調用的宏.
而POSIX函數是系統調用的簡單封裝,這裡指的POSIX函數就是類似於open,close,read等
也就是說open不是系統調用,而是POSIX函數,只是這個POSIX函數包含了由核心賦值的系統調用.
例如:
#include <syscall.h>
 n = syscall(SYS_read, fd, buffer,length);
在上面的例子中,系統調用由宏SYS_read定義,而read函數則對上面的系統調用進行封裝.

2)Linux使用的Syscall技術

Linux使用的syscall技術稱為應用程式的二進位介面(ABI),它與應用程式介面(API)不同,API要求連結相容的函數,
而ABI不要求連結不需要啟動並執行代碼
也就是說,當你直接調用ABI(SYSCALL)的時候,是不需要連結庫,而調用API,例如read,則需要連結庫,例如libc等等.

3)程式碼片段與資料區段
###############################################################
.data
sleeptime:
.long   10,0
###############################################################
Linux 是一個運行在保護模式下的 32 位作業系統,採用 flat memory 模式,目前最常用到的是 ELF 格式的二進位代碼.
一個 ELF 格式的可執行程式通常劃分為如下幾個部分:.text、.data 和 .bss,其中 .text 是唯讀代碼區,.data 是可讀可寫的資料區,
而 .bss 則是可讀可寫且沒有初始化的資料區。
代碼區和資料區在 ELF 中統稱為 section,根據實際需要你可以使用其它標準的 section,也可以添加自訂 section,但一個 ELF 可執行程式至少應該有一個 .text 部分
這裡用了資料區段.data,並定義了sleeptime: .long 10.0,long 即32位bit的長度

 

4)程式碼片段的入口
###############################################################
.text
.global _start
        .type _start, @function
###############################################################
這裡定義了程式碼片段.text,定義了全域入口為_start,ld連結器會預設選擇_start做為符號入口,如果在程式中不指定_start做為全域入口,編譯將得到下面的警告,但不影響運行
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000008048074
注意指定_start做為全域程式入口,而在連結時同時也連結了/usr/lib/crt1.o,將報錯,因為crt1.o已經提供了一個_start進入點,這裡有就會出現多重定義,所以報錯.
一個C程式的進入點是main函數,其實是不準確的,真正的進入點是_start,因為ld會預設把crt.o和目標程式連結在一起,也就是程式先調用_start(startup routine),再通過_start調用main函數.

 

5)系統調用
###############################################################
_start:
        movl    $SYS_nanosleep, %eax
        movl    $sleeptime,     %ebx
        int     $0x80

        movl    $SYS_exit,      %eax
        movl    $0,             %ebx
        int     $0x80
###############################################################
這裡調用了SYS_nanosleep和SYS_exit的系統調用.類似於C的read函數的封裝調用:
 n = syscall(SYS_read, fd, buffer,length);

 

二)編譯與連結

以上的程式通過下面的語句進行編譯:
gcc -o basic -nostdlib basic.S

1)這裡為什麼要指定-nostdlib呢.
答案是因為我們不需要main函數的調用,GCC編譯器預設會在main函數的開始部分產生一個__main函數的調用,如果我們使用-nostdlib編譯器,將不會調用__main函數,自然也不會在連結的時候加上-lgcc.

在編譯時間加上-v,來列印出編譯的過程:
gcc -o basic -nostdlib basic.S   -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.2-1.1' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-targets=all --enable-cld --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.3.2 (Debian 4.3.2-1.1)
COLLECT_GCC_OPTIONS='-o' 'basic' '-nostdlib' '-v' '-mtune=generic'
 /usr/lib/gcc/i486-linux-gnu/4.3.2/cc1 -E -lang-asm -quiet -v basic.S -mtune=generic -fno-directives-only -o /tmp/ccsjdQ1s.s
ignoring nonexistent directory "/usr/local/include/i486-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../i486-linux-gnu/include"
ignoring nonexistent directory "/usr/include/i486-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /usr/lib/gcc/i486-linux-gnu/4.3.2/include
 /usr/lib/gcc/i486-linux-gnu/4.3.2/include-fixed
 /usr/include
End of search list.
COLLECT_GCC_OPTIONS='-o' 'basic' '-nostdlib' '-v' '-mtune=generic'
 as -V -Qy -o /tmp/cc6h5zXO.o /tmp/ccsjdQ1s.s
GNU assembler version 2.18.0 (i486-linux-gnu) using BFD version (GNU Binutils for Debian) 2.18.0.20080103
COMPILER_PATH=/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-o' 'basic' '-nostdlib' '-v' '-mtune=generic'
 /usr/lib/gcc/i486-linux-gnu/4.3.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=both -dynamic-linker /lib/ld-linux.so.2 -o basic -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../.. /tmp/cc6h5zXO.o

2)加-nostdlib編譯與連結的過程:

第一步:
/usr/lib/gcc/i486-linux-gnu/4.3.2/cc1 -E -lang-asm -quiet -v basic.S -mtune=generic -fno-directives-only -o /tmp/ccsjdQ1s.s
第二步:
as -V -Qy -o /tmp/cc6h5zXO.o /tmp/ccsjdQ1s.s
第三步:
/usr/lib/gcc/i486-linux-gnu/4.3.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=both -dynamic-linker /lib/ld-linux.so.2 -o basic -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../.. /tmp/cc6h5zXO.o

3)不加-nostdlib編譯與連結的過程:

第一步:
/usr/lib/gcc/i486-linux-gnu/4.3.2/cc1 -E -lang-asm -quiet -v basic.S -mtune=generic -fno-directives-only -o /tmp/ccsjdQ1s.s
第二步:
as -V -Qy -o /tmp/cc6h5zXO.o /tmp/ccsjdQ1s.s
第三步:
/usr/lib/gcc/i486-linux-gnu/4.3.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=both -dynamic-linker /lib/ld-linux.so.2 -o basic /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crti.o /usr/lib/gcc/i486-linux-gnu/4.3.2/crtbegin.o -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2 -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/i486-linux-gnu/4.3.2/../../.. /tmp/cccfpbFe.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-linux-gnu/4.3.2/crtend.o /usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crtn.o

注意:不加-nostdlib進行編譯會報錯的.

對比結果可以看出在collect2的第三步是不同的

4)什麼是collect2
collect2是ld連結器的一個封裝,最終還是要調用ld來完成連結工作,collect2的作用是在實現main函數的代碼前對目標檔案中命名的特殊符號進行收集.
這些特殊的符號表明它們是全域建構函式或在main前執行,collect2會產生一個臨時的.c檔案,將這些符號的地址收整合一個數組,然後放到這個.c檔案裡面,編譯後與其他目標檔案一起被連結到最終的輸出檔案中。
在這裡我們沒有加-nostdlib,所以自然不會調用__main,也就不會連結main函數所需引用的目標檔案,也就不會對那些特殊的符號進行收集.

 

5)不加-nostdlib為什麼會報錯?

以下是不加-nostdlib的報錯資訊:

gcc -o basic  basic.S 
/tmp/ccaNEi0C.o: In function `_start':
(.text+0x0): multiple definition of `_start'
/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o:(.text+0x0): first defined here
/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status

一共有兩個錯誤
第一個錯誤:
(.text+0x0): multiple definition of `_start'
它告訴我們,有多個地方引用了_start,我們只是在一個地方指定了_start的引用,另一個引用在哪裡?
答案是在/usr/lib/crt1.o裡面:
查看crt1.o的符號表:
nm /usr/lib/crt1.o
00000000 R _IO_stdin_used
00000000 D __data_start
         U __libc_csu_fini
         U __libc_csu_init
         U __libc_start_main
00000000 R _fp_hw
00000000 T _start
00000000 W data_start
         U main
看到這裡是先引用了_start,才調用了main

OK,為什麼在加-nostdlib進行編譯時間沒有這個錯誤呢,因為加-nostdlib是不會連結crt1.o,也就不會收集它的符號表,自然不會報錯.

如果在組譯工具將程式的進入點用別的名字呢?

編譯的結果是不會報這個錯.
例如:
gcc -o basic  basic.S 
/usr/lib/gcc/i486-linux-gnu/4.3.2/../../../../lib/crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status

而(.text+0x18): undefined reference to `main'錯誤又是怎麼回事呢?
那是因為沒加-nostdlib,它依然是連結了crt1.o,也就是收集了它的符號表,符號表裡依然有main的調用,而組譯工具中沒有main函數,所以自然會報錯.
這就是引發第二個錯誤的原因.

 

三)用stract命令分析這個程式

命令如下:
strace -t ./basic
07:27:56 execve("./basic", ["./basic"], [/* 17 vars */]) = 0
07:27:56 nanosleep({5, 0}, NULL)        = 0
07:28:01 _exit(0)                                 = ?

這裡涉有三個系統調用:
1)execve("./basic", ["./basic"], [/* 17 vars */]) = 0
execve是在父進程中fork一個子進程,在子進程中調用exec函數啟動新的程式.
exec函數一共有六個,其中execve為核心級系統調用,其他(execl,execle,execlp,execv,execvp)都是調用execve的庫函數。

2)nanosleep({5, 0}, NULL)        = 0
是我們組譯工具中
        movl    $SYS_nanosleep, %eax
        movl    $sleeptime,     %ebx
        int     $0x80
的系統調用,意為程式的睡眠時間.

3)_exit(0)                                 = ?
是我們組譯工具中
        movl    $SYS_exit,      %eax
        movl    $0,             %ebx
        int     $0x80
的系統調用,意為退出程式.

 

這三個系統調用(syscall)就使程式從使用者模式轉向核心模式,而strace命令是會根蹤核心模式的系統調用,並將結果列印輸出.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.