linux与汇编
Linux操作系統(tǒng)是用C語言編寫的,匯編只在必要的時候才被人們想到,但它卻是減少代碼尺寸和優(yōu)化代碼性能的一種非常重要的手段,特別是在與硬件 直接交互的時候,匯編可以說是最佳的選擇。Linux提供了非常優(yōu)秀的工具來支持匯編程序的開發(fā),使用GCC的內(nèi)聯(lián)匯編能夠充分地發(fā)揮C語言和匯編語言各 自的優(yōu)點。
作為最基本的編程語言之一,匯編語言雖然應(yīng)用的范圍不算很廣,但重要性卻勿庸置疑,因為它能夠完成許多其它語言所無法完成的功能。就拿 Linux 內(nèi)核來講,雖然絕大部分代碼是用 C 語言編寫的,但仍然不可避免地在某些關(guān)鍵地方使用了匯編代碼,其中主要是在 Linux 的啟動部分。由于這部分代碼與硬件的關(guān)系非常密切,即使是 C 語言也會有些力不從心,而匯編語言則能夠很好揚長避短,最大限度地發(fā)揮硬件的性能。
大多數(shù)情況下 Linux 程序員不需要使用匯編語言,因為即便是硬件驅(qū)動這樣的底層程序在 Linux 操作系統(tǒng)中也可以用完全用 C 語言來實現(xiàn),再加上 GCC 這一優(yōu)秀的編譯器目前已經(jīng)能夠?qū)ψ罱K生成的代碼進行很好的優(yōu)化,的確有足夠的理由讓我們可以暫時將匯編語言拋在一邊了。但實現(xiàn)情況是 Linux 程序員有時還是需要使用匯編,或者不得不使用匯編,理由很簡單:精簡、高效和 libc 無關(guān)性。假設(shè)要移植 Linux 到某一特定的嵌入式硬件環(huán)境下,首先必然面臨如何減少系統(tǒng)大小、提高執(zhí)行效率等問題,此時或許只有匯編語言能幫上忙了。
匯編語言直接同計算機的底層軟件甚至硬件進行交互,它具有如下一些優(yōu)點:
·? ? ? ? ?能夠直接訪問與硬件相關(guān)的存儲器或 I/O 端口;
·? ? ? ? ?能夠不受編譯器的限制,對生成的二進制代碼進行完全的控制;
·? ? ? ? ?能夠?qū)﹃P(guān)鍵代碼進行更準確的控制,避免因線程共同訪問或者硬件設(shè)備共享引起的死鎖;
·? ? ? ? ?能夠根據(jù)特定的應(yīng)用對代碼做最佳的優(yōu)化,提高運行速度;
·? ? ? ? ?能夠最大限度地發(fā)揮硬件的功能。
?
同時還應(yīng)該認識到,匯編語言是一種層次非常低的語言,它僅僅高于直接手工編寫二進制的機器指令碼,因此不可避免地存在一些缺點:
·? ? ? ? ?編寫的代碼非常難懂,不好維護;
·? ? ? ? ?很容易產(chǎn)生 bug,難于調(diào)試;
·? ? ? ? ?只能針對特定的體系結(jié)構(gòu)和處理器進行優(yōu)化;
·? ? ? ? ?開發(fā)效率很低,時間長且單調(diào)。
?
Linux 下用匯編語言編寫的代碼具有兩種不同的形式。第一種是完全的匯編代碼,指的是整個程序全部用匯編語言編寫。盡管是完全的匯編代碼,Linux 平臺下的匯編工具也吸收了 C 語言的長處,使得程序員可以使用 #include、#ifdef 等預(yù)處理指令,并能夠通過宏定義來簡化代碼。第二種是內(nèi)嵌的匯編代碼,指的是可以嵌入到C語言程序中的匯編代碼片段。雖然 ANSI 的 C 語言標準中沒有關(guān)于內(nèi)嵌匯編代碼的相應(yīng)規(guī)定,但各種實際使用的 C 編譯器都做了這方面的擴充,這其中當(dāng)然就包括 Linux 平臺下的 GCC。
二、Linux 匯編語法格式
絕大多數(shù) Linux 程序員以前只接觸過DOS/Windows 下的匯編語言,這些匯編代碼都是 Intel 風(fēng)格的。但在 Unix 和 Linux 系統(tǒng)中,更多采用的還是 AT&T 格式,兩者在語法格式上有著很大的不同:
三、Hello World!
真不知道打破這個傳統(tǒng)會帶來什么樣的后果,但既然所有程序設(shè)計語言的第一個例子都是在屏幕上打印一個字符串 “Hello World!”,那我們也以這種方式來開始介紹 Linux 下的匯編語言程序設(shè)計。
在 Linux 操作系統(tǒng)中,你有很多辦法可以實現(xiàn)在屏幕上顯示一個字符串,但最簡潔的方式是使用 Linux 內(nèi)核提供的系統(tǒng)調(diào)用。使用這種方法最大的好處是可以直接和操作系統(tǒng)的內(nèi)核進行通訊,不需要鏈接諸如 libc 這樣的函數(shù)庫,也不需要使用 ELF 解釋器,因而代碼尺寸小且執(zhí)行速度快。
Linux 是一個運行在保護模式下的 32 位操作系統(tǒng),采用 flat memory 模式,目前最常用到的是 ELF 格式的二進制代碼。一個 ELF 格式的可執(zhí)行程序通常劃分為如下幾個部分:.text、.data 和 .bss,其中 .text 是只讀的代碼區(qū),.data 是可讀可寫的數(shù)據(jù)區(qū),而 .bss 則是可讀可寫且沒有初始化的數(shù)據(jù)區(qū)。代碼區(qū)和數(shù)據(jù)區(qū)在 ELF 中統(tǒng)稱為 section,根據(jù)實際需要你可以使用其它標準的 section,也可以添加自定義 section,但一個 ELF 可執(zhí)行程序至少應(yīng)該有一個 .text 部分。下面給出我們的第一個匯編程序,用的是 AT&T 匯編語言格式:
例1. AT&T 格式
#hello.s
.data? ? ? ? ? ? ? ? ? ? # 數(shù)據(jù)段聲明
? ? ? ? msg : .string "Hello, world!\n" # 要輸出的字符串
? ? ? ? len = . - msg? ? ? ? ? ? ? ? ? ?# 字串長度
.text? ? ? ? ? ? ? ? ? ? # 代碼段聲明
.global _start? ? ? ? ? ?# 指定入口函數(shù)
?
_start:? ? ? ? ? ? ? ? ? # 在屏幕上顯示一個字符串
? ? ? ? movl $len, %edx? # 參數(shù)三:字符串長度
? ? ? ? movl $msg, %ecx? # 參數(shù)二:要顯示的字符串
? ? ? ? movl $1, %ebx? ? # 參數(shù)一:文件描述符(stdout)
? ? ? ? movl $4, %eax? ? # 系統(tǒng)調(diào)用號(sys_write)
? ? ? ? int? $0x80? ? ? ?# 調(diào)用內(nèi)核功能
?
? ? ? ? ? ? ? ? ? ? ? ? ?# 退出程序
? ? ? ? movl $0,%ebx? ? ?# 參數(shù)一:退出代碼
? ? ? ? movl $1,%eax? ? ?# 系統(tǒng)調(diào)用號(sys_exit)
? ? ? ? int? $0x80? ? ? ?# 調(diào)用內(nèi)核功能
?
初次接觸到 AT&T 格式的匯編代碼時,很多程序員都認為太晦澀難懂了,沒有關(guān)系,在 Linux 平臺上你同樣可以使用 Intel 格式來編寫匯編程序:
例2. Intel 格式
; hello.asm
section .data? ? ? ? ? ? ; 數(shù)據(jù)段聲明
? ? ? ? msg db "Hello, world!", 0xA? ? ?; 要輸出的字符串
? ? ? ? len equ $ - msg? ? ? ? ? ? ? ? ?; 字串長度
section .text? ? ? ? ? ? ; 代碼段聲明
global _start? ? ? ? ? ? ; 指定入口函數(shù)
_start:? ? ? ? ? ? ? ? ? ; 在屏幕上顯示一個字符串
? ? ? ? mov edx, len? ? ?; 參數(shù)三:字符串長度
? ? ? ? mov ecx, msg? ? ?; 參數(shù)二:要顯示的字符串
? ? ? ? mov ebx, 1? ? ? ?; 參數(shù)一:文件描述符(stdout)
? ? ? ? mov eax, 4? ? ? ?; 系統(tǒng)調(diào)用號(sys_write)
? ? ? ? int 0x80? ? ? ? ?; 調(diào)用內(nèi)核功能
? ? ? ? ? ? ? ? ? ? ? ? ?; 退出程序
? ? ? ? mov ebx, 0? ? ? ?; 參數(shù)一:退出代碼
? ? ? ? mov eax, 1? ? ? ?; 系統(tǒng)調(diào)用號(sys_exit)
? ? ? ? int 0x80? ? ? ? ?; 調(diào)用內(nèi)核功能
?
上面兩個匯編程序采用的語法雖然完全不同,但功能卻都是調(diào)用 Linux 內(nèi)核提供的 sys_write 來顯示一個字符串,然后再調(diào)用 sys_exit 退出程序。在 Linux 內(nèi)核源文件 include/asm-i386/unistd.h 中,可以找到所有系統(tǒng)調(diào)用的定義。
四、Linux 匯編工具
Linux 平臺下的匯編工具雖然種類很多,但同 DOS/Windows 一樣,最基本的仍然是匯編器、連接器和調(diào)試器。
1.匯編器
匯編器(assembler)的作用是將用匯編語言編寫的源程序轉(zhuǎn)換成二進制形式的目標代碼。Linux 平臺的標準匯編器是 GAS,它是 GCC 所依賴的后臺匯編工具,通常包含在 binutils 軟件包中。GAS 使用標準的 AT&T 匯編語法,可以用來匯編用 AT&T 格式編寫的程序:
[xiaowp@gary code]$ as -o hello.o hello.s
?
Linux 平臺上另一個經(jīng)常用到的匯編器是 NASM,它提供了很好的宏指令功能,并能夠支持相當(dāng)多的目標代碼格式,包括 bin、a.out、coff、elf、rdf 等。NASM 采用的是人工編寫的語法分析器,因而執(zhí)行速度要比 GAS 快很多,更重要的是它使用的是 Intel 匯編語法,可以用來編譯用 Intel 語法格式編寫的匯編程序:
[xiaowp@gary code]$ nasm -f elf hello.asm
?
2.鏈接器
由匯編器產(chǎn)生的目標代碼是不能直接在計算機上運行的,它必須經(jīng)過鏈接器的處理才能生成可執(zhí)行代碼。鏈接器通常用來將多個目標代碼連接成一個可執(zhí)行代 碼,這樣可以先將整個程序分成幾個模塊來單獨開發(fā),然后才將它們組合(鏈接)成一個應(yīng)用程序。 Linux 使用 ld 作為標準的鏈接程序,它同樣也包含在 binutils 軟件包中。匯編程序在成功通過 GAS 或 NASM 的編譯并生成目標代碼后,就可以使用 ld 將其鏈接成可執(zhí)行程序了:
3.調(diào)試器
有人說程序不是編出來而是調(diào)出來的,足見調(diào)試在軟件開發(fā)中的重要作用,在用匯編語言編寫程序時尤其如此。Linux 下調(diào)試匯編代碼既可以用 GDB、DDD 這類通用的調(diào)試器,也可以使用專門用來調(diào)試匯編代碼的 ALD(Assembly Language Debugger)。
從調(diào)試的角度來看,使用 GAS 的好處是可以在生成的目標代碼中包含符號表(symbol table),這樣就可以使用 GDB 和 DDD 來進行源碼級的調(diào)試了。要在生成的可執(zhí)行程序中包含符號表,可以采用下面的方式進行編譯和鏈接:
[xiaowp@gary code]$ as --gstabs -o hello.o hello.s
[xiaowp@gary code]$ ld -o hello hello.o
執(zhí)行 as 命令時帶上參數(shù) –gstabs 可以告訴匯編器在生成的目標代碼中加上符號表,同時需要注意的是,在用 ld 命令進行鏈接時不要加上 -s 參數(shù),否則目標代碼中的符號表在鏈接時將被刪去。
在 GDB 和 DDD 中調(diào)試匯編代碼和調(diào)試 C 語言代碼是一樣的,你可以通過設(shè)置斷點來中斷程序的運行,查看變量和寄存器的當(dāng)前值,并可以對代碼進行單步跟蹤。圖1 是在 DDD 中調(diào)試匯編代碼時的情景:
?用 DDD 中調(diào)試匯編程序
匯編程序員通常面對的都是一些比較苛刻的軟硬件環(huán)境,短小精悍的ALD可能更能符合實際的需要,因此下面主要介紹一下如何用ALD來調(diào)試匯編程序。首先在命令行方式下執(zhí)行ald命令來啟動調(diào)試器,該命令的參數(shù)是將要被調(diào)試的可執(zhí)行程序:
總結(jié)
                            
                        - 上一篇: C C++面试常问简答题(1)
 - 下一篇: typename的作用