本文由zengl.com站长对
http://pan.baidu.com/share/link?shareid=3717576860&uk=940392313 汇编教程英文版相应章节进行翻译得来。
另外再附加一个英特尔英文手册的共享链接地址:
http://pan.baidu.com/share/link?shareid=2345340326&uk=940392313 (在某些例子中会用到)
本篇翻译对应汇编教程英文原著的70到81页
根据前面的内容,你应该对IA-32平台比较熟悉了,是时候接触和汇编开发相关的工具了。本文将对各种商业的,免费的开发工具进行介绍,尤其是GNU的开发工具,英文原著中所有的例子都是采用GNU的开发工具来进行汇编教学的。
The Development Tools (开发工具):
和其他行业一样,编程需要合适的开发工具。要创建一个很好的汇编语言开发环境,你必须根据你的需要和工作环境选择合适的工具,不像高级编程语言,你可以购买一个完整的开发环境,对于汇编语言,你往往需要自己将各种工具拼凑在一起构成一个汇编开发环境,你至少应该有以下几个工具:
此外,如果需要为高级编程语言创建汇编例程,那么你还需要有下面这些工具:
-
高级语言编译器
-
目标代码反汇编工具
-
用于优化的分析工具
下面对上面这些工具进行描述,分析它们在汇编开发环境中的作用和使用方法。
The assembler (汇编器):
要创建汇编程序,显然,你需要一些工具将汇编语言源代码转为处理器可以执行的指令代码。这就是汇编器的作用。
从第一章“什么是汇编语言”,可以知道汇编程序是和你做开发的底层硬件平台相关的。每个处理器家族都有自己的指令集。你所选择的汇编器就必须具备创建对应平台的指令集的能力。
前面的章节,我们提到过,汇编语言由三个部分组成:
-
Opcode mnemonics (操作码助记符)
-
Data sections (数据段)
-
Directives (伪操作)
不幸的是,每种汇编器对每个部分所使用的格式都不一样,使用一种汇编器编写的程序可能与另一种汇编器的写法完全不同。所以尽管汇编的基础原理是相通的,但是实现的方法可以是五花八门的。
不同汇编器之间最大的不同在于汇编器的伪操作符,不同汇编器的伪操作符都是唯一的,伪操作符用于指示汇编器如何更有效的组建程序的指令代码。有的汇编器的伪操作符并不多,而有的汇编器则有很多,这些伪操作符从定义程序的各种段到实现if-then结构或while循环结构都有很重要的作用。
有的汇编器内部包含了编辑器,可以对你编写的汇编源码进行代码高亮等处理,有的则是简单的命令行模式,需要自己使用额外的第三方编辑器,如unix系统下的vi编辑器,将源码写好后,再用命令行进行汇编输出目标码,这些都是根据自己的喜好来决定。下面介绍IA-32平台下常见的汇编器。
MASM (微软的汇编器):
MASM(全称:Microsoft Assembler ,微软的汇编器)可以说是英特尔平台上所有汇编器的鼻祖,顾名思义,它是由微软公司开发的。它从IBM兼容PC开始就已经在使用了,这个汇编器允许程序员在dos或windows环境下开发汇编程序。
由于MASM历史悠久,所以有很多关于该汇编器的教程,书籍和案例,许多都还是免费的。尽管微软不再单独发布MASM产品,而是将它整合到Visual Studio中,但是微软允许其他的公司或组织免费发布MASM 6.0的程式,所以只要你在网上搜索MASM 6.0,是可以下载到该程序的,它让你可以在命令行下将汇编源码转为指令码。
除了MASM,还有个MASM32,它是由Steve Hutchessen创建的。MASM32除了包含MASM汇编器外,它还包括了win32 API(windows下应用程序编程接口,C , C++程序经常使用这些接口函数来创建windows应用程序),所以有了MASM32,程序员就可以使用汇编程序来创建windows下的应用程序,例如某些GUI窗口应用等。MASM32的官方网址是
www.masm32.com
NASM (Netwide汇编器):
NASM(全称:Netwide Assembler ,Netwide汇编器),最初是unix环境下的商业汇编开发包。最近,开发者们将NASM作为unix和windows环境下的开源软件发布出去。它完全兼容所有的英特尔指令集,并且能生成unix ,16位MS-DOS及32位windows下的可执行文件格式。
和MASM类似,也有很多关于NASM的书籍和教程,NASM的下载地址是
http://nasm.sourceforge.net (译者注:该地址会跳转到
http://www.nasm.us/ ,因为nasm的官网已经从sourceforge搬迁到此站点!)
GAS (GNU汇编器):
自由软件组织的GNU项目已经开发了许多运行于unix操作系统环境下的开源软件,GNU汇编器,简称gas ,可以说是unix下最流行的跨平台汇编器了。
这里提到了跨平台,gas和其他的汇编器不一样,它除了可以产生当前程序所在的处理器指令代码外,只要给它一些额外的参数,它就可以产生许多不同处理器平台下的指令代码,例如可以在英特尔处理器平台下产生MIPS平台的指令代码,当然这些指令代码只能在MIPS平台下运行。在没指定平台参数的情况下,gas会自动检测当前的硬件平台,并产生合适的指令代码。
小提示:英文原著中所有的例子都是用的是GNU汇编器。因为这个汇编器除了非常优秀外,它还是GNU的C编译器用于将C和C++程序转为指令代码的工具,理解了如何使用gas进行汇编开发,你将会很容易在已存在的C和C++程序中嵌入汇编语言,这也是原著编写的目的之一。
HLA (高级汇编器):
HLA(全称:High Level Assembler,高级汇编器),是由Randall Hyde教授创建的。它用于创建Dos ,Windows 及 Linux操作系统下的英特尔指令集的应用程序。
HLA的主要目的是为了让刚学习汇编语言的程序员能够快速的了解和使用汇编语言。它提供了很多丰富的伪操作符,来帮助程序员从高级编程语言过渡到汇编语言(所以它的名字就叫做高级汇编器)。HLA的官网是
http://webster.cs.ucr.edu 这个网站除了HLA的信息外,还提供了很多其他的和汇编相关的信息,网站里面包含了很多其他汇编开发包的链接。
The linker (链接器):
高级编程语言的编译和链接步骤往往在一个单一的命令中就完成了,所谓链接,就是指将多个目标代码文件链接在一起成为一个可执行文件,因为程序中的函数和变量可能分布在不同的目标代码文件或库文件中,当一个目标代码文件里需要使用的某个函数或变量在其他的文件里时,链接器就会对这些函数和变量的地址进行解析,让其指向正确的代码位置。
然而,汇编程序开发时,汇编器却不会自动将目标代码链接成可执行文件,所以汇编程序开发,通常分为两步,第一步是使用汇编器将汇编源文件转为目标代码文件,第二步则是使用链接器将生成的目标代码文件和需要的库文件链接成一个可执行文件。
使用链接器时,需要注意的是,必须让链接器知道汇编程序开发所需的函数库文件和目标代码文件所在的路径,以及程序需要使用哪些函数库来解析程序中的函数,否则就会链接失败。
小提示:你必须选择和汇编器相匹配的链接器,并确保函数库文件和其他的库文件是相兼容的,以及生成的输出文件格式是和目标平台相关的。
The debugger (调试器):
对于简短的程序,你可能不需要使用调试器,但是如果你写了上万行的代码,想从中找出程序逻辑上的BUG时,就需要一个好的调试器了。
和汇编器类似,调试器也是和程序运行所在的操作系统及目标硬件平台相关的。调试器必须知道硬件平台的指令集,并且知道操作系统中寄存器和内存的操作方法。
大多数调试器都为程序员提供了四个基础的功能:
-
让程序运行在一个可控制的环境下,并可以根据需要指定任意的运行时参数
-
可以在程序运行时暂停或停止程序的执行
-
可以检测一些需要的数据,例如某内存位置或寄存器的值
-
可以在程序运行时动态的改变某些元素的值,以方便BUG的调试
调试器会将程序放在内部的“沙盘”中运行,沙盘就是一个虚拟的执行环境,在该环境下,程序可以访问内存单元,寄存器及I/O设备,但是所有这些访问都是在调试器的控制下进行的。调试器可以控制程序如何访问这些元素,以及显示访问这些元素时的相关信息。
在程序运行的任何时刻,调试器都可以暂停或停止程序的执行,并且可以显示出程序停止时所在的源代码位置和相关信息,要完成这个操作,调试器必须知道程序源代码以及生成的指令代码和源代码行列号的对应关系,这就需要将额外的调试信息编译到可执行文件中,在编译或汇编过程中通过使用特定的命令行参数就可以达到这个目的。
另外有了调试信息,调试器还可以知道所需内存位置和寄存器的值,这对于程序员调试BUG来说,是很重要的功能。
最后,调试器还可以让程序员在运行时修改程序中的某些数据的值。然后查看这些修改对程序产生的影响,这也是一个很有价值的功能,它让程序员不用修改源代码,重新编译,就可以直接调整某些数据的值,从而节省时间。
The compiler (编译器):
如果你只是想进行简单的汇编开发,那么就不需要高级语言的编译器,但是对于一个大点的项目,只用汇编语言进行开发将导致维护上的困难,所以很多情况下,是使用C或C++等高级编程语言来编写程序的主体部分,而需要优化的部分则使用汇编来编写,这种情况下,你就需要一个高级语言的编译器。
编译器的工作是将高级语言转为处理器可以执行的指令代码,但是,大多数编译器并不是直接就将源代码转为了指令代码,通常还存在一个中间步骤,即编译器会先将高级语言的源代码转为汇编语言代码,接着再使用汇编器将汇编代码转为指令代码。许多编译器都内置了汇编器程序,当然并不是所有的都是这样。
GNU的编译器在将源代码转为汇编语言后,就使用GNU汇编器将汇编语言代码转为对应的指令代码。你可以通过参数来控制它只生成汇编语言,然后在需要优化的地方进行修改,最后再使用汇编器生成新的指令代码。
The object code disassembler (目标代码反汇编工具):
在逆向工程中,你可以使用反汇编工具来查看可执行文件或目标代码文件的指令代码,有的反汇编工具还可以将指令代码转为适合阅读的汇编语法格式,通过查看这些指令代码,你就可以知道哪些地方的指令没有优化好,然后可以修改替换原程序中的指令代码,以提高性能。
The profiler (分析器):
分析器可以检测出程序的函数过程的处理器执行时间,通过分析出来的执行时间就可以知道哪些过程需要调整和优化。
优化一个程序,除了可以优化程序算法以减少处理器执行时间外,还可以使用一些处理器里的高级指令(为了提高兼容性,这些指令通常不会被编译器生成),从而在硬件层次提升性能。
The GNU Assembler (GNU汇编器):
GNU汇编器(简称:gas),是unix环境下最流行的汇编器,它支持如下所示的不同的硬件平台:
-
VAX
-
AMD 29K
-
Hitachi H8/300
-
Intel 80960
-
M680x0
-
SPARC
-
Intel 80x86
-
Z8000
-
MIPS
英文原著中所有的例子都是使用的gas,许多unix系统在安装的系统程序中都包含有gas ,大多数Linux发行版也将gas包含在默认的软件开发包中。
下面就介绍如何下载和安装gas ,同时了解如何使用gas进行汇编程序开发。
Installing the assembler (安装GNU汇编器):
不像其他的软件开发包,GNU汇编器并不是以单独的软件包的形式进行发布的,而是被捆绑在GNU的binutils软件开发包中。下表显示了binutils软件开发包中包含的子程序(对应2.15的版本):
Package |
Description |
addr2line |
Converts addresses into filenames and line numbers
将地址转为文件名和行号信息 |
ar |
Creates, modifies, and extracts file archives
创建,修改,提取库文件(通常以.a为扩展名) |
as |
Assembles assembly language code into object code
将汇编语言转为目标代码 |
c++filt |
Filter to demangle C++ symbols
转换为C++的可读风格 |
gprof |
Program to display program profiling information
GNU的分析器,用于显示程序的处理器执行时间等分析信息 |
ld |
Linker to convert object code files into executable files
链接器,将目标代码文件转为可执行文件 |
nlmconv |
Converts object code into Netware Loadable Module format
将目标代码转为Netware网络操作系统的可加载模块格式 |
nm |
Lists symbols from object files
列出目标文件里的符号信息 |
objcopy |
Copies and translates object files
把一种目标文件中的内容复制到另一种类型的目标文件中,例如可以将图片等二进制文件链接到可执行文件内部等 |
objdump |
Displays information from object files
显示目标文件里的信息和内容 |
ranlib |
Generates an index to the contents of an archive file
生成库文件里内容的索引 |
readelf |
Displays information from an object file in ELF format
显示ELF格式的目标文件里的信息 |
size |
Lists the section sizes of an object or archive file
列出目标文件或库文件里各个段的大小信息 |
strings |
Displays printable strings found in object files
显示出目标文件中找到的可打印字符串信息 |
strip |
Discards symbols
丢弃符号信息 |
windres |
Compiles Microsoft Windows resource files
编译windows的资源文件 |
|
大部分支持软件开发的linux发行版中都包含了binutils软件包(尤其是在包含了GNU的C编译器的发行版中)。你可以使用你的linux发行版对应的包管理器来检查是否安装了binutils ,原著作者使用的是Mandrake发行版的linux系统,该系统使用的是RedHat包管理器(即RPM)来安装软件包,在RPM下可以使用下面命令来进行检测:
$ rpm -qa | grep binutils
libbinutils2-2.10.1.0.2-4mdk
binutils-2.10.1.0.2-4mdk
$ |
上面的命令显示出当前系统中已经安装了2.10版本的binutils软件包,其中
libbinutils2是binutils软件包所依赖的底层库。
如果你的系统使用的是Debian的包管理器,那么你可以使用dpkg命令来进行检查:
$ dpkg -l | grep binutil
ii binutils 2.14.90.0.7-3 The GNU assembler, linker and binary utilities
ii binutils-doc 2.14.90.0.7-3 Documentation for the GNU assembler, linker
$ |
上面的输出表示当前系统中已经安装了2.14版本的binutils软件包。
小提示:如果你的Linux系统上已经安装和使用了binutils软件包,那么强烈建议不要随意改动这些binutils软件包,因为该软件包有许多用于编译操作系统组件的底层库文件,如果这些库文件被改动了,那么很可能会对你的系统产生非常严重的影响。
如果你的系统中还没包含binutils软件包,那么你可以进入
http://sources.redhat.com/binutils 或者使用FTP地址:
ftp://ftp.gnu.org/gnu/binutils/ ,在该FTP下,可以看到很多版本的binutils软件包,在英文原著编写的时候最新版本是2.15的版本,到了译者翻译的时候,最新版本是2.23的版本,不过这里仍然以英文原著的2.15版本来进行说明。
从FTP中下载binutils-2.15.tar.gz(译者注:如果没看到这个版本,就选择一个别的版本进行下载)。使用如下的命令进行解压:
tar –zxvf binutils-2.15.tar.gz |
这个命令会在当前目录下创建一个binutils-2.15的工作目录,进入该工作目录,然后使用下面的命令来编译binutils软件包:
configure命令用于检测当前的系统环境,以确保当前系统已经具备了编译binutils所需的依赖组件,在make编译完后,就可以使用make install命令将软件包安装到常规的位置以供他人使用。
Using the assembler (使用汇编器):
GNU汇编器是一个基于命令行的程序。因此需要在命令行提示符下运行它,还可以给它添加一些合适的命令行参数,不过需要注意的是,尽管这个汇编器被称作gas,不过它的可执行程序文件名却是as 。
as程序可用的命令行参数是依赖于操作系统所在的硬件平台的。其中,所有硬件平台下通用的命令行参数显示如下:
as [-a[cdhlns][=file]] [-D] [--defsym sym=val]
[-f] [--gstabs] [--gstabs+] [--gdwarf2] [--help]
[-I dir] [-J] [-K] [-L]
[--listing-lhs-width=NUM] [--listing-lhs-width2=NUM]
[--listing-rhs-width=NUM] [--listing-cont-lines=NUM]
[--keep-locals] [-o objfile] [-R] [--statistics] [-v]
[-version] [--version] [-W] [--warn] [--fatal-warnings]
[-w] [-x] [-Z] [--target-help] [target-options]
[--|files ...] |
下表对这些命令行参数进行了解释说明:
Parameter |
Description |
-a |
Specifies which listings to include in the output
指定输出时要列举显示的信息 |
-D |
Included for backward compatibility, but ignored
被忽略的参数,主要用于向后兼容的需要 |
--defsym sym=value |
Define the symbol sym to be value before assembling
the input file
定义一个值为value的sym符号(sym和value此处只是个例子,
value必须是整数常量) |
-f |
"fast" - skip whitespace and comment preprocessing
快速汇编,跳过空白和注释的预处理 |
--gstabs |
Includes debugging information for each source code line
包含每行源代码对应的调试信息 |
--gstabs+ |
Includes special gdb debugging information
包含特殊的gdb调试信息 |
-I |
Specify directories to search for include files
指定搜索include包含文件的目录 |
-J |
Do not warn about signed overflows
不要输出符号溢出的警告 |
-K |
Included for backward compatibility, but ignored
和-D参数一样,被忽略的参数,主要用于向后兼容的需要 |
-L |
Keep local symbols in the symbol table
在符号表中保留局部符号信息 |
--listing-lhs-width |
Set the maximum width of the output data column
设置输出数据最大的列宽,即十六进制显示区域,
该区域在输出列的左侧 |
--listing-lhs-width2 |
Set the maximum width of the output data column
for continual lines
设置输出数据额外行所对应的最大的列宽,
即十六进制显示区域中额外的行 |
--listing-rhs-width |
Set the maximum width of input source lines
输入源码行的最大列宽,即源代码的字符显示区域,
该区域在十六进制显示区域的旁边,即输出列右侧 |
--listing-cont-lines |
Set the maximum number of lines printed in a
listing for a single line of input
设置在单行源码输入的情况下,
十六进制显示区域最多显示多少行 |
-o |
Specify name of the output object file
指定输出的目标代码文件的名称 |
-R |
Fold the data section into the text section
将数据段附加到代码段 |
--statistics |
Display the maximum space and total time
used by the assembly
显示出汇编过程所占用的最大内存资源和总时间 |
-v |
Display the version number of as
显示出as的版本信息 |
-W |
Do not display warning messages
不要显示警告消息 |
-- |
Use standard input for source files
使用标准输入的源代码进行汇编 |
|
例如将test.s汇编程序源代码转成test.o目标代码文件,可以使用下面的命令:
这条命令会创建test.o的目标代码文件,该文件中包含了汇编程序对应的指令代码,如果在汇编代码中有什么错误的话,汇编器就会将错误的原因和对应的源代码的位置给显示出来。如下所示:
$ as -o test.o test.s
test.s: Assembler messages:
test.s:16: Error: no such instruction: `mpvl $4,%eax’
$ |
上面就是汇编器的错误提示信息,该信息显示出在test.s的第16行,出现了无效的指令。
A word about opcode syntax (汇编语法需要注意的地方):
GNU汇编器使用的是AT&T的汇编语法格式,AT&T的汇编语法源于AT&T贝尔实验室,该实验室也是unix系统诞生的地方。AT&T的汇编语法就是起源于实现unix系统的处理器芯片所使用的指令操作码语法。尽管很多处理器制造商使用这种语法格式,但是不幸的是,英特尔却选择了不同的指令操作码语法格式。
正因如此,使用gas创建英特尔平台下的汇编程序可能会比较棘手,多数英特尔汇编开发文档使用的是英特尔的语法,而多数unix系统的汇编开发文档又用的是AT&T的语法,这是让gas程序员感到困惑的地方。
这两种语法格式最大的不同主要集中在以下几个方面:
-
AT&T语法中立即数必须在前面添加$符号,而英特尔的则不需要,所以当要表示十进制数4时,AT&T对应写法是$4 ,而英特尔语法则直接使用4即可
-
AT&T语法在引用寄存器时必须在前面添加%符号,而英特尔则不需要,例如引用eax寄存器在AT&T中写法为%eax ,而英特尔语法直接使用eax即可
-
AT&T语法中源操作数和目标操作数的顺序和英特尔语法的顺序刚好相反,例如将十进制数4赋值给EAX寄存器,AT&T的写法是 movl $4,%eax ,而英特尔的写法则是 mov eax , 4
-
AT&T语法通过在助记符后面使用特殊的字符来指定操作数的大小,而英特尔则将大小声明到指定的操作数上,例如AT&T语句 movl $test , %eax 等效于英特尔汇编语句 mov eax , dword ptr test
-
长函数调用和长跳转指令的语法也不相同,例如AT&T语句:ljmp $section, $offset ,对应的英特尔语句是 jmp section:offset
这些不同之处让两种语法格式相互切换变得有些困难,当然如果你只关注某一种汇编语法的话,就不会有这方面的困惑。如果你使用AT&T语法来学习汇编语言的话,你将很容易在大多数unix系统下开发汇编程序。如果你想在unix和windows平台下做跨平台开发的话,那么你可能就需要考虑使用英特尔语法来进行汇编开发。
GNU汇编器也提供了一种方法可以在该汇编器中使用英特尔语法来代替AT&T语法,不过在英文原著编写的时候,这种方法还不完善。通过在汇编程序中使用.intel_syntax的伪操作符就可以告诉as汇编器使用英特尔语法来解析汇编代码中的指令助记符,但是这种方法是有限制的,例如引用寄存器时,前面还是必须添加百分号,就和AT&T里的一样,所以这种方法并不完善。(至于译者翻译的时候,这种状况有没有改变,译者也不清楚)
英文原著中所有汇编程序的例子都是使用的AT&T的语法。
限于篇幅,先写到这里,欲知后事如何,且听下回分解。。。
OK,到这里,休息,休息一下 o(∩_∩)o~~