在Linux下使用汇编语言(一)
by Wang Hui([email protected])
我开始学习在Linux使用汇编语言了。我以前从来没有在Linux下使用过汇编语言。我今天看到了一份文档,是Linux Assembly HOWTO,我看了看,决定学习学习。下面是我的学习笔记。让我一起学习吧。
1. 你需要汇编语言吗?
汇编语言的优点和缺点
汇编语言可以很直接的表达比较低层次的:
· 你可以通过汇编代码访问与机器硬件直接相关的存储器或者I/O口。
· 你可以在一些关键的代码区准确的控制某些代码的行文,避免其他方法带来的多线程共同访问或者硬件设备共享引起的死锁。
· 你可以通过使用汇编打破通常的编译器编译的结果,例如你可以根据自己的考虑优化一些东西。
· 我们可以在代码分片之间建立一些接口。
· 我们可以通过一些汇编代码使处理器进入不寻常的编程模式。例如16 bit mode to interface startup, firmware, or legacy code on Intel PCs.
· 可以优化一些代码,提高运行速度。
· 可以为特定的应该编写优化代码。
· 可以为某语言的编译器编写代码。
汇编语言是一种层次非常低的语言(仅仅高于手动编写二进制机器指令码),这就意味着:
· 开始写起来会很长而且单调
· 很容易出现bug
· 程序中的bug非常难查找
· 写出来的代码非常难懂,而且不好维护和修改
· 而且结果是不能到其他体系结构的平台上运行。
· 代码只能针对某特特定体系结构的特定型号的处理器优化。例如,在Intel兼容系列的CPU中,每一款CPU都有特殊的设计,变量也不一样,都有特定的优化考虑。CPU的时间已经有很多了,例如:Intel 386, 486, Pentium, PPro, PII, PIII, PIV; Cyrix 5x86, 6x86, M2; AMD K5, K6 (K6-2, K6-III), K7 (Athlon, Duron). 而且新的设计还不断地出现。
· 编写汇编代码时候,我们会花费很多的时间在一些细节事情上,而不能专注一些小的或者大的算法设计,而算法的优化能更快的提高程序运行的速度。例如,你可以用汇编加快一些数组的访问和管理,但是总体上讲,使用hash表会更大程度上加快访问速度。
· 一个算法上的小的改动,可能需要我们全部重新写一段很长的代码。
"compilers make it a lot easier to use complex data structures,and compilers don get bored halfway through and generate reliably pretty good code."
2. Linux 和汇编
你可能已经注意到了,在linux下开发程序,通常不需要使用汇编语言。与DOS不一样,你不需要使用汇编来编写linux的驱动程序(当然,如果你非得用汇编写,也可以)。而且现在的编译器有很多优化器,可以针对不多的CPU优化尼的高级语言写的代码,例如C代码。当然既然我们在讨论这个问题,而且你都在阅读本文了,luster知道你肯定有自己的理由去使用汇编而不是C或者C++了。:)
你可能需要使用汇编,或者你不得不使用汇编。简单的说,使用汇编的理由就是精简代码和libc无关性。还有一个理由就是,希望像一个以前的疯狂的hacker那样,这些20多岁的hacker的习惯就是用汇编写任何代码。
然而,假如你要移植linux到某些嵌入式硬件环境下,你需要减少系统的大小,你需要将很多基本的系统精简到很小的空间。其中的一个可行的方法就是,重新用汇编写部分(或者全部?)代码。例如,用汇编写的简单的http服务器程序可能少于600 bytes,这样呢可以把建立一个webserver(包括一个内核和一个httpd),大小只有400KB或者更小。是不是很爽?:)
今天就学习到这里。下面就让我们真正开始吧。
3. 汇编器
gcc内嵌编译器
GNU C/C++编译器(GCC)是一个32-bit的编译器,是GNU计划的核心,能非常好的支持x86体系结构,它支持在C程序里面嵌入汇编代码,这样看来,寄存器可以自己指定也可以留给gcc来分配。GCC可以在多个平台下使用,除了linux,还有*BSD, VSTa, OS/2, *DOS, Win*等等。
可以在GNU FTP站点下载GCC。链接是:
ftp://prep.ai.mit.edu/pub/gnu/gcc/
现在GCC已经分裂成两个分支(GCC 2.8和EGCS),但是它们又重新合并了起来,GCC的主页位于:http://gcc.gnu.org.
因为内核头文件中定义的一些汇编例程函数都是extern inline函数,所以GCC必须要加-O(或者-O2,-O3等)编译标志,这样才能使得这些函数可以使用。否则,如果不加-O标志,代码也可以被编译,但是可能link会不正确。
嵌入汇编语言可以通过“-fno-asm”来被禁止。不过通常来说,比较好的编译标志是如下的,这对于大多数的x86平台都是很不错的选择:
gcc -O2 -fomit-frame-pointer -W -Wall
-O2在大部分情况下都是很好的代码优化级别。事实上,使用优化后会带来更多的编译时间,也会产生大一些地代码,而在速度上仅仅有一点点提高。
-fomit-frame-pointer允许产生的代码中跳过一些错误的frame pointer maintenance,这样可以使得代码小一些而且快一些,而且会释放一些寄存器来获得更进一步的优化。
-W –Wall使得我们可以获得很多有用的warning,这对于我们减少一些愚蠢的编码错误是非常有用的。
我们还可以加一些编译参数来指明目标CPU的类型,这样就允许GCC来产生一些针对特定CPU的优化代码。在编译内核中使用指明CPU类型的参数是非常有用的。具体GCC支持什么类型CPU优化,我们可以参考所使用的GCC的文档。
GCC对宏的支持
GCC允许而且要求你指明你的嵌入汇编代码使用的寄存器,这样编译器的优化器才能知道如何优化。因此,嵌入汇编代码通常是一些patterns,而不是真正的代码(exact code)。因此,通常我们把汇编代码放到CPP的宏定义里面,或者定义成inline函数,这样就可以方便的就像使用C函数或宏一样使用这些代码。Inline函数和宏非常相似,不过inline函数比较起后者,使用起来更清晰。
GAS
GAS是GNU的汇编器,GCC实际上也依赖它。我们可以在任何能下载GCC的地方找到这个实用工具,它通常位于一个名叫binutils的包里面。下面的URL给出了最新的版本:
ftp://ftp.varesearch.com/pub/support/hjl/binutils/.
GAS是一个支持32-bit的unix环境下的汇编编译器,它使用标准的AT&T语法,它使用标准的m68k编译器的语法,这是UNIX world的标准。这个语法和Intel的语法不一样,如果你适应了这种语法,那么你就会发现它也是很好用的。下面是一些GAS语法的主要特点:
· 所有的寄存器的名字都是以“%”最为前缀,所以可能的一些寄存器是%eax,%dl等等,而不是简单的eax,dl等。这样的话可以使得C语言中方便的嵌入一些汇编代码而不会有变量名字方面冲突的担心。
· 操作数的顺序也是相反的。例如要把edx寄存器的内容拷贝到eax寄存器里面的话,GAS的语法是:mov %edx,%eax;而Intel平台下的汇编语句是:mov eax,edx。
· 操作数的尺寸作为一个后缀加在操作名的后面。B表示(8-bit)byte,w表示(16-bit) word,l表示(32-bit) long。例如,上面例子中正确的语法应该是:movl %edx,%eax。不过,GAS并不需要严格的AT&T语法,所以这个后缀可可以省略的,这时候编译器就自己根据操作数判断或者置为缺省的位宽32-bit。
· 立即数需要一个“$”前缀符号,例如:addl $5,%eax。
· 如果没有操作数前缀,则表示它是内存地址,例如:movl $foo,%eax,把变量foo的地址送到了%eax寄存器。但是“movl foo,%eax”则把foo变量的内容送到eax寄存器中。
已经有一些工具可以帮助你把你的使用Intel汇编语法的源程序转换成AT&T语法,或者做相反的转换。
不过,已经有的好消息是Binutils 2.10开始,GAS已经可以支持Intel语法了。
16-bit mode
Binutils (2.9.1.0.25+) 可以全面支持i386PC体系下的16-bit模式(存储器和寻址)。使用“.code16”和“.code32”来切换汇编模式。
还有很多的编译器,我们可以选择,明天再学习把。
昨天晚上美国遭到恐怖袭击,去看电视直播去了。拜拜。今天到此。
发布人:netbull 来自:LinuxAid