?? linux 匯編語言開發指南 - 21ic中國電子網.htm
字號:
<P align=center>
<TABLE cellSpacing=0 borderColorDark=#d9d9d9 cellPadding=2
borderColorLight=#000000 border=1>
<TBODY>
<TR>
<TD align=middle width=200 bgColor=#d9d9d9><B>AT&T 格式</B></TD>
<TD align=middle width=200 bgColor=#d9d9d9><B>Intel 格式</B></TD></TR>
<TR>
<TD>ljump $section, $offset</TD>
<TD width=200>jmp far section:offset</TD></TR>
<TR>
<TD>lcall $section, $offset</TD>
<TD width=200>call far section:offset</TD></TR></TBODY></TABLE></P>
<P>與之相應的遠程返回指令則為:</P>
<P align=center>
<TABLE cellSpacing=0 borderColorDark=#d9d9d9 cellPadding=2
borderColorLight=#000000 border=1>
<TBODY>
<TR>
<TD align=middle width=200 bgColor=#d9d9d9><B>AT&T 格式</B></TD>
<TD align=middle width=200 bgColor=#d9d9d9><B>Intel 格式</B></TD></TR>
<TR>
<TD>lret $stack_adjust</TD>
<TD width=200>ret far stack_adjust</TD></TR></TBODY></TABLE></P>
<LI>
<P>在 AT&T 匯編格式中,內存操作數的尋址方式是</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
section:disp(base, index, scale)
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>而在 Intel 匯編格式中,內存操作數的尋址方式為:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
section:[base + index*scale + disp]
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>由于 Linux 工作在保護模式下,用的是 32 位線性地址,所以在計算地址時不用考慮段基址和偏移量,而是采用如下的地址計算方法:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
disp + base + index * scale
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>下面是一些內存操作數的例子:</P>
<P align=center>
<TABLE cellSpacing=0 borderColorDark=#d9d9d9 cellPadding=2
borderColorLight=#000000 border=1>
<TBODY>
<TR>
<TD align=middle width=200 bgColor=#d9d9d9><B>AT&T 格式</B></TD>
<TD align=middle width=200 bgColor=#d9d9d9><B>Intel 格式</B></TD></TR>
<TR>
<TD>movl -4(%ebp), %eax</TD>
<TD width=200>mov eax, [ebp - 4]</TD></TR>
<TR>
<TD>movl array(, %eax, 4), %eax</TD>
<TD width=200>mov eax, [eax*4 + array]</TD></TR>
<TR>
<TD>movw array(%ebx, %eax, 4), %cx</TD>
<TD width=200>mov cx, [ebx + 4*eax + array]</TD></TR>
<TR>
<TD>movb $4, %fs:(%eax)</TD>
<TD width=200>mov fs:eax, 4</TD></TR></TBODY></TABLE></P></LI></OL>
<P></P>
<P><A name=2><SPAN class=atitle2>三、Hello World!</SPAN></A></P>
<P>真不知道打破這個傳統會帶來什么樣的后果,但既然所有程序設計語言的第一個例子都是在屏幕上打印一個字符串 "Hello
World!",那我們也以這種方式來開始介紹 Linux 下的匯編語言程序設計。</P>
<P>在 Linux 操作系統中,你有很多辦法可以實現在屏幕上顯示一個字符串,但最簡潔的方式是使用 Linux
內核提供的系統調用。使用這種方法最大的好處是可以直接和操作系統的內核進行通訊,不需要鏈接諸如 libc 這樣的函數庫,也不需要使用 ELF
解釋器,因而代碼尺寸小且執行速度快。</P>
<P>Linux 是一個運行在保護模式下的 32 位操作系統,采用 flat memory 模式,目前最常用到的是 ELF 格式的二進制代碼。一個
ELF 格式的可執行程序通常劃分為如下幾個部分:.text、.data 和 .bss,其中 .text 是只讀的代碼區,.data
是可讀可寫的數據區,而 .bss 則是可讀可寫且沒有初始化的數據區。代碼區和數據區在 ELF 中統稱為
section,根據實際需要你可以使用其它標準的 section,也可以添加自定義 section,但一個 ELF 可執行程序至少應該有一個
.text 部分。下面給出我們的第一個匯編程序,用的是 AT&T 匯編語言格式:</P>
<P>例1. AT&T 格式</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
#hello.s
.data # 數據段聲明
msg : .string "Hello, world!\\n" # 要輸出的字符串
len = . - msg # 字串長度
.text # 代碼段聲明
.global _start # 指定入口函數
_start: # 在屏幕上顯示一個字符串
movl $len, %edx # 參數三:字符串長度
movl $msg, %ecx # 參數二:要顯示的字符串
movl $1, %ebx # 參數一:文件描述符(stdout)
movl $4, %eax # 系統調用號(sys_write)
int $0x80 # 調用內核功能
# 退出程序
movl $0,%ebx # 參數一:退出代碼
movl $1,%eax # 系統調用號(sys_exit)
int $0x80 # 調用內核功能
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>初次接觸到 AT&T 格式的匯編代碼時,很多程序員都認為太晦澀難懂了,沒有關系,在 Linux 平臺上你同樣可以使用 Intel
格式來編寫匯編程序:</P>
<P>例2. Intel 格式</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
; hello.asm
section .data ; 數據段聲明
msg db "Hello, world!", 0xA ; 要輸出的字符串
len equ $ - msg ; 字串長度
section .text ; 代碼段聲明
global _start ; 指定入口函數
_start: ; 在屏幕上顯示一個字符串
mov edx, len ; 參數三:字符串長度
mov ecx, msg ; 參數二:要顯示的字符串
mov ebx, 1 ; 參數一:文件描述符(stdout)
mov eax, 4 ; 系統調用號(sys_write)
int 0x80 ; 調用內核功能
; 退出程序
mov ebx, 0 ; 參數一:退出代碼
mov eax, 1 ; 系統調用號(sys_exit)
int 0x80 ; 調用內核功能
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>上面兩個匯編程序采用的語法雖然完全不同,但功能卻都是調用 Linux 內核提供的 sys_write 來顯示一個字符串,然后再調用
sys_exit 退出程序。在 Linux 內核源文件 include/asm-i386/unistd.h 中,可以找到所有系統調用的定義。</P>
<P><A name=3><SPAN class=atitle2>四、Linux 匯編工具</SPAN></A></P>
<P>Linux 平臺下的匯編工具雖然種類很多,但同 DOS/Windows 一樣,最基本的仍然是匯編器、連接器和調試器。</P>
<P><B>1.匯編器</B></P>
<P>匯編器(assembler)的作用是將用匯編語言編寫的源程序轉換成二進制形式的目標代碼。Linux 平臺的標準匯編器是 GAS,它是 GCC
所依賴的后臺匯編工具,通常包含在 binutils 軟件包中。GAS 使用標準的 AT&T 匯編語法,可以用來匯編用 AT&T
格式編寫的程序:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
[xiaowp@gary code]$ as -o hello.o hello.s
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>Linux 平臺上另一個經常用到的匯編器是 NASM,它提供了很好的宏指令功能,并能夠支持相當多的目標代碼格式,包括
bin、a.out、coff、elf、rdf 等。NASM 采用的是人工編寫的語法分析器,因而執行速度要比 GAS 快很多,更重要的是它使用的是
Intel 匯編語法,可以用來編譯用 Intel 語法格式編寫的匯編程序:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
[xiaowp@gary code]$ nasm -f elf hello.asm
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><B>2.鏈接器</B></P>
<P>由匯編器產生的目標代碼是不能直接在計算機上運行的,它必須經過鏈接器的處理才能生成可執行代碼。鏈接器通常用來將多個目標代碼連接成一個可執行代碼,這樣可以先將整個程序分成幾個模塊來單獨開發,然后才將它們組合(鏈接)成一個應用程序。
Linux 使用 ld 作為標準的鏈接程序,它同樣也包含在 binutils 軟件包中。匯編程序在成功通過 GAS 或 NASM
的編譯并生成目標代碼后,就可以使用 ld 將其鏈接成可執行程序了:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
[xiaowp@gary code]$ ld -s -o hello hello.o
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><B>3.調試器</B></P>
<P>有人說程序不是編出來而是調出來的,足見調試在軟件開發中的重要作用,在用匯編語言編寫程序時尤其如此。Linux 下調試匯編代碼既可以用
GDB、DDD 這類通用的調試器,也可以使用專門用來調試匯編代碼的 ALD(Assembly Language Debugger)。</P>
<P>從調試的角度來看,使用 GAS 的好處是可以在生成的目標代碼中包含符號表(symbol table),這樣就可以使用 GDB 和 DDD
來進行源碼級的調試了。要在生成的可執行程序中包含符號表,可以采用下面的方式進行編譯和鏈接:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
[xiaowp@gary code]$ as --gstabs -o hello.o hello.s
[xiaowp@gary code]$ ld -o hello hello.o
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>執行 as 命令時帶上參數 --gstabs 可以告訴匯編器在生成的目標代碼中加上符號表,同時需要注意的是,在用 ld 命令進行鏈接時不要加上
-s 參數,否則目標代碼中的符號表在鏈接時將被刪去。</P>
<P>在 GDB 和 DDD 中調試匯編代碼和調試 C
語言代碼是一樣的,你可以通過設置斷點來中斷程序的運行,查看變量和寄存器的當前值,并可以對代碼進行單步跟蹤。圖1 是在 DDD
中調試匯編代碼時的情景:</P>
<P align=center><IMG onmousewheel="return bbimg(this)"
onclick=ImgClick(this)
src="Linux 匯編語言開發指南 - 21IC中國電子網.files/050109014193841.jpg"
resized="0"><BR>圖1 用 DDD 中調試匯編程序</P>
<P>匯編程序員通常面對的都是一些比較苛刻的軟硬件環境,短小精悍的ALD可能更能符合實際的需要,因此下面主要介紹一下如何用ALD來調試匯編程序。首先在命令行方式下執行ald命令來啟動調試器,該命令的參數是將要被調試的可執行程序:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
[xiaowp@gary doc]$ ald hello
Assembly Language Debugger 0.1.3
Copyright (C) 2000-2002 Patrick Alken
hello: ELF Intel 80386 (32 bit), LSB, Executable, Version 1 (current)
Loading debugging symbols...(15 symbols loaded)
ald>
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>當 ALD 的提示符出現之后,用 disassemble 命令對代碼段進行反匯編:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
ald> disassemble -s .text
Disassembling section .text (0x08048074 - 0x08048096)
08048074 BA0F000000 mov edx, 0xf
08048079 B998900408 mov ecx, 0x8049098
0804807E BB01000000 mov ebx, 0x1
08048083 B804000000 mov eax, 0x4
08048088 CD80 int 0x80
0804808A BB00000000 mov ebx, 0x0
0804808F B801000000 mov eax, 0x1
08048094 CD80 int 0x80
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>上述輸出信息的第一列是指令對應的地址碼,利用它可以設置在程序執行時的斷點:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
ald> break 0x08048088
Breakpoint 1 set for 0x08048088
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>斷點設置好后,使用 run 命令開始執行程序。ALD 在遇到斷點時將自動暫停程序的運行,同時會顯示所有寄存器的當前值:</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc
border=1><TBODY>
<TR>
<TD><PRE><CODE>
ald> run
Starting program: hello
Breakpoint 1 encountered at 0x08048088
eax = 0x00000004 ebx = 0x00000001 ecx = 0x08049098 edx = 0x0000000F
esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x0000002B es = 0x0000002B fs = 0x00000000 gs = 0x00000000
ss = 0x0000002B cs = 0x00000023 eip = 0x08048088 eflags = 0x00000246
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -