目前使用的作業系統:ubuntu11.10
彙程式設計式由定義好的段構成,每個段都有不同的目的,三個最常用的段:
1)data 段
彙程式設計式 data(資料)段是可選的。
資料段聲明帶有初始值的資料元素,這些資料元素用作彙程式設計式的變量。
2)bss 段
彙程式設計式 bss段 是可選的。
bss段聲明使用 零(或 NULL)值初始化的資料元素。這些元素最常用作彙程式設計式中的緩沖區
3)text 段
彙程式設計式必須有 text(文本)段。
這個段是 在可執行程式内聲明指令碼 的地方。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5ydl1Wb2ETM4cDN4UzMx8VMwgDO1QDOy8CX4EzLcFDMzEDMy8CX05WZth2YhRHdh9CX0VmbugXauVXYulGaj5yZvxmYvw1LcpDc0RHaiojIsJye.png)
定義段:
GNU彙編器使用 .section 指令語句聲明段。
.section 語句隻使用一個參數------它聲明的段的類型
定義起始點:
當彙編語言程式被轉換為可執行檔案時,連接配接器必須知道指令碼中的起始點是什麼。
GNU 彙編器聲明一個預設标簽,或者說辨別符,_start ,它應該用作應用程式的入口點。
_start 标簽用于表明程式應該從這條指令開始運作。如果連接配接器找不到這個标簽,就會生成如下錯誤消息:
彙程式設計式的基本模闆如下:
1 2 3 4 5 6 7 8 9 10 11 | |
@a1@
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
sdf
CPUID 指令是一條彙編指令,它是請求處理器的特定資訊并且把資訊傳回到特定寄存器的低級指令。
CPUID 指令使用單一寄存器值作為輸入。EAX 寄存器用于決定 CPUID 指令生成什麼資訊。根據 EAX 寄存器的值, CPUID 指令在 EBX,ECX 和 EDX 寄存器中生成關于處理器的不同資訊。
本例子中,使用零選項從處理器獲得簡單的廠商ID字元串。
當 零(0)值被放入到 EAX 寄存器并且執行 CPUID 指令時,處理器把廠商ID字元串傳回到 EBX,EDX 和 ECX 寄存器中,如下:
1)EBX 包含字元串的最低 4 個位元組
2)EDX 包含字元串的中間 4 個位元組
3)ECX 包含字元串的最高 4 個位元組
字元串按照 小端法(little-endian)格式放入寄存器中:
@b1@ 在資料段中聲明了一個字元串:
1 2 | |
.ascii 聲明使用 ASCII 字元聲明一個文本字元串。
這個字元串元素被預定義并且放在記憶體中,其起始記憶體位置由 output辨別符 訓示。
後面的 x 在保留給資料變量的記憶體區域中作為占位符,從處理器獲得廠商ID字元串時,會把這些字元串放在位于這些記憶體位置的資料中。
@b2@ 聲明程式的指令碼和一般的起始位置:
1 2 3 | |
@b3@ 程式做的第一件事就是使 EAX 寄存器 加載 零 值,然後運作 CPUID 指令:
1 2 | |
@b4@ EAX中的零值定義 CPUID 輸出選項(在本例子 中是廠商ID字元串)。
CPUID指令運作之後,必須收集分散在 3個 輸出寄存器中的指令響應:
1 2 3 4 | |
首先建立一個指針,處理記憶體中聲明的 output 變量時會使用這個指針。
output 辨別符的記憶體位置被加載到 EDI 寄存器中。
接下來,按照 EDI 指針,包含廠商ID字元串片段的 3 個寄存器的内容被放到資料記憶體中的正确位置。
括号外的數值表示相對于 output辨別符的放置資料的位置。這個數字和 EDI 寄存器中的位址相加,确定寄存器中的值被寫入的位址。這個過程使用實際的廠商ID字元串片段替換用作 x 占位符(注意:廠商ID字元串按照奇怪的順序 EBX,EDX 和 ECX 分散在寄存器中) 。
@b5@ 在記憶體中放置好所有廠商ID字元串片段之後,就可以顯示資訊了:
1 2 3 4 5 | |
系統調用表:http://blog.chinaunix.net/uid-28458801-id-3477399.html
使用一個 linux 系統調用(int $0x80)從 linux 核心通路控制台顯示。linux 核心提供了很多可以很容易地從彙編應用程式通路的預置函數。為通路核心函數,必須使用 int 指令碼,它生成具有 0x80 值的軟中斷。
執行的具體函數有EAX寄存器的值來确定。如果沒有這個核心函數,就必須自己把每個輸出字元發送到正确的顯示器 I/O位址上。
4 對應的是 sys_write
linux 的 write 系統調用用于把位元組寫入到檔案中。
1)EAX包含系統調用的值
2)EBX包含要寫入的檔案描述符
3)ECX包含字元串的起始位址
4)EDX包含字元串的長度
标準輸出(STDOUT)表示目前會話的顯示終端,它的檔案描述符為 1,。寫入到這個檔案描述符将在控制台螢幕上顯示資訊。
@b6@ 顯示完資訊後,就應該退出程式:
1 2 3 | |
sdf
通過系統調用1(sys_exit()),程式被正确的終止,并且傳回到指令提示符,零值表示程式成功執行。
EBX 寄存器包含程式傳回給shell的退出代碼值。可以使用 EBX寄存器的内容,按照彙程式設計式内的情況在shell腳本程式中生成不同的結果。
@b DONE@
生成可執行檔案:
運作可執行檔案:
用 gcc 編譯:
1,修改代碼如下:
1 2 3 4 5 | |
調試程式:(為了調試彙程式設計式,必須使用 -gstabs 參數編譯源檔案)
@a2@ 在彙程式設計式中使用 C庫函數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
@b1@ .asciz 指令
printf 函數使用多個輸入參數,這些參數取決于要顯示的變量。第一個參數是輸出字元串,帶有用于顯示變量:
1 2 | |
sfsdf
注意:這裡使用的是 .asciz 指令,而不是 .ascii 。printf 函數要求以空字元串結尾的作為輸出字元串。 .asciz 指令在定義的字元串末尾添加空字元。
@b2@ .lcomm 指令
printf函數要使用的下一個參數是将要包含 廠商ID字元串的緩沖區。因為不需要定義這個緩沖區的初始值,是以在 bss段中使用 .lcomm 指令把它聲明為 12 個位元組的緩沖區區域:
1 2 | |
@b3@ PUSHL指令
1 2 3 | |
為了把參數傳遞給 C 函數 printf,必須把參數壓入堆棧中。這就要使用 PUSHL 指令完成。
參數放入堆棧的順序和 printf 函數擷取它們的順序是相反的,是以緩沖區值被首先放入,然後是輸出字元串值。
完成這些操作後, 使用 CALL 指令調用 printf 函數。
@b4@ 程式退出
1 2 3 | |
ADDL 指令用于清空 為 printf 函數放入堆棧中的參數。
再通過 PUSHL 指令把 0 壓入堆棧中,
最後調用系統函數 exit,參數為 0,也就是exit(0);表示程式正常退出。
@b DONE@
連接配接C庫函數
1)在彙程式設計式中使用 C 庫函數時,必須把 C庫檔案連接配接到程式目标代碼中,如果C庫函數不可用,連接配接 器會發生如下錯誤:
在 linux 系統上,标準的 C 動态庫位于 libc.so.x 檔案中,其中 x 表示庫的版本。
這個庫檔案包含了标準 C 函數,包括 printf 和 exit。
在使用 gcc 時,這個檔案會被自動連接配接到 C 程式中。但是在彙程式設計式中,必須手動把它連接配接到程式目标代碼中以便 C 函數能夠操作。為了連接配接 libc.so 檔案,必須使用 GNU 連接配接器的 -l 參數:
這個時候,連接配接了标準C函數庫檔案的程式目标代碼沒有問題。可是當執行這個可執行檔案時,卻發生了錯誤。
原因:連接配接器能夠解析 C 函數,但是函數本身沒有包含到最終的可執行程式中,我們使用的是動态連結庫,連接配接器假設運作時能夠找到必須的庫檔案。顯然,本例子中并不是這樣。
為解決這個問題,還必須指定在運作時加載動态庫的程式:
@a DONE@