《Linux汇编语言开发指南简介》由会员分享,可在线阅读,更多相关《Linux汇编语言开发指南简介(86页珍藏版)》请在金锄头文库上搜索。
1、Linux汇编语言开发指南汇编语言开发指南 一、简介一、简介 作为最基本的编程语言之一,汇编语言虽然应用的作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但重要性却勿庸置疑,因为它能够完成许多范围不算很广,但重要性却勿庸置疑,因为它能够完成许多其它语言所无法完成的功能。就拿其它语言所无法完成的功能。就拿 Linux Linux 内核来讲,虽然内核来讲,虽然绝大部分代码是用绝大部分代码是用 C C 语言编写的,但仍然不可避免地在某语言编写的,但仍然不可避免地在某些关键地方使用了汇编代码,其中主要是在些关键地方使用了汇编代码,其中主要是在 Linux Linux 的启动的启动部分。由于这
2、部分代码与硬件的关系非常密切,即使是部分。由于这部分代码与硬件的关系非常密切,即使是 C C 语言也会有些力不从心,而汇编语言则能够很好扬长避短,语言也会有些力不从心,而汇编语言则能够很好扬长避短,最大限度地发挥硬件的性能。最大限度地发挥硬件的性能。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介大多数情况下大多数情况下 Linux Linux 程序员不需要使用汇编语言,因为即程序员不需要使用汇编语言,因为即便是硬件驱动这样的底层程序在便是硬件驱动这样的底层程序在 Linux Linux 操作系统中也可以操作系统中也可以用完全用用完全用 C C 语言来实现,再加上语言来实现,再加
3、上 GCC GCC 这一优秀的编译器这一优秀的编译器目前已经能够对最终生成的代码进行很好的优化,的确有目前已经能够对最终生成的代码进行很好的优化,的确有足够的理由让我们可以暂时将汇编语言抛在一边了。但实足够的理由让我们可以暂时将汇编语言抛在一边了。但实现情况是现情况是 Linux Linux 程序员有时还是需要使用汇编,或者不得程序员有时还是需要使用汇编,或者不得不使用汇编,理由很简单:精简、高效和不使用汇编,理由很简单:精简、高效和 libc libc 无关性。无关性。假设要移植假设要移植 Linux Linux 到某一特定的嵌入式硬件环境下,首先到某一特定的嵌入式硬件环境下,首先必然面临如
4、何减少系统大小、提高执行效率等问题,此时必然面临如何减少系统大小、提高执行效率等问题,此时或许只有汇编语言能帮上忙了。或许只有汇编语言能帮上忙了。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介汇编语言直接同计算机的底层软件甚至硬件进行交互,汇编语言直接同计算机的底层软件甚至硬件进行交互,它具有如下一些优点:它具有如下一些优点: 能够直接访问与硬件相关的存储器或能够直接访问与硬件相关的存储器或 I/O I/O 端口;端口; 能够不受编译器的限制,对生成的二进制代码进行完全能够不受编译器的限制,对生成的二进制代码进行完全的控制;的控制; 能够对关键代码进行更准确的控制,避免因线程共
5、同访能够对关键代码进行更准确的控制,避免因线程共同访问或者硬件设备共享引起的死锁;问或者硬件设备共享引起的死锁; 能够根据特定的应用对代码做最佳的优化,提高运行速能够根据特定的应用对代码做最佳的优化,提高运行速度;度; 能够最大限度地发挥硬件的功能。能够最大限度地发挥硬件的功能。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介同时还应该认识到,汇编语言是一种层次非常低的语言,它同时还应该认识到,汇编语言是一种层次非常低的语言,它仅仅高于直接手工编写二进制的机器指令码,因此不可避免仅仅高于直接手工编写二进制的机器指令码,因此不可避免地存在一些缺点:地存在一些缺点: 编写的代码非常难
6、懂,不好维护;编写的代码非常难懂,不好维护; 很容易产生很容易产生 bug bug,难于调试;,难于调试; 只能针对特定的体系结构和处理器进行优化;只能针对特定的体系结构和处理器进行优化; 开发效率很低,时间长且单调。开发效率很低,时间长且单调。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介Linux Linux 下用汇编语言编写的代码具有两种不同的形式。下用汇编语言编写的代码具有两种不同的形式。第一种是完全的汇编代码,指的是整个程序全部用汇编语言第一种是完全的汇编代码,指的是整个程序全部用汇编语言编写。尽管是完全的汇编代码,编写。尽管是完全的汇编代码,Linux Linux 平
7、台下的汇编工具也平台下的汇编工具也吸收了吸收了 C C 语言的长处,使得程序员可以使用语言的长处,使得程序员可以使用 #include #include、#ifdef #ifdef 等预处理指令,并能够通过宏定义来简化代码。等预处理指令,并能够通过宏定义来简化代码。第二种是内嵌的汇编代码,指的是可以嵌入到第二种是内嵌的汇编代码,指的是可以嵌入到C C语言程序中语言程序中的汇编代码片段。虽然的汇编代码片段。虽然 ANSI ANSI 的的 C C 语言标准中没有关于内语言标准中没有关于内嵌汇编代码的相应规定,但各种实际使用的嵌汇编代码的相应规定,但各种实际使用的 C C 编译器都做编译器都做了这方
8、面的扩充,这其中当然就包括了这方面的扩充,这其中当然就包括 Linux Linux 平台下的平台下的 GCC GCC。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介二、二、Linux Linux 汇编语法格式汇编语法格式 绝大多数绝大多数 Linux Linux 程序员以前只接触过程序员以前只接触过DOS/Windows DOS/Windows 下的汇编语言,下的汇编语言,这些汇编代码都是这些汇编代码都是 Intel Intel 风格的。但在风格的。但在 Unix Unix 和和 Linux Linux 系统中,更多系统中,更多采用的还是采用的还是 AT&T AT&T 格式,两
9、者在语法格式上有着很大的不同:格式,两者在语法格式上有着很大的不同: 在在 AT&T AT&T 汇编格式中,寄存器名要加上汇编格式中,寄存器名要加上 % % 作为前缀;而在作为前缀;而在 Intel Intel 汇编格式中,寄存器名不需要加前缀。例如:汇编格式中,寄存器名不需要加前缀。例如: AT&T AT&T 格式格式 pushl %eax pushl %eax Intel Intel 格式格式 push eax push eax 在在 AT&T AT&T 汇编格式中,用汇编格式中,用 $ $ 前缀表示一个立即操作数;而在前缀表示一个立即操作数;而在 Intel Intel 汇编格式中,立即
10、数的表示不用带任何前缀。例如:汇编格式中,立即数的表示不用带任何前缀。例如: AT&T AT&T 格式格式 pushl $1 pushl $1 Intel Intel 格式格式 push 1 push 1 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 AT&T AT&T 和和 Intel Intel 格式中的源操作数和目标操作数的位置正好相格式中的源操作数和目标操作数的位置正好相反。在反。在 Intel Intel 汇编格式中,目标操作数在源操作数的左边;而在汇编格式中,目标操作数在源操作数的左边;而在 AT&T AT&T 汇编格式中,目标操作数在源操作数的右边。例如:汇编格式
11、中,目标操作数在源操作数的右边。例如: AT&T AT&T 格式格式 addl $1, %eax addl $1, %eax Intel Intel 格式格式 add eax, 1 add eax, 1 在在 AT&T AT&T 汇编格式中,操作数的字长由操作符的最后一个汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀字母决定,后缀bb、ww、ll分别表示操作数为字节(分别表示操作数为字节(bytebyte,8 8 比特)比特)、字(、字(wordword,16 16 比特)和长字(比特)和长字(longlong,3232比特);而在比特);而在 Intel Intel 汇汇编格式中,
12、操作数的字长是用编格式中,操作数的字长是用 byte ptrbyte ptr 和和 word ptrword ptr 等前缀来等前缀来表示的。例如:表示的。例如: AT&T AT&T 格式格式movb val, %al movb val, %al Intel Intel 格式格式 mov al, byte ptr val mov al, byte ptr val LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 在在 AT&T AT&T 汇编格式中,绝对转移和调用指令(汇编格式中,绝对转移和调用指令(jump/calljump/call)的)的操作数前要加上操作数前要加上*作为前缀
13、,而在作为前缀,而在 Intel Intel 格式中则不需要。格式中则不需要。 远程转移指令和远程子调用指令的操作码,在远程转移指令和远程子调用指令的操作码,在 AT&T AT&T 汇编格汇编格式中为式中为 “ljump” “ljump” 和和 “lcall” “lcall”,而在,而在 Intel Intel 汇编格式中则为汇编格式中则为 “jmp “jmp far” far” 和和 “call far” “call far”,即:,即: AT&T AT&T 格式格式 ljump ljump $section, $offset $section, $offset lcall lcall $s
14、ection, $offset $section, $offset Intel Intel 格式格式 jmp jmp far section:offset far section:offset call call far section:offsetfar section:offset与之相应的远程返回指令则为:与之相应的远程返回指令则为: AT&T AT&T 格式格式 lret lret $stack_adjust $stack_adjust Intel Intel 格式格式 ret ret far stack_adjust far stack_adjust LinuxLinux汇编语言开发
15、指南简介汇编语言开发指南简介 在在 AT&T AT&T 汇编格式中,内存操作数的寻址方式是汇编格式中,内存操作数的寻址方式是 section:disp(base, index, scale) section:disp(base, index, scale) 而在而在 Intel Intel 汇编格式中,内存操作数的寻址方式为:汇编格式中,内存操作数的寻址方式为: section:base + index*scale + displacement section:base + index*scale + displacement 由于由于 Linux Linux 工作在保护模式下,用的是工作在保
16、护模式下,用的是 32 32 位线性地址,所位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:算方法: displacement + base + index * scale displacement + base + index * scale 其中,其中,basebase和和indexindex是任意的是任意的32-bit base32-bit base和和indexindex寄存器。寄存器。scalescale可可以取值以取值1 1,2 2,4 4,8 8。如果不指定。如果不指定scalescale值
17、,则默认值为值,则默认值为1 1。sectionsection可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情况下不一样。如果你在指令中指定了默认的段前缀,则编译器在目况下不一样。如果你在指令中指定了默认的段前缀,则编译器在目标代码中不会产生此段前缀代码标代码中不会产生此段前缀代码LinuxLinux汇编语言开发指南简介汇编语言开发指南简介下面是一些内存操作数的例子:下面是一些内存操作数的例子: AT&T AT&T 格式格式 Intel Intel 格式格式 movl -4(%ebp), %eax movl -4(%ebp),
18、 %eax mov eax, ebp - 4 mov eax, ebp - 4 movl array(, %eax, 4), %eax mov eax, eax*4 + array movl array(, %eax, 4), %eax mov eax, eax*4 + array movw array(%ebx, %eax, 4), %cx mov cx, ebx + 4*eax + array movw array(%ebx, %eax, 4), %cx mov cx, ebx + 4*eax + array movb $4, %fs:(%eax) movb $4, %fs:(%eax)
19、mov fs:eax, 4mov fs:eax, 4三、三、Hello World!Hello World! 真不知道打破这个传统会带来什么样的后果,但既然所有程序设计语真不知道打破这个传统会带来什么样的后果,但既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串言的第一个例子都是在屏幕上打印一个字符串 Hello World! Hello World!,那我们也,那我们也以这种方式来开始介绍以这种方式来开始介绍 Linux Linux 下的汇编语言程序设计。下的汇编语言程序设计。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 在在 Linux Linux 操作系统中,你
20、有很多办法可以实现在屏幕上显操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用示一个字符串,但最简洁的方式是使用 Linux Linux 内核提供的系统调用。内核提供的系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如需要链接诸如 libc libc 这样的函数库,也不需要使用这样的函数库,也不需要使用 ELF ELF 解释器,因而解释器,因而代码尺寸小且执行速度快。代码尺寸小且执行速度快。 Linux Linux 是一个运行在保护模式下的是一个运行在保护模式下的 32 32
21、位操作系统,采用位操作系统,采用 flat memory flat memory 模式,目前最常用到的是模式,目前最常用到的是 ELF ELF 格式的二进制代码。一个格式的二进制代码。一个 ELF ELF 格式的可执行程序通常划分为如下几个部分:格式的可执行程序通常划分为如下几个部分:.text.text、.data .data 和和 .bss .bss,其中,其中 .text .text 是只读的代码区,是只读的代码区,.data .data 是可读可写的数据区,而是可读可写的数据区,而 .bss .bss 则是可读可写且没有初始化的数据区。代码区和数据区在则是可读可写且没有初始化的数据区。
22、代码区和数据区在 ELF ELF 中统称中统称为为 section section,根据实际需要你可以使用其它标准的,根据实际需要你可以使用其它标准的 section section,也可以,也可以添加自定义添加自定义 section section,但一个,但一个 ELF ELF 可执行程序至少应该有一个可执行程序至少应该有一个 .text .text 部分。下面给出我们的第一个汇编程序,用的是部分。下面给出我们的第一个汇编程序,用的是 AT&T AT&T 汇编语言格式:汇编语言格式: LinuxLinux汇编语言开发指南简介汇编语言开发指南简介例例1. AT&T 1. AT&T 格式格式
23、#hello.s #hello.s .data .data # # 数据段声数据段声明明 msg : .string Hello, world!n msg : .string Hello, world!n # # 要输出的字符串要输出的字符串 len = . - msg len = . - msg # # 字串长度字串长度.text .text # # 代码段声明代码段声明 .global _start .global _start # # 指定入口函数指定入口函数 _start: _start: # # 在屏幕上显示一个字符串在屏幕上显示一个字符串 movl $len, %edx movl
24、$len, %edx # # 参数三:字符串长度参数三:字符串长度 movl $msg, %ecx movl $msg, %ecx # # 参数二:要显示的字符串参数二:要显示的字符串 movl $1, %ebx movl $1, %ebx # # 参数一:文件描述符参数一:文件描述符(stdout) (stdout) movl $4, %eax movl $4, %eax # # 系统调用号系统调用号(sys_write) (sys_write) int $0x80 int $0x80 # # 调用内核功能调用内核功能 # # 退出程序退出程序 movl $0,%ebx movl $0,%e
25、bx # # 参数一:退出代码参数一:退出代码 movl $1,%eax movl $1,%eax # # 系统调用号系统调用号(sys_exit) (sys_exit) int $0x80 int $0x80 # # 调用内核功能调用内核功能 初次接触到初次接触到 AT&T AT&T 格式的汇编代码时,很多程序员都认为太晦格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在涩难懂了,没有关系,在 Linux Linux 平台上你同样可以使用平台上你同样可以使用 Intel Intel 格式来编格式来编写汇编程序:写汇编程序: LinuxLinux汇编语言开发指南简介汇编语言开发指南简
26、介例例2. Intel 2. Intel 格式格式; hello.asm ; hello.asm section .data section .data ; ; 数据段声明数据段声明 msg db “Hello, world!”, 0xA ; msg db “Hello, world!”, 0xA ; 要输出的字符串要输出的字符串 len equ $ - msg ; len equ $ - msg ; 字串长度字串长度 section .text section .text ; ; 代码段声明代码段声明 global _start global _start ; ; 指定入口函数指定入口函数
27、_start: _start: ; ; 在屏幕上显示一个字符串在屏幕上显示一个字符串 mov edx, len mov edx, len ; ; 参数三:字符串长度参数三:字符串长度 mov ecx, msg mov ecx, msg ; ; 参数二:要显示的字符串参数二:要显示的字符串 mov ebx, 1 mov ebx, 1 ; ; 参数一:文件描述符参数一:文件描述符(stdout) (stdout) mov eax, 4 mov eax, 4 ; ; 系统调用号系统调用号(sys_write) (sys_write) int 0x80 int 0x80 ; ; 调用内核功能调用内核功
28、能 ; ; 退出程序退出程序 mov ebx, 0 mov ebx, 0 ; ; 参数一:退出代码参数一:退出代码 mov eax, 1 mov eax, 1 ; ; 系统调用号系统调用号(sys_exit) (sys_exit) int 0x80 int 0x80 ; ; 调用内核功能调用内核功能 上面两个汇编程序采用的语法虽然完全不同,但功能却都是调上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用用 Linux Linux 内核提供的内核提供的 sys_write sys_write 来显示一个字符串,然后再调用来显示一个字符串,然后再调用 sys_exit sys_exit 退出
29、程序。在退出程序。在 Linux Linux 内核源文件内核源文件 include/asm-i386/unistd.h include/asm-i386/unistd.h 中,可以找到所有系统调用的定义。中,可以找到所有系统调用的定义。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介四、四、Linux Linux 汇编工具汇编工具 Linux Linux 平台下的汇编工具虽然种类很多,但同平台下的汇编工具虽然种类很多,但同 DOS/Windows DOS/Windows 一一样,最基本的仍然是汇编器、连接器和调试器。样,最基本的仍然是汇编器、连接器和调试器。 1.1.汇编器汇编器
30、 汇编器(汇编器(assemblerassembler)的作用是)的作用是将用汇编语言编写的源程序转将用汇编语言编写的源程序转换成二进制形式的目标代码换成二进制形式的目标代码。Linux Linux 平台的标准汇编器是平台的标准汇编器是 GAS GAS,它是,它是 GCC GCC 所依赖的后台汇编工具,通常包含在所依赖的后台汇编工具,通常包含在 binutils binutils 软件包中。软件包中。GAS GAS 使使用标准的用标准的 AT&T AT&T 汇编语法,可以用来汇编用汇编语法,可以用来汇编用 AT&T AT&T 格式编写的程序:格式编写的程序: xiaowpgary code$
31、as -o hello.o hello.sxiaowpgary code$ as -o hello.o hello.s Linux Linux 平台上另一个经常用到的汇编器是平台上另一个经常用到的汇编器是 NASM NASM,它提供了很,它提供了很好的宏指令功能,并能够支持相当多的目标代码格式,包括好的宏指令功能,并能够支持相当多的目标代码格式,包括 bin bin、a.outa.out、coffcoff、elfelf、rdf rdf 等。等。NASM NASM 采用的是人工编写的语法分析器,采用的是人工编写的语法分析器,因而执行速度要比因而执行速度要比 GAS GAS 快很多,更重要的是它使
32、用的是快很多,更重要的是它使用的是 Intel Intel 汇编语汇编语法,可以用来编译用法,可以用来编译用 Intel Intel 语法格式编写的汇编程序:语法格式编写的汇编程序: xiaowpgary code$ nasm -f elf hello.asm xiaowpgary code$ nasm -f elf hello.asm LinuxLinux汇编语言开发指南简介汇编语言开发指南简介2.2.链接器链接器 由汇编器产生的目标代码是不能直接在计算机上运行的,由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过链接器的处理才能生成可执行代码。链接器通常用来将它必须经过链接器的处
33、理才能生成可执行代码。链接器通常用来将多个目标代码连接成一个可执行代码,这样可以先将整个程序分成多个目标代码连接成一个可执行代码,这样可以先将整个程序分成几个模块来单独开发,然后才将它们组合几个模块来单独开发,然后才将它们组合( (链接链接) )成一个应用程序。成一个应用程序。 Linux Linux 使用使用 ld ld 作为标准的链接程序,它同样也包含在作为标准的链接程序,它同样也包含在 binutils binutils 软件包中。汇编程序在成功通过软件包中。汇编程序在成功通过 GAS GAS 或或 NASM NASM 的编译并生成目标的编译并生成目标代码后,就可以使用代码后,就可以使用
34、 ld ld 将其链接成可执行程序了:将其链接成可执行程序了: xiaowpgary code$ ld -s -o hello hello.o xiaowpgary code$ ld -s -o hello hello.o 3.3.调试器调试器 有人说程序不是编出来而是调出来的,足见调试在软件有人说程序不是编出来而是调出来的,足见调试在软件开发中的重要作用,在用汇编语言编写程序时尤其如此。开发中的重要作用,在用汇编语言编写程序时尤其如此。Linux Linux 下下调试汇编代码既可以用调试汇编代码既可以用 GDB GDB、DDD DDD 这类通用的调试器,也可以使用这类通用的调试器,也可以使用
35、专门用来调试汇编代码的专门用来调试汇编代码的 ALD(Assembly Language Debugger) ALD(Assembly Language Debugger)。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 从调试的角度来看,使用从调试的角度来看,使用 GAS GAS 的好处是可以在生成的目标代的好处是可以在生成的目标代码中码中包含符号表包含符号表(symbol table)(symbol table),这样就可以使用,这样就可以使用 GDB GDB 和和 DDD DDD 来来进行源码级的调试了。要在生成的可执行程序中包含符号表,可以进行源码级的调试了。要在生成的
36、可执行程序中包含符号表,可以采用下面的方式进行编译和链接:采用下面的方式进行编译和链接: xiaowpgary code$ as -gstabs -o hello.o hello.s xiaowpgary code$ as -gstabs -o hello.o hello.s xiaowpgary code$ ld -o hello hello.o xiaowpgary code$ ld -o hello hello.o 执行执行 as as 命令时带上参数命令时带上参数 -gstabs -gstabs 可以告诉汇编器在可以告诉汇编器在生成的目标代码中加上符号表,同时需要注意的是,在用生成的目
37、标代码中加上符号表,同时需要注意的是,在用 ld ld 命令命令进行链接时不要加上进行链接时不要加上 -s -s 参数,否则目标代码中的符号表在链接时参数,否则目标代码中的符号表在链接时将被删去。将被删去。 在在 GDB GDB 和和 DDD DDD 中调试汇编代码和调试中调试汇编代码和调试 C C 语言代码是一样语言代码是一样的,你可以通过设置断点来中断程序的运行,查看变量和寄存器的的,你可以通过设置断点来中断程序的运行,查看变量和寄存器的当前值,并可以对代码进行单步跟踪。当前值,并可以对代码进行单步跟踪。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 汇编程序员通常面对的都是
38、一些比较苛刻的软硬件环境,汇编程序员通常面对的都是一些比较苛刻的软硬件环境,短小精悍的短小精悍的ALDALD可能更能符合实际的需要,因此下面主要介绍一下如可能更能符合实际的需要,因此下面主要介绍一下如何用何用ALDALD来调试汇编程序。首先在命令行方式下执行来调试汇编程序。首先在命令行方式下执行aldald命令来启动命令来启动调试器,该命令的参数是将要被调试的可执行程序:调试器,该命令的参数是将要被调试的可执行程序: xiaowpgary doc$ ald hello xiaowpgary doc$ ald hello Assembly Language Debugger 0.1.3 Asse
39、mbly Language Debugger 0.1.3 Copyright (C) 2000-2002 Patrick Alken Copyright (C) 2000-2002 Patrick Alken hello: ELF Intel 80386 (32 bit), LSB, Executable, Version 1 hello: ELF Intel 80386 (32 bit), LSB, Executable, Version 1 (current) (current) Loading debugging symbols.(15 symbols loaded) Loading d
40、ebugging symbols.(15 symbols loaded) ald ald 当当 ALD ALD 的提示符出现之后,用的提示符出现之后,用 disassemble disassemble 命令对代码段进命令对代码段进行反汇编:行反汇编: LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 当当 ALD ALD 的提示符出现之后,用的提示符出现之后,用 disassemble disassemble 命令对代码段进命令对代码段进行反汇编:行反汇编: ald disassemble -s .text ald disassemble -s .text Disassemblin
41、g section .text (0x08048074 - 0x08048096) Disassembling section .text (0x08048074 - 0x08048096) 08048074 BA0F000000 mov edx, 0xf 08048074 BA0F000000 mov edx, 0xf 08048079 B998900408 mov ecx, 08048079 B998900408 mov ecx, 0x8049098 0x8049098 0804807E BB01000000 mov ebx, 0x1 0804807E BB01000000 mov ebx
42、, 0x1 08048083 B804000000 mov eax, 0x4 08048083 B804000000 mov eax, 0x4 08048088 CD80 int 0x80 08048088 CD80 int 0x80 0804808A BB00000000 mov ebx, 0x0 0804808A BB00000000 mov ebx, 0x0 0804808F B801000000 mov eax, 0x1 0804808F B801000000 mov eax, 0x1 08048094 CD80 int 0x80 08048094 CD80 int 0x80 上述输出
43、信息的第一列是指令对应的地址码,利用它可以上述输出信息的第一列是指令对应的地址码,利用它可以设置在程序执行时的断点:设置在程序执行时的断点: ald break 0x08048088 ald break 0x08048088 Breakpoint 1 set for 0x08048088 Breakpoint 1 set for 0x08048088 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 断点设置好后,使用断点设置好后,使用 run run 命令开始执行程序。命令开始执行程序。ALD ALD 在遇到断点在遇到断点时将自动暂停程序的运行,同时会显示所有寄存器的当前值:时将
44、自动暂停程序的运行,同时会显示所有寄存器的当前值: ald run ald run Starting program: hello Starting program: hello Breakpoint 1 encountered at 0x08048088 Breakpoint 1 encountered at 0x08048088 eax = 0x00000004 ebx = 0x00000001 ecx = 0x08049098 edx = eax = 0x00000004 ebx = 0x00000001 ecx = 0x08049098 edx = 0x0000000F 0x00000
45、00F esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000 0x00000000 ds = 0x0000002B es = 0x0000002B fs = 0x00000000 gs ds = 0x0000002B es = 0x0000002B fs = 0x00000000 gs = 0x00000000 = 0x00000000 ss = 0x0000002B cs = 0x00000023
46、eip = 0x08048088 eflags ss = 0x0000002B cs = 0x00000023 eip = 0x08048088 eflags = 0x00000246 = 0x00000246 Flags: PF ZF IF Flags: PF ZF IF 08048088 CD80 int 0x80 08048088 CD80 int 0x80 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介如果需要对汇编代码进行单步调试,可以使用如果需要对汇编代码进行单步调试,可以使用 next next 命令:命令: ald next ald next Hello, wor
47、ld! Hello, world! eax = 0x0000000F ebx = 0x00000000 ecx = 0x08049098 edx = eax = 0x0000000F ebx = 0x00000000 ecx = 0x08049098 edx = 0x0000000F 0x0000000F esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = esp = 0xBFFFF6C0 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000 0x00000000 ds = 0x000
48、0002B es = 0x0000002B fs = 0x00000000 gs = ds = 0x0000002B es = 0x0000002B fs = 0x00000000 gs = 0x00000000 0x00000000 ss = 0x0000002B cs = 0x00000023 eip = 0x0804808F eflags ss = 0x0000002B cs = 0x00000023 eip = 0x0804808F eflags = 0x00000346 = 0x00000346 Flags: PF ZF TF IF Flags: PF ZF TF IF 080480
49、8F B801000000 mov eax, 0x1 0804808F B801000000 mov eax, 0x1 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 若想获得若想获得 ALD ALD 支持的所有调试命令的详细列表,可以使用支持的所有调试命令的详细列表,可以使用 help help 命令:命令: ald help ald help Commands may be abbreviated. Commands may be abbreviated. If a blank command is entered, the last command is repeated.
50、 If a blank command is entered, the last command is repeated. Type help for more specific information on Type help for more specific information on . . General commands General commands attach clear continue detach attach clear continue detach disassemble disassemble enter examine file help enter ex
51、amine file help load load next quit register run next quit register run set set step unload window write step unload window write Breakpoint related commands Breakpoint related commands break delete disable enable break delete disable enable ignore ignore lbreak tbreak lbreak tbreak LinuxLinux汇编语言开发
52、指南简介汇编语言开发指南简介 五、系统调用五、系统调用 即便是最简单的汇编程序,也难免要用到诸如输入、输即便是最简单的汇编程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运算,否则将很难避免使用系统调用,事实上除了系统调用不同之外,算,否则将很难避免使用系统调用,事实上除了系统调用不同之外,各种操作系统的汇编编程往往都是芾嗨频摹各种操作系统的汇编编程往往都是芾嗨频摹 在在 Linux
53、Linux 平台下有两种方式来使用系统调用:利用封装平台下有两种方式来使用系统调用:利用封装后的后的 C C 库(库(libclibc)或者通过汇编直接调用。其中通过汇编语言来)或者通过汇编直接调用。其中通过汇编语言来直接调用系统调用,是最高效地使用直接调用系统调用,是最高效地使用 Linux Linux 内核服务的方法,因内核服务的方法,因为最终生成的程序不需要与任何库进行链接,而是直接和内核通信。为最终生成的程序不需要与任何库进行链接,而是直接和内核通信。和和 DOS DOS 一样,一样,Linux Linux 下的系统调用也是通过中断(下的系统调用也是通过中断(int 0x80int 0
54、x80)来)来实现的。在执行实现的。在执行 int 80 int 80 指令时,寄存器指令时,寄存器 eax eax 中存放的是系统调中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx ebx,ecxecx,edxedx,esiesi,edi edi 中,当系统调用完成之后,返回值可以在中,当系统调用完成之后,返回值可以在寄存器寄存器 eax eax 中获得。中获得。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介所有的系统调用功能号都可以在文件所有的系统调用功能号都可以在文件 /usr/inclu
55、de/bits/syscall.h /usr/include/bits/syscall.h 中中找到,为了便于使用,它们是用找到,为了便于使用,它们是用 SYS_ SYS_ 这样的宏来定义的,这样的宏来定义的,如如 SYS_write SYS_write、SYS_exit SYS_exit 等。例如,经常用到的等。例如,经常用到的 write write 函数是如函数是如下定义的:下定义的: ssize_t write(int fd, const void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t co
56、unt); 该函数的功能最终是通过该函数的功能最终是通过 SYS_write SYS_write 这一系统调用来实这一系统调用来实现的。根据上面的约定,参数现的。根据上面的约定,参数 fb fb、buf buf 和和 count count 分别存在寄存器分别存在寄存器 ebxebx、ecx ecx 和和 edx edx 中,而系统调用号中,而系统调用号 SYS_write SYS_write 则放在寄存器则放在寄存器 eax eax 中,当中,当 int 0x80 int 0x80 指令执行完毕后,返回值可以从寄存器指令执行完毕后,返回值可以从寄存器 eax eax 中获得。或许你已经发现,
57、在进行系统调用时至多只有中获得。或许你已经发现,在进行系统调用时至多只有 5 5 个寄存个寄存器能够用来保存参数,难道所有系统调用的参数个数都不超过器能够用来保存参数,难道所有系统调用的参数个数都不超过 5 5 吗?当然不是,例如吗?当然不是,例如 mmap mmap 函数就有函数就有 6 6 个参数,这些参数最后都个参数,这些参数最后都需要传递给系统调用需要传递给系统调用 SYS_mmap SYS_mmap: void * mmap(void *start, size_t length, int prot , int void * mmap(void *start, size_t lengt
58、h, int prot , int flags, int fd, off_t offset); flags, int fd, off_t offset); LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 当一个系统调用所需的参数个数大于当一个系统调用所需的参数个数大于 5 5 时,执行时,执行int 0x80 int 0x80 指令时仍需将系统调用功能号保存在寄存器指令时仍需将系统调用功能号保存在寄存器 eax eax 中,所不同的中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器器 ebx ebx 中保
59、存指向该内存区域的指针。系统调用完成之后,返中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器回值仍将保存在寄存器 eax eax 中。中。 由于只是需要一块连续的内存区域来保存系统调用的参数,由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈因此完全可以像普通的函数调用一样使用栈(stack)(stack)来传递系统调来传递系统调用所需的参数。用所需的参数。 但要注意一点,但要注意一点,Linux Linux 采用的是采用的是 C C 语言的调用模式,这就语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,意
60、味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参数,在执行数,在执行int 0x80 int 0x80 指令时还应该将栈指针的当前值复制到寄指令时还应该将栈指针的当前值复制到寄存器存器 ebx ebx中。中。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介六、命令行参数六、命令行参数 在在 Linux Linux 操作系统中,当一个可执行程序通过命令行启动时,操作系统中,当一个可执行程序通过命令行启动时,其所需的参数将被保存到栈中:首先是其所需的参数将被保存到栈中
61、:首先是 argc argc,然后是指向各个命令行参,然后是指向各个命令行参数的指针数组数的指针数组 argv argv,最后是指向环境变量的指针数据,最后是指向环境变量的指针数据 envp envp。在编写汇编。在编写汇编语言程序时,很多时候需要对这些参数进行处理,下面的代码示范了如何语言程序时,很多时候需要对这些参数进行处理,下面的代码示范了如何在汇编代码中进行命令行参数的处理:在汇编代码中进行命令行参数的处理: 例例3. 3. 处理命令行参数处理命令行参数 # args.s # args.s .text .text .globl _start .globl _start _start:
62、_start: popl %ecx # argc popl %ecx # argc vnext: vnext: popl %ecx # argv popl %ecx # argv test %ecx, %ecx # test %ecx, %ecx # 空指针表明结束空指针表明结束 jz exit jz exit LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 movl %ecx, %ebx movl %ecx, %ebx xorl %edx, %edx xorl %edx, %edx strlen: strlen: movb (%ebx), %al movb (%ebx), %a
63、l inc %edx inc %edx inc %ebx inc %ebx test %al, %al test %al, %al jnz strlen jnz strlen movb $10, -1(%ebx) movb $10, -1(%ebx) movl $4, %eax # movl $4, %eax # 系统调用号系统调用号(sys_write) (sys_write) movl $1, %ebx # movl $1, %ebx # 文件描述符文件描述符(stdout) (stdout) int $0x80 int $0x80 jmp vnext jmp vnext exit: ex
64、it: movl $1,%eax # movl $1,%eax # 系统调用号系统调用号(sys_exit) (sys_exit) xorl %ebx, %ebx # xorl %ebx, %ebx # 退出代码退出代码 int $0x80 int $0x80 ret ret LinuxLinux汇编语言开发指南简介汇编语言开发指南简介七、七、GCC GCC 内联汇编内联汇编 用汇编编写的程序虽然运行速度快,但开发速度非常慢,用汇编编写的程序虽然运行速度快,但开发速度非常慢,效率也很低。如果只是想对关键代码段进行优化,或许更好的办法效率也很低。如果只是想对关键代码段进行优化,或许更好的办法是将
65、汇编指令嵌入到是将汇编指令嵌入到 C C 语言程序中,从而充分利用高级语言和汇编语言程序中,从而充分利用高级语言和汇编语言各自的特点。但一般来讲,在语言各自的特点。但一般来讲,在 C C 代码中嵌入汇编语句要比代码中嵌入汇编语句要比 纯纯粹粹 的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及如何与如何与C C代码中的变量相结合等问题。代码中的变量相结合等问题。 GCC GCC 提供了很好的内联汇编支持,最基本的格式是:提供了很好的内联汇编支持,最基本的格式是: _asm_(asm statements); _asm_(asm st
66、atements); 例如:例如: _asm_(nop); _asm_(nop); 如果需要同时执行多条汇编语句,则应该用如果需要同时执行多条汇编语句,则应该用ntnt将各个语将各个语句分隔开,例如:句分隔开,例如: _asm_( pushl %eax nt _asm_( pushl %eax nt movl $0, %eax nt movl $0, %eax nt popl %eax); popl %eax); LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 通常嵌入到通常嵌入到 C C 代码中的汇编语句很难做到与其它部分没有代码中的汇编语句很难做到与其它部分没有任何关系,因此
67、更多时候需要用到完整的内联汇编格式:任何关系,因此更多时候需要用到完整的内联汇编格式: _asm_(asm statements : outputs : inputs : registers-_asm_(asm statements : outputs : inputs : registers-modified); modified); 插入到插入到 C C 代码中的汇编语句是以代码中的汇编语句是以:分隔的四个部分,分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其
68、它部汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。分则可以根据实际情况而省略。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 在将汇编语句嵌入到在将汇编语句嵌入到C C代码中时,操作数如何与代码中时,操作数如何与C C代码中的代码中的变量相结合是个很大的问题。变量相结合是个很大的问题。GCCGCC采用如下方法来解决这个问采用如下方法来解决这个问题:程序员提供具体的指令,而对寄存器的使用则只需给出题:程序员提供具体的指令,而对寄存器的使用则只需给出“样样板板”和约束条件就可以了,具体如何将寄存器与变量结合起来完和约束条件就可以了,具体如何将寄
69、存器与变量结合起来完全由全由GCCGCC和和GASGAS来负责。在来负责。在GCCGCC内联汇编语句的指令部中,加上内联汇编语句的指令部中,加上前缀前缀%的数字的数字( (如如%0%0,%1)%1)表示的就是需要使用寄存器的表示的就是需要使用寄存器的“样板样板”操操作数。作数。 指令部中使用了几个样板操作数,就表明有几个变量需要指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合,这样与寄存器相结合,这样GCCGCC和和GASGAS在编译和汇编时会根据后面给在编译和汇编时会根据后面给定的约束条件进行恰当的处理。由于样板操作数也使用定的约束条件进行恰当的处理。由于样板操作数也使用%作
70、为作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个两个%,以免产生混淆。,以免产生混淆。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 紧跟在指令部后面的是输出部,是规定输出变量如何紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个与样板操作数进行结合的条件,每个条件称为一个“约束约束”,必要时可以包含多个约束,相互之间用逗号分隔开就可以必要时可以包含多个约束,相互之间用逗号分隔开就可以了。了。 每个输出约束都以每个输出约束都以=号开始,然后紧跟一个对操作数号开始,然后紧跟
71、一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是这是GCCGCC在调度寄存器时所使用的依据。在调度寄存器时所使用的依据。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 输出部后面是输入部,输入约束的格式和输出约束相似,输出部后面是输入部,输入约束的格式和输出约束相似,但不带但不带=号。如果一个输入约束要求使用寄
72、存器,则号。如果一个输入约束要求使用寄存器,则GCCGCC在在预处理时就会为之分配一个寄存器,并插入必要的指令将操预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。前的内容。 有时在进行某些操作时,除了要用到进行数据输入和输有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样
73、就难免会破坏原有寄存器的内容。在这样就难免会破坏原有寄存器的内容。在GCCGCC内联汇编格式中内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便以便GCCGCC能够采用相应的措施。能够采用相应的措施。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介下面是一个内联汇编的简单例子:下面是一个内联汇编的简单例子: 例例4.4.内联汇编内联汇编 /* inline.c */ /* inline.c */ int main() int main() int a = 10, b = 0; int a = 10, b =
74、 0; _asm_ _volatile_(movl %1, %eax;nr _asm_ _volatile_(movl %1, %eax;nr movl %eax, %0; movl %eax, %0; :=r(b) /* :=r(b) /* 输输出出 */ */ :r(a) /* :r(a) /* 输输入入 */ */ :%eax); /* :%eax); /* 不受不受影响的寄存器影响的寄存器 */ */ printf(Result: %d, %dn, a, b); printf(Result: %d, %dn, a, b); LinuxLinux汇编语言开发指南简介汇编语言开发指南简介变
75、量变量b b是输出操作数,通过是输出操作数,通过%0%0来引用,而变量来引用,而变量a a是输入操作数,是输入操作数,通过通过%1%1来引用。来引用。 输入操作数和输出操作数都使用输入操作数和输出操作数都使用r r进行约束,进行约束,表示将变量表示将变量a a和和变量变量b b存储在寄存器中存储在寄存器中。输入约束和输出约束的不同点在于输。输入约束和输出约束的不同点在于输出约束多一个约束修饰符出约束多一个约束修饰符=。在内联汇编语句中使用寄存器在内联汇编语句中使用寄存器eaxeax时,寄存器名前应该加两个时,寄存器名前应该加两个%,即,即%eaxeax。内联汇编中使用。内联汇编中使用%0%0、
76、%1%1等来标识变量,任何只带等来标识变量,任何只带一个一个%的标识符的标识符都看成是操作数,而不是寄存器都看成是操作数,而不是寄存器。内联汇编语句的最后一个部分告诉内联汇编语句的最后一个部分告诉GCCGCC它将改变寄存器它将改变寄存器eaxeax中中的值,的值,GCCGCC在处理时不应使用该寄存器来存储任何其它的值。在处理时不应使用该寄存器来存储任何其它的值。 上面的程序完成将变量上面的程序完成将变量a a的值赋予变量的值赋予变量b b,有几点需要说明:,有几点需要说明:LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 由于变量由于变量b b被指定成输出操作数,当内联汇编语句执行
77、完毕后,被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。它所保存的值将被更新。 在内联汇编中用到的操作数从输出部的第一个约束开始编号,在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从序号从0 0开始,每个约束记数一次,指令部要引用这些操作数时,只开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上需在序号前加上%作为前缀就可以了。需要注意的是,内联汇编语作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为句的指令部在引用一个操作数时总是将其作为3232位的长字使用,但位的长字使用,但实际情况可能需要的是字或字节,因此应
78、该在约束中指明正确的限实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:定符: 限定符限定符 意义意义 mm、vv、o o 内存单元内存单元 r r 任何寄存器任何寄存器 q q 寄存器寄存器eaxeax、ebxebx、ecxecx、edxedx之一之一 ii、h h 直接操作数直接操作数 EE和和F F 浮点数浮点数 g g 任意任意 aa、bb、cc、d d 分别表示寄存器分别表示寄存器eaxeax、ebxebx、ecxecx和和edx edx SS和和D D 寄存器寄存器esiesi、edi edi I I 常数(常数(0 0至至3131) LinuxLinux汇编语言开发
79、指南简介汇编语言开发指南简介八、小结八、小结 Linux Linux操作系统是用操作系统是用C C语言编写的,汇编只在必要的语言编写的,汇编只在必要的时候才被人们想到,但它却是减少代码尺寸和优化代码性能时候才被人们想到,但它却是减少代码尺寸和优化代码性能的一种非常重要的手段,特别是在与硬件直接交互的时候,的一种非常重要的手段,特别是在与硬件直接交互的时候,汇编可以说是最佳的选择。汇编可以说是最佳的选择。LinuxLinux提供了非常优秀的工具来支提供了非常优秀的工具来支持汇编程序的开发,使用持汇编程序的开发,使用GCCGCC的内联汇编能够充分地发挥的内联汇编能够充分地发挥C C语语言和汇编语言
80、各自的优点。言和汇编语言各自的优点。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介九、参考资料九、参考资料 在网站上可以找到大量的在网站上可以找到大量的LinuxLinux汇编资源。汇编资源。 软件包软件包binutilsbinutils提供了提供了asas和和ldld等实用工具,其相关信息可以在网站上找等实用工具,其相关信息可以在网站上找到。到。 NASMNASM是是IntelIntel格式的汇编器,其相关信息可以在网站上找到。格式的汇编器,其相关信息可以在网站上找到。 ALDALD是一个短小精悍的汇编调试器,其相关信息可以在网站上找到。是一个短小精悍的汇编调试器,其相关信息
81、可以在网站上找到。 intel2gasintel2gas是一个能够将是一个能够将IntelIntel汇编格式转换成汇编格式转换成AT&TAT&T汇编格式的小工具,汇编格式的小工具,其相关信息可以在网站上找到。其相关信息可以在网站上找到。 IBM developerWorksIBM developerWorks上有一篇介绍上有一篇介绍GCCGCC内联汇编的文章内联汇编的文章()()。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介gcc中的内嵌汇编语言中的内嵌汇编语言(Intel i386平台)平台) 一一.声明声明 虽然虽然Linux的核心代码大部分是用的核心代码大部分是用C语言编
82、写的,语言编写的,但是不可避免的其中但是不可避免的其中 还是有一部分是用汇编语言还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇编源程序写成的。有些汇编语言代码是直接写在汇编源程序中的,特别是中的,特别是Linux的启动代码部分;还有一些则的启动代码部分;还有一些则是利用是利用gcc 的内嵌汇编语言嵌在的内嵌汇编语言嵌在C语言程序中的。语言程序中的。这篇文章简单介绍了这篇文章简单介绍了gcc中的内中的内 嵌式汇编语言,主嵌式汇编语言,主要想帮助那些才开始阅读要想帮助那些才开始阅读Linux核心代码的朋友们核心代码的朋友们 能够更快的入手。能够更快的入手。 LinuxLinux汇编语
83、言开发指南简介汇编语言开发指南简介写这篇文章的主要信息来源是写这篇文章的主要信息来源是GNU的两个的两个info文件:文件:as.info和和 gcc.info,如果你觉得这篇文章中的介绍还,如果你觉得这篇文章中的介绍还不够详细的话,你可以查不够详细的话,你可以查 阅这两个文件。当然,直接阅这两个文件。当然,直接查阅这两个文件可以获得更加权威的信息。查阅这两个文件可以获得更加权威的信息。 如果你不如果你不想被这两篇文档中的一大堆信息搞迷糊的话,我建议想被这两篇文档中的一大堆信息搞迷糊的话,我建议你先阅你先阅 读一下这篇文章,然后在必要时再去查阅更权读一下这篇文章,然后在必要时再去查阅更权威的信
84、息。威的信息。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介二二.简介简介 在在Linux的核心代码中,还是存在相当一部分的汇编语言的核心代码中,还是存在相当一部分的汇编语言代码。如果代码。如果 你想顺利阅读你想顺利阅读Linux代码的话,你不可能绕过代码的话,你不可能绕过这一部分代码。在这一部分代码。在Linux 使用的汇编语言代码中,主要有使用的汇编语言代码中,主要有两种格式:一种是直接写成汇编语言两种格式:一种是直接写成汇编语言 源程序的形式,这一源程序的形式,这一部分主要是一些部分主要是一些Linux的启动代码;另一部分则的启动代码;另一部分则 是利用是利用gcc的内嵌
85、式汇编语言语句的内嵌式汇编语言语句asm嵌在嵌在Linux的的C语言代码中语言代码中的。这的。这 篇文章主要是介绍第二种形式的汇编语言代码。篇文章主要是介绍第二种形式的汇编语言代码。 首先,我介绍一下首先,我介绍一下as支持的汇编语言的语法格式。大家知支持的汇编语言的语法格式。大家知道,我们现道,我们现 在学习的汇编语言的格式主要是在学习的汇编语言的格式主要是Intel风格的,风格的,而在而在Linux的核心代码的核心代码 中使用的则是中使用的则是AT&T格式的汇编语言格式的汇编语言代码,应该说大部分人对这种格式代码,应该说大部分人对这种格式 的汇编语言还不是很了的汇编语言还不是很了解,所以我
86、觉得有必要介绍一下。解,所以我觉得有必要介绍一下。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介接着,我主要介绍一下接着,我主要介绍一下gcc的内嵌式汇编语言的格式。的内嵌式汇编语言的格式。gcc的的内嵌式汇内嵌式汇 编语言提供了一种在编语言提供了一种在C语言源程序中直接嵌入汇编指语言源程序中直接嵌入汇编指令的很好的办法,令的很好的办法, 既能够直接控制所形成的指令序列,又有既能够直接控制所形成的指令序列,又有着与着与C语言的良好接口,所语言的良好接口,所 以在以在Linux代码中很多地方都使用代码中很多地方都使用了这一语句。了这一语句。 LinuxLinux汇编语言开发指南简
87、介汇编语言开发指南简介三三.AT&T的汇编语言语法格式的汇编语言语法格式 我想我们大部分人对我想我们大部分人对Intel格式的汇编语言都很了解了。格式的汇编语言都很了解了。但是,在但是,在 Linux核心代码中,所有的汇编语言指令都核心代码中,所有的汇编语言指令都是用是用AT&T格式的汇编语格式的汇编语 言书写的。这两种汇编语言言书写的。这两种汇编语言在语法格式上有着很大的不同:在语法格式上有着很大的不同: 1.在在AT&T的汇编语言中,用的汇编语言中,用$前缀表示一个立即操作前缀表示一个立即操作数;而在数;而在Intel 的格式中,立即操作数的表示不带任何的格式中,立即操作数的表示不带任何前
88、缀符。例如:下面两个语句前缀符。例如:下面两个语句 是完全相同的:是完全相同的: *AT&T: pushl $4 *Intel: push 4 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介2.AT&T和和Intel的汇编语言格式中,源操作数和目标操的汇编语言格式中,源操作数和目标操作数的位置正作数的位置正 好相反。好相反。Intel的汇编语言中,目标操作的汇编语言中,目标操作数在源操作数的左边;而在数在源操作数的左边;而在 AT&T的汇编语言中,目标的汇编语言中,目标操作数则在源操作数的右边。例如:操作数则在源操作数的右边。例如: *AT&T : addl $4,%eax *I
89、ntel: add eax,4 3.在在AT&T的汇编语言中,操作数的字长是由操作码的汇编语言中,操作数的字长是由操作码助记符的最后一个助记符的最后一个 字母决定的,后缀字母决定的,后缀b、w、l分别分别表示操作数的字长为表示操作数的字长为8比特(字比特(字 节,节,byte),),16比特比特(字,(字,word)和)和32比特(长字,比特(长字,long),而),而 Intel格格式中操作数的字长是用式中操作数的字长是用“word ptr”或者或者“byte ptr”等前等前 缀来表示的。例如:缀来表示的。例如: *AT&T: movb FOO,%al *Intel: mov al,byt
90、e ptr FOO LinuxLinux汇编语言开发指南简介汇编语言开发指南简介4.在在AT&T汇编指令中,直接远跳转汇编指令中,直接远跳转/调用的指令格式是调用的指令格式是“lcall/ljmp $SECTION,$OFFSET”,同样,远程返回的指令是同样,远程返回的指令是“lret $STACK-ADJUST”;而在;而在Intel格式中,相应的指令分格式中,相应的指令分别为别为“call/jmp far SECTION:OFFSET”和和“ret far STACK-ADJUST”。 AT&T汇编指令操作助记符命名规则汇编指令操作助记符命名规则 AT&T汇编语言中,汇编语言中,操作码助
91、记符的后缀字符指定了该指令中操作数的操作码助记符的后缀字符指定了该指令中操作数的 字长。字长。后缀字母后缀字母b、w、l分别表示字长为分别表示字长为8比特(字节,比特(字节,byte),), 16比特(字,比特(字,word)和)和32比特(长字,比特(长字,long)的操作数。)的操作数。如果助记符如果助记符 中没有指定字长后缀并且该指令中没有内存操中没有指定字长后缀并且该指令中没有内存操作数,汇编程序作数,汇编程序as会会 根据指令中指定的寄存器操作数补上根据指令中指定的寄存器操作数补上相应的后缀字符。所以,下面的相应的后缀字符。所以,下面的 两个指令具有相同的效果两个指令具有相同的效果(
92、这只是(这只是GNU的汇编程序的汇编程序as的一个特性,的一个特性,AT&T 的的Unix汇汇编程序将没有字长后缀的指令的操作数字长假设为编程序将没有字长后缀的指令的操作数字长假设为32比特):比特):mov %ax,%bx movw %ax,%bx LinuxLinux汇编语言开发指南简介汇编语言开发指南简介AT&T中几乎所有的操作助记符与中几乎所有的操作助记符与Intel格式中的助记符同名,格式中的助记符同名,仅有一仅有一 小部分例外。操作数扩展指令就是例外之一。在小部分例外。操作数扩展指令就是例外之一。在AT&T汇编指令中,操汇编指令中,操 作数扩展指令有两个后缀:一个指定作数扩展指令有
93、两个后缀:一个指定源操作数的字长,另一个指定目源操作数的字长,另一个指定目 标操作数的字长。标操作数的字长。AT&T的的符号扩展指令的基本助记符为符号扩展指令的基本助记符为movs,零扩,零扩 展指令的基本助展指令的基本助记符为记符为movz(相应的(相应的Intel指令为指令为movsx和和 movzx)。因)。因此,此,movsbl %al,%edx表示对寄存器表示对寄存器al中的字节数据中的字节数据 进行字进行字节到长字的符号扩展,计算结果存放在寄存器节到长字的符号扩展,计算结果存放在寄存器edx中。下面中。下面是一是一 些允许的操作数扩展后缀:些允许的操作数扩展后缀: *bl: 字节字
94、节-长字长字 *bw: 字节字节-字字 *wl: 字字-长字长字 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介还有一些其他的类型转换指令的对应关系:还有一些其他的类型转换指令的对应关系: *Intel *AT&T cbw cbtw 符号扩展:符号扩展:al-ax cwde cwtl 符号扩展:符号扩展:ax-eax cwd cwtd 符号扩展:符号扩展:ax-dx:ax cdq cltd 符号扩展:符号扩展:eax-edx:eax 还有一个不同名的助记符就是远程跳转还有一个不同名的助记符就是远程跳转/调用指令。在调用指令。在Intel格式中,格式中, 远程跳转远程跳转/调用指令
95、的助记符为调用指令的助记符为“call/jmp far”,而在,而在AT&T的汇编的汇编 语言中,相应的指令为语言中,相应的指令为“lcall”和和“ljmp”。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介AT&T中寄存器的命名中寄存器的命名 在在AT&T汇编语言中,寄存器操作数汇编语言中,寄存器操作数总是以总是以%作为前缀。作为前缀。80386芯片的芯片的 寄存器包括:寄存器包括: 8个个32位寄存器:位寄存器:%eax,%ebx,%ecx,%edx,%edi,%esi, %ebp,%esp 8个个16位寄存器:位寄存器:%ax,%bx,%cx,%dx,%si,%di,%b
96、p, %sp8个个8位寄存器:位寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh, %dl 6个段寄存器:个段寄存器:%cs,%ds,%es,%ss,%fs,%gs LinuxLinux汇编语言开发指南简介汇编语言开发指南简介3个控制寄存器:个控制寄存器:%cr0,%cr1,%cr26个调试寄存器:个调试寄存器:%db0,%db1,%db2,%db3,%db6,%db72个测试寄存器:个测试寄存器:%tr6,%tr78个浮点寄存器栈:个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3), %st(4),%st(5),%st(6),%st(7) *注:我对这些寄存
97、器并不是都了解,这些资料只是摘自注:我对这些寄存器并不是都了解,这些资料只是摘自as.info文档。文档。 如果真的需要寄存器命名的资料,我想可以参如果真的需要寄存器命名的资料,我想可以参考一下相应考一下相应GNU工具的机工具的机 器描述方面的源文件。器描述方面的源文件。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介AT&T中的操作码前缀中的操作码前缀段超越前缀段超越前缀cs,ds,es,ss,fs,gs:当汇编程序中对内当汇编程序中对内 存操作数进行存操作数进行SECTION:MEMORY-OPERAND引用时,自动加上相应的段超引用时,自动加上相应的段超 越前缀。越前缀。操
98、作数操作数/地址尺寸前缀地址尺寸前缀data16,addr16:这些前缀将:这些前缀将32位的操作数位的操作数/地址转化为地址转化为16位的操作数位的操作数/地址。地址。总线锁定前缀总线锁定前缀lock:总线锁定操作。总线锁定操作。lock前缀在前缀在Linux核心代码核心代码 中使用很多,特别是中使用很多,特别是SMP代码中。代码中。协处理器等待前缀协处理器等待前缀wait:等待协处理器完成当前操作。:等待协处理器完成当前操作。 指令重复前缀指令重复前缀rep,repe,repne:在串操作中重复指令:在串操作中重复指令的执行。的执行。 LinuxLinux汇编语言开发指南简介汇编语言开发指
99、南简介AT&T中的内存操作数中的内存操作数 在在Intel的汇编语言中,内存操作的汇编语言中,内存操作数引用的格式如下:数引用的格式如下: SECTION:BASE + INDEX*SCALE + DISP而在而在AT&T的汇编语言中,内存操作数的应用格式则是这的汇编语言中,内存操作数的应用格式则是这样的:样的: SECTION:DISP(BASE,INDEX,SCALE) 下面是一些内存下面是一些内存操作数的例子:操作数的例子: *AT&T *Intel -4(%ebp) ebp-4 foo(,%eax,4) foo+eax*4 foo(,1) foo %gs:foo gs:foo 还有,绝
100、对跳转还有,绝对跳转/调用指令中的内存调用指令中的内存操作数必须以操作数必须以*最为前缀,否则最为前缀,否则 as总是假设这是一个相总是假设这是一个相对跳转对跳转/调用指令。调用指令。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介AT&T中的跳转指令中的跳转指令 as汇编程序自动对跳转指令进汇编程序自动对跳转指令进行优化,总是使用尽可能小的跳转偏移行优化,总是使用尽可能小的跳转偏移 量。如果量。如果8比比特的偏移量无法满足要求的话,特的偏移量无法满足要求的话,as会使用一个会使用一个32位位的偏的偏 移量,移量,as汇编程序暂时还不支持汇编程序暂时还不支持16位的跳转偏位的跳转
101、偏移量,所以对跳转指令移量,所以对跳转指令 使用使用addr16前缀是无效的。前缀是无效的。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介还有一些跳转指令只支持还有一些跳转指令只支持8位的跳转偏移量,这些指令包括:位的跳转偏移量,这些指令包括:jcxz, jecxz,loop,loopz,loope,loopnz和和loopne。所以,。所以, 在在as的汇编源程序中使用这些指令可能会出错。(幸运的是,的汇编源程序中使用这些指令可能会出错。(幸运的是,gcc并并 不使用这些指令)不使用这些指令) 对对AT&T汇编语言语法的简单介绍差不多了,其中有些特性是汇编语言语法的简单介绍差
102、不多了,其中有些特性是as特有的。特有的。 在在Linux核心代码中,并不涉及到所有上面这些提到的核心代码中,并不涉及到所有上面这些提到的语法规则,其语法规则,其 中有两点规则特别重要:第一中有两点规则特别重要:第一,as中对寄存器引用中对寄存器引用时使用前缀时使用前缀%;第;第 二,二,AT&T汇编语言中源操作数和目标操作数汇编语言中源操作数和目标操作数的位置与我们熟悉的的位置与我们熟悉的Intel 的语法正好相反。的语法正好相反。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介四四.gcc的内嵌汇编语言语句的内嵌汇编语言语句asm 利用利用gcc的的asm语语句,你可以在句,
103、你可以在C语言代码中直接嵌入汇编语言指令,语言代码中直接嵌入汇编语言指令, 同时还可以使用同时还可以使用C语言的表达式指定汇编指令所用到的语言的表达式指定汇编指令所用到的操作数。这一操作数。这一 特性提供了很大的方便。特性提供了很大的方便。 要使用这一特要使用这一特性,首先要写一个汇编指令的模板(这种模板有点类性,首先要写一个汇编指令的模板(这种模板有点类似似 于机器描述文件中的指令模板),然后要为每一个于机器描述文件中的指令模板),然后要为每一个操作数指定一个限操作数指定一个限 定字符串。定字符串。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介例如:例如: extern _i
104、nline_ void change_bit(int nr,volatile void *addr) _asm_ _volatile_( LOCK_PREFIX “btcl %1,%0” :“=m” (ADDR) :“ir” (nr); 上面的函数中:上面的函数中: LOCK_PREFIX:这是一个宏,如果定义了:这是一个宏,如果定义了_SMP_,扩展为,扩展为“lock;”, 用于指定总线锁定前缀,否则扩展为用于指定总线锁定前缀,否则扩展为“”。 ADDR:这也是一个宏,定义为:这也是一个宏,定义为(*(volatile struct _dummy *) addr) btcl %1,%0:这
105、就是嵌入的汇编语言指令,:这就是嵌入的汇编语言指令,btcl为指令操为指令操作码,作码,%1, %0是这条指令两个操作数的占位符。后面的两个限定字是这条指令两个操作数的占位符。后面的两个限定字符串就用于描符串就用于描 述这两个操作数。述这两个操作数。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介: “=m” (ADDR):第一个冒号后的限定字符串用于描述指:第一个冒号后的限定字符串用于描述指令中的令中的“输输 出出”操作数。刮号中的操作数。刮号中的ADDR将操作数与将操作数与C语语言的变量联系起来。这个言的变量联系起来。这个 限定字符串表示指令中的限定字符串表示指令中的“%0”
106、就是就是addr指针指向的内存操作数。这指针指向的内存操作数。这 是一个是一个“输出输出”类类型的内存操作数。型的内存操作数。 : “ir” (nr):第二个冒号后的限定字符串用于描述指令:第二个冒号后的限定字符串用于描述指令中的中的“输入输入” 操作数。这条限定字符串表示指令中的操作数。这条限定字符串表示指令中的“%1”就是变量就是变量nr,这个的操,这个的操 作数可以是一个立即操作数或作数可以是一个立即操作数或者是一个寄存器操作数。者是一个寄存器操作数。 *注:限定字符串与操作数占位符之间的对应关系是这注:限定字符串与操作数占位符之间的对应关系是这样的:在所有样的:在所有 限定字符串中(包
107、括第一个冒号后的以限定字符串中(包括第一个冒号后的以及第二个冒号后的所有限定字及第二个冒号后的所有限定字 符串),最先出现的字符串),最先出现的字符串用于描述操作数符串用于描述操作数“%0”,第二个出现的字,第二个出现的字 符串描述操符串描述操作数作数“%1”,以此类推。,以此类推。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介汇编指令模板汇编指令模板 asm语句中的汇编指令模板主要由汇编指令语句中的汇编指令模板主要由汇编指令序列和限定字符串组成。序列和限定字符串组成。 在一个在一个asm语句中可以包括多条汇语句中可以包括多条汇编指令。汇编指令序列中使用操作编指令。汇编指令序列
108、中使用操作 数占位符引用数占位符引用C语言中的语言中的变量。一条变量。一条asm语句中最多可以包含十个操语句中最多可以包含十个操 作数占位符:作数占位符:%0,%1,.,%9。汇编指令序列后面是操作数限定字。汇编指令序列后面是操作数限定字 符串,对符串,对指令序列中的占位符进行限定。限定的内容包括:该占位符指令序列中的占位符进行限定。限定的内容包括:该占位符 与哪个与哪个C语言变量对应,可以是什么类型的操作数等等。语言变量对应,可以是什么类型的操作数等等。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介限定字符串限定字符串 可以分为三个部分:输出操作数限定字符串(指可以分为三个部分
109、:输出操作数限定字符串(指令序列后第一个冒号令序列后第一个冒号 后的限定字符串),输入操作数限定字后的限定字符串),输入操作数限定字符串(第一个冒号与第二个冒符串(第一个冒号与第二个冒 号之间),还有第三种类型的号之间),还有第三种类型的限定字符串在第二个冒号之后。同一种限定字符串在第二个冒号之后。同一种 类型的限定字符串之类型的限定字符串之间用逗号间隔。间用逗号间隔。asm语句中出现的第一个限定字语句中出现的第一个限定字 符串用于描符串用于描述占位符述占位符“%0”,第二个用于描述占位符,第二个用于描述占位符“%1”,以此类,以此类 推(不推(不管该限定字符串的类型)。如果指令序列中没有任何
110、输出操管该限定字符串的类型)。如果指令序列中没有任何输出操作作 数,那么在语句中出现的第一个限定字符串(该字符串用数,那么在语句中出现的第一个限定字符串(该字符串用于描述输入于描述输入 操作数)之前应该有两个冒号(这样,编译器就操作数)之前应该有两个冒号(这样,编译器就知道指令中没有输出知道指令中没有输出 操作数)。操作数)。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介指令中的输出操作数对应的指令中的输出操作数对应的C语言变量应该具有左值类型,当语言变量应该具有左值类型,当然对于然对于 输出操作数没有这种左值限制。输出操作数没有这种左值限制。 输出操作数必须是只写输出操作数必
111、须是只写的,也就是说,的,也就是说,asm对取出某个操作数,执行对取出某个操作数,执行 一定计算以后再一定计算以后再将结果存回该操作数这种类型的汇编指令的支持不是将结果存回该操作数这种类型的汇编指令的支持不是 直接的,直接的,而必须通过特定的格式的说明。如果汇编指令中包含了一个而必须通过特定的格式的说明。如果汇编指令中包含了一个 输输入入-输出类型的操作数,那么在模板中必须用两个占位符对该操输出类型的操作数,那么在模板中必须用两个占位符对该操作作 数的不同功能进行引用:一个负责输入,另一个负责输出。数的不同功能进行引用:一个负责输入,另一个负责输出。例如:例如: LinuxLinux汇编语言开
112、发指南简介汇编语言开发指南简介asm (addl %2,%0:=r(foo):0(foo),g(bar); 在上面在上面这条指令中,这条指令中,“%0”是一个输入是一个输入-输出类型的操作数,输出类型的操作数,=r(foo) 用于限定其输出功能,该指令的输出结果会存放到用于限定其输出功能,该指令的输出结果会存放到C语言变语言变量量foo中;中; 指令中没有显式的出现指令中没有显式的出现“%1”操作数,但是针对它操作数,但是针对它有一个限定字符串有一个限定字符串 0(foo),事实上指令中隐式的,事实上指令中隐式的“%1”操作操作数用于描述数用于描述“%0”操作数操作数 的输入功能,它的限定字符
113、串中的的输入功能,它的限定字符串中的0限定了限定了“%1”操作数与操作数与“%0” 具有相同的地址。可以这样理解具有相同的地址。可以这样理解上述指令中的模板:该指令将上述指令中的模板:该指令将“%1” 和和“%2”中的值相加,计算中的值相加,计算结果存放回结果存放回“%0”中,指令中的中,指令中的“%1”与与 “%0”具有相同的地址。具有相同的地址。注意,用于描述注意,用于描述“%1”的的0限定字符足以限定字符足以 保证保证“%1”与与“%0”具有具有相同的地址。但是,如果用下面的指令完成相同的地址。但是,如果用下面的指令完成 这种输入这种输入-输出输出操作就不会正常工作:操作就不会正常工作:
114、 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介asm (“addl %2,%0”:“=r”(foo):“r”(foo),“g”(bar); 虽然虽然该指令中该指令中“%0”和和“%1”同样引用了同样引用了C语言变量语言变量foo,但是,但是gcc并并 不保证在生成的汇编程序中它们具有相同的地址。不保证在生成的汇编程序中它们具有相同的地址。 还有一些汇编指令可能会改变某些寄存器的值,相应的汇还有一些汇编指令可能会改变某些寄存器的值,相应的汇编指令模板编指令模板 中必须将这种情况通知编译器。所以在模板中必须将这种情况通知编译器。所以在模板中还有第三种类型的限定中还有第三种类型的限定
115、 字符串,它们跟在输入操作数字符串,它们跟在输入操作数限定字符串的后面,之间用冒号间隔。限定字符串的后面,之间用冒号间隔。 这些字符串是某这些字符串是某些寄存器的名称,代表该指令会改变这些寄存器中的些寄存器的名称,代表该指令会改变这些寄存器中的 内内容。容。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介在内嵌的汇编指令中可能会直接引用某些硬件寄存器,我们在内嵌的汇编指令中可能会直接引用某些硬件寄存器,我们已经知道已经知道 AT&T格式的汇编语言中,寄存器名以格式的汇编语言中,寄存器名以“%”作为前缀,作为前缀,为了在生成的为了在生成的 汇编程序中保留这个汇编程序中保留这个“%”
116、号,在号,在asm语句中对语句中对硬件寄存器的引用必须硬件寄存器的引用必须 用用“%”作为寄存器名称的前缀。如果作为寄存器名称的前缀。如果汇编指令改变了硬件寄存器的汇编指令改变了硬件寄存器的 内容,不要忘记通知编译器内容,不要忘记通知编译器(在第三种类型的限定串中添加相应的字(在第三种类型的限定串中添加相应的字 符串)。还有一些符串)。还有一些指令可能会改变指令可能会改变CPU标志寄存器标志寄存器EFLAG的内容,那么的内容,那么 需要需要在第三种类型的限定字符串中加入在第三种类型的限定字符串中加入“cc”。 为了防止为了防止gcc在优化过程中对在优化过程中对asm中的汇编指令进行改变,中的汇
117、编指令进行改变,可以在可以在“asm” 关键字后加上关键字后加上“volatile”修饰符。修饰符。 可以在一条可以在一条asm语句中描述多条汇编语言指令;各条汇编指语句中描述多条汇编语言指令;各条汇编指令之间用令之间用 “;”或者或者“n”隔开。隔开。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介操作数限定字符操作数限定字符 操作数限定字符串中利用规定的限定字操作数限定字符串中利用规定的限定字符来描述相应的操作数,一些符来描述相应的操作数,一些 常用的限定字符有:(还有常用的限定字符有:(还有一些没有涉及的限定字符,参见一些没有涉及的限定字符,参见gcc.info) 1.“m
118、”:操作数是内存变量。操作数是内存变量。 2.“o”:操作数是内存变量,但它的寻址方式必须是操作数是内存变量,但它的寻址方式必须是“偏移量偏移量”类型的,类型的, 也就是基址寻址或者基址加变址寻址。也就是基址寻址或者基址加变址寻址。3.“V”:操作数是内存变量,其寻址方式非操作数是内存变量,其寻址方式非“偏移量偏移量”类型。类型。 4.“ ”:操作数是内存变量,其地址自动增量。操作数是内存变量,其地址自动增量。5.“r”:操作数是通用寄存器。操作数是通用寄存器。6.i:操作数是立即操作数。(其值可在汇编时确定)操作数是立即操作数。(其值可在汇编时确定) LinuxLinux汇编语言开发指南简介
119、汇编语言开发指南简介7.。“n”:操作数是立即操作数。有些系统不支持除字操作数是立即操作数。有些系统不支持除字(双字节双字节)以外以外的的 立即操作数,这些操作数要用立即操作数,这些操作数要用“n”而不是而不是“i”来描述。来描述。8 .“g”:操操作数可以是立即数,内存变量或者寄存器,只要寄存器属作数可以是立即数,内存变量或者寄存器,只要寄存器属 于通于通用寄存器。用寄存器。 9.“X”:操作数允许是任何类型。操作数允许是任何类型。10.“0”,“1”,.,“9”:操作数与某个指定的操作数匹配。也就是说,操作数与某个指定的操作数匹配。也就是说, 该操作数就是指定的那个操作数。例如,如果用该操
120、作数就是指定的那个操作数。例如,如果用“0”来描述来描述“%1”操操作作 数,那么数,那么“%1”引用的其实就是引用的其实就是“%0”操作数。操作数。 11.“p”:操作数是一个合法的内存地址(指针)。操作数是一个合法的内存地址(指针)。 12.“=”:操作数在指令中是只写的(输出操作数)。操作数在指令中是只写的(输出操作数)。 13.“+”:操作数在指令中是读操作数在指令中是读-写类型的(输入写类型的(输入-输出操作数)。输出操作数)。 14“a”:寄存器寄存器EAX。 15.“b”:寄存器寄存器EBX。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介16.“c”:寄存器寄存器
121、ECX。 17.“d”:寄存器寄存器EDX。 18.“q”:寄存器寄存器“a”,“b”,“c”或者或者“d”。 19.“A”:寄存器寄存器“a”或者或者“d”。 20.“a”:寄存器寄存器EAX。 21.“f”:浮点数寄存器。浮点数寄存器。 22.“t”:第一个浮点数寄存器。第一个浮点数寄存器。 23.“u”:第二个浮点数寄存器。第二个浮点数寄存器。 24.“D”:寄存器寄存器di。 25.“S”:寄存器寄存器si。 26.I:0-31之间的立即数。(用于之间的立即数。(用于32位的移位指令)位的移位指令) 27.“J”:0-63之间的立即数。(用于之间的立即数。(用于64位的移位指令)位的移
122、位指令) 28.“N”:0-255之间的立即数。之间的立即数。(用于用于“out”指令)指令) 29.G:标准的标准的80387浮点常数。浮点常数。 LinuxLinux汇编语言开发指南简介汇编语言开发指南简介一、汇编语言与高级语言的比较一、汇编语言与高级语言的比较特特 征征汇编语言汇编语言 高级语言高级语言目标程序运行时间目标程序运行时间短短长长程序运行时占用的存储空间程序运行时占用的存储空间小小大大直接访问硬件的能力直接访问硬件的能力强强弱弱程序可读性和可维护性程序可读性和可维护性差差强强程序开发的复杂程序程序开发的复杂程序高高低低程序的可移植性程序的可移植性差差强强对硬件环境和软件的兼容
123、性对硬件环境和软件的兼容性差差强强汇编语言与高级语言混合编程汇编语言与高级语言混合编程LinuxLinux汇编语言开发指南简介汇编语言开发指南简介二、嵌入式汇编二、嵌入式汇编 许多高级语言中包含了嵌入式汇编程序,允许在高级许多高级语言中包含了嵌入式汇编程序,允许在高级语言源程序内插入用汇编写的个别语句序列。语言源程序内插入用汇编写的个别语句序列。 1 Mcrosoft C 6.0 允许在允许在C语言程序中使用语言程序中使用_asm修饰符嵌修饰符嵌入汇编语言语句,其格式为:入汇编语言语句,其格式为: 单行汇编格式:单行汇编格式: _asm 汇编语言指令汇编语言指令 这里,这里, _asm 指明本
124、行内的后续部分为嵌入式汇编语言语句。指明本行内的后续部分为嵌入式汇编语言语句。 多行汇编格式:多行汇编格式: _asm 汇编语言语句序列汇编语言语句序列 这里,这里, _asm 指明其后大括号内的每一行都是嵌入式汇指明其后大括号内的每一行都是嵌入式汇编语言语句。编语言语句。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 2 Borland公司的公司的Turbo C提供了直接插入汇编指令,提供了直接插入汇编指令, 其格式为:其格式为: asm 汇编指令;或换行符汇编指令;或换行符 例例1: if ( i0) _asm mov al, 4 else i=7;三、库的使用三、库的使用
125、库文件可分为两种,一种是文本形式的汇编语言源程序库,另库文件可分为两种,一种是文本形式的汇编语言源程序库,另一种是二进制形式的目标文件库。一种是二进制形式的目标文件库。 对库文件的引用使用以下伪指令:对库文件的引用使用以下伪指令: INCLUDE 库文件名;引用汇编语言源程序库库文件名;引用汇编语言源程序库 INCLUDELIB 库文件名;引用二进制形式的目标文件库库文件名;引用二进制形式的目标文件库LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 四、汇编语言与四、汇编语言与C语言的接口语言的接口 在汇编语言与高级语言混合编程时,必须遵从一系列的接口约定。在汇编语言与高级语言混合
126、编程时,必须遵从一系列的接口约定。 1、符号的定义和说明、符号的定义和说明 字母的大小写:对字母的大小写字母的大小写:对字母的大小写C语言敏感,而汇编语言不敏感。语言敏感,而汇编语言不敏感。许多汇编程序的版本许多汇编程序的版本允许使用汇编开关通知汇编程序以大小写敏感允许使用汇编开关通知汇编程序以大小写敏感的方式进行汇编。的方式进行汇编。 下划线下划线 C语言产生的目标文件自动为语言产生的目标文件自动为每个标识符加入下划线作为前缀每个标识符加入下划线作为前缀,所,所以汇编程序中引用以汇编程序中引用C语言的符号时,应在符号名前增加一个下划线;语言的符号时,应在符号名前增加一个下划线;而在汇编中定义
127、需要由而在汇编中定义需要由C语言引用的符号时,也要在符号名前加一个语言引用的符号时,也要在符号名前加一个下划线。下划线。 例例2:在:在C语言中定义如下变量语言中定义如下变量 intnumber 则在汇编程序中需要用带有下划线的该变量:则在汇编程序中需要用带有下划线的该变量: mov _number,axLinuxLinux汇编语言开发指南简介汇编语言开发指南简介2、全局符号的定义和说明、全局符号的定义和说明 C语言中符号的定义和说明:按语言中符号的定义和说明:按C语言版本的规定。语言版本的规定。 汇编语言中符号的定义和说明汇编语言中符号的定义和说明 如果如果C语言中使用汇编中的变量或函数,语
128、言中使用汇编中的变量或函数,则在汇编中使则在汇编中使用用PUBLIC或或EXTERNDEF伪指令说明变量名或函数名具伪指令说明变量名或函数名具有公共属性。有公共属性。 反之,在汇编中使用反之,在汇编中使用C中定义的变量或函数,应在汇编中中定义的变量或函数,应在汇编中 使用使用EXTERNDEF或或EXTERN伪指令说明。伪指令说明。3、段和内存模式、段和内存模式 由于高级语言中段是由编译程序自动生成段结构,因此在由于高级语言中段是由编译程序自动生成段结构,因此在混合编程中,应使汇编程序遵从高级语言的段约定。混合编程中,应使汇编程序遵从高级语言的段约定。4、寄存器的约定、寄存器的约定 在混合编程
129、中,在混合编程中,CPU内部的寄存器也是公共资源。所以在内部的寄存器也是公共资源。所以在互相调用时,要注意如何使用和保护互相调用时,要注意如何使用和保护CPU寄存器的问题。寄存器的问题。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介5、参数的传递、参数的传递 C语言中执行函数调用时,函数的入口参数是通过堆栈传递的。语言中执行函数调用时,函数的入口参数是通过堆栈传递的。 参数类型:参数类型:C语言参数类型见下表:语言参数类型见下表:原始类型原始类型入栈类型入栈类型在堆栈中占用的字节数在堆栈中占用的字节数charint2unisigned charunisigned int2shor
130、t , intint2unisigned short, unisigned unisigned int2longlong4unisigned long unisigned long4floatfloat/double4/8doubledouble8near(指针)(指针)near(指针)(指针)2far(指针)(指针)far(指针)(指针)4array数组起始地址数组起始地址2或或4struct按值入栈按值入栈union按值入栈按值入栈LinuxLinux汇编语言开发指南简介汇编语言开发指南简介80x86的堆栈是以字为的单位的,故的堆栈是以字为的单位的,故相应的类型入栈时都要相应的类型入栈时都
131、要转换成相应的转换成相应的16位数据类型。位数据类型。 函数的调用规则函数的调用规则 C语言的压栈顺序是按从右到左的顺序将参数依次入栈。语言的压栈顺序是按从右到左的顺序将参数依次入栈。对调用者和被调用者而言,当调用返回时,由压入参数的调用对调用者和被调用者而言,当调用返回时,由压入参数的调用者负责将堆栈中的参数废弃。者负责将堆栈中的参数废弃。 例例3:在:在C语言中定义如下的函数:语言中定义如下的函数: void test (char a, int *b, char c ) 并给出下面调用:并给出下面调用: test ( i , &j, k); 在本例中,数组在本例中,数组k作为参数传递时,将
132、作为参数传递时,将k的起始地址的近指的起始地址的近指针两个字节入栈;指针针两个字节入栈;指针j的缺省属性也是的缺省属性也是near,也占两个字节入也占两个字节入栈;字符型参数栈;字符型参数 i 扩展成两个字节入栈。扩展成两个字节入栈。故在调用返回时,故在调用返回时,由调用代码执行一条加法指令将堆栈指针加由调用代码执行一条加法指令将堆栈指针加6,以便从堆栈中,以便从堆栈中清除入栈时占用的堆栈空间。清除入栈时占用的堆栈空间。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介6、返回值传递规则、返回值传递规则 在在C语言和汇编语言相互调用过程中,还需考虑函数返回值的传递。语言和汇编语言相互
133、调用过程中,还需考虑函数返回值的传递。C语言的函数返回值是通过约定寄存器传递的。如下表所示:语言的函数返回值是通过约定寄存器传递的。如下表所示:返回值类型返回值类型返回值的位置返回值的位置char, unsigned charALshort, unsigned shortAXint, unsigned intAXlong, unsigned longDX:AXfloat, double静态存储器(静态存储器(AX或或DX:AX指向存储器起始地址)指向存储器起始地址)指针指针 near far AXDX:AX结构结构 1字节字节和联合和联合 2字节字节 4字节字节 4字节以上字节以上ALAXDX
134、:AX静态存储器(静态存储器(AX或或DX:AX指向存储器起始地址)指向存储器起始地址)LinuxLinux汇编语言开发指南简介汇编语言开发指南简介五、在五、在C语言中调用汇编程序语言中调用汇编程序 在在C语言中调用汇编语言中定义的数据或过程,必须让汇编语言遵从语言中调用汇编语言中定义的数据或过程,必须让汇编语言遵从C语言的接口约定。例语言的接口约定。例4:许多应用程序用:许多应用程序用C语言实现人机接口,而实际的数语言实现人机接口,而实际的数据处理通过汇编子程序完成。据处理通过汇编子程序完成。 本例要求在本例要求在C语言主程序中从键盘上输入一个两位十进制数,再调用汇语言主程序中从键盘上输入一
135、个两位十进制数,再调用汇编子程序将该数的个位与十位数互换,并返回到编子程序将该数的个位与十位数互换,并返回到C语言主程序中打印结果。语言主程序中打印结果。C语言主程序:语言主程序: #include “stdio.h”extern unsigned char reverse (unsigned char byte); main( ) unsigned char byte;printf(“nEnter an unsigned byte: ”); scanf(“%d, &byte); printf(“The reversed byte is: %d n ”, reverse(byte); 汇编语言
136、程序如下:汇编语言程序如下: LinuxLinux汇编语言开发指南简介汇编语言开发指南简介.model small;模式定义,定义为小模式;模式定义,定义为小模式.code;简化码段定义;简化码段定义public_reverse_reverse proc; C callable function to reverse a byte; C prototype:extern unsigned char reverse(unsigned char byte); pushbp movbp, sp movbh, 10 moval, bp+4;取参数时应从栈顶退回;取参数时应从栈顶退回4个字节个字节 xo
137、r ah, ah div bh;用;用10整除该数,分离出个位和十位整除该数,分离出个位和十位 movbl, al moval, ah mulbh addal, bl ;两者交换后,再重新组合成两位十进制数;两者交换后,再重新组合成两位十进制数 popbp ret _reverse endp endLinuxLinux汇编语言开发指南简介汇编语言开发指南简介六、在汇编语言中调用六、在汇编语言中调用C语言程序语言程序 混合编程多数都是混合编程多数都是C语言中调用汇编,只有在特殊情语言中调用汇编,只有在特殊情况下从汇编语言中调用况下从汇编语言中调用C语言的函数或数据。语言的函数或数据。例例5:本例
138、以:本例以C语言模块作为启动主模块,其中有调用汇语言模块作为启动主模块,其中有调用汇编语言函数的语句。而在汇编语言函数中,通过编语言函数的语句。而在汇编语言函数中,通过DOS调调用,取得当前的系统时间,并调用用,取得当前的系统时间,并调用C语言的标准格式输出语言的标准格式输出函数函数printf打印当前的时间。打印当前的时间。 C语言模块语言模块“#include externvoid get_date(void);main( ) get_date( ); 汇编语言模块:汇编语言模块:LinuxLinux汇编语言开发指南简介汇编语言开发指南简介.model small;简化段定义,定义为小模式
139、简化段定义,定义为小模式extern_printf:procpublic_get_date.data;简化数据段定义;简化数据段定义format db 0ah,”Today is %u/%u/%u.”,0ah,0dh ;输出输出格式格式.code;简化代码段定义;简化代码段定义_get_data procmovah, 2ah;取系统时间,年、月、日;取系统时间,年、月、日int21h;分别在;分别在cx、dh、dl总总xorbh, bhmovbl, dl;DL中返回中返回“日日”pushbxmovbl, dh;DH中返回中返回“月月”pushbxmovbx, cx ;CX中返回中返回“年年”p
140、ushbxmovbx, offset format;输出格式;输出格式LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 pushbx call _printf;printf (“nToday is %u/%u/%u.n”,year,month,day)addsp, 8ret_get_date endpend七、简化段定义七、简化段定义 在高版本的宏汇编语言中增加了许多新的伪指令,下面只介绍有关模块定在高版本的宏汇编语言中增加了许多新的伪指令,下面只介绍有关模块定义和段定义伪指令。义和段定义伪指令。 1、模块定义伪指令(、模块定义伪指令(.MODEL) 格式:格式: .MODEL 存
141、储模式存储模式 ,语言类型,语言类型,操作系统类型,操作系统类型,堆栈类型,堆栈类型 (1)存储模式)存储模式 微模式微模式tiny、小模式、小模式small、中模式、中模式medium、紧缩模式、紧缩模式compact、LinuxLinux汇编语言开发指南简介汇编语言开发指南简介 大模式大模式large、巨模式、巨模式huge 各种存储模式的比较见下表:各种存储模式的比较见下表:存储模式存储模式代码段代码段数据段数据段容量限制容量限制代码的距代码的距离属性离属性数据的距数据的距离属性离属性tiny代码和数据组合成一个代码和数据组合成一个段段代码代码+数据数据64KNearnearsmall1
142、1代码代码 64K数据数据 64Knearnearmedium多个多个1数据数据 64Kfarnearcompact1多个多个代码代码 64Knearfarlarge多个多个多个多个无无farfarhuge多个多个多个多个无无farfarLinuxLinux汇编语言开发指南简介汇编语言开发指南简介(2)语言类型,指明语言类型,如)语言类型,指明语言类型,如C、BASIC、PASCAL、FORTRAN等。等。(3)操作系统类型,指明生成的目标代码在哪一种操作系)操作系统类型,指明生成的目标代码在哪一种操作系统上运行。统上运行。 (4)堆栈类型,该参数指定堆栈段和数据段是否组合在同)堆栈类型,该参
143、数指定堆栈段和数据段是否组合在同一物理段中。一物理段中。2、段的定义、段的定义 一旦使用了一旦使用了.MODEL伪指令定义了存储模式,就可以使伪指令定义了存储模式,就可以使用简化段定义伪指令定义段。用简化段定义伪指令定义段。 代码段由代码段由. CODE伪指令定义,其格式为:伪指令定义,其格式为: . CODE 段名段名;段名为;段名为 _TEXT或模块名或模块名_TEXT 堆栈段由堆栈段由.STACK伪指令定义,其格式为:伪指令定义,其格式为: . STACK 大小大小;缺省为;缺省为1024字节,段名字节,段名STACKLinuxLinux汇编语言开发指南简介汇编语言开发指南简介 数据段定
144、义有三条伪指令:数据段定义有三条伪指令: (1).DATA:定义初始化的数据段:定义初始化的数据段 格式:格式: .DATA段名:段名:_DATA (2) .DATA?:定义未初始化的数据段?:定义未初始化的数据段格式:格式: .DATA?段名:段名:_BSS (3).CONST:定义常数数据段:定义常数数据段 格式:格式: .CONST段名:段名: CONST例例6:使用简化段定义,调用:使用简化段定义,调用DOS的字符串显示输出中断的字符串显示输出中断向屏幕输出一个字符串。向屏幕输出一个字符串。LinuxLinux汇编语言开发指南简介汇编语言开发指南简介.MODELsmall;定义存储模式
145、:小模式;定义存储模式:小模式.STACK256;定义堆栈段,;定义堆栈段,256字节字节.DATA;定义数据段;定义数据段msgdb“This program illustrates the usage of”db“complete segment control directives.”db0dh,0ahdb“$”.CODE;定义代码段;定义代码段movax,datamovds,axmovah,09hmovdx, offset msgint21hmovax,4c00hint21hENDLinuxLinux汇编语言开发指南简介汇编语言开发指南简介附录:附录:有符号十进制数转换为二进制数子程序
146、有符号十进制数转换为二进制数子程序F10T2n位有符号十进制数(包括符号位)有三种形式:位有符号十进制数(包括符号位)有三种形式:d1d2 d3 dn-1 ,+ d1d2 d3 dn-1 ,- d1d2 d3 dn-1 其中其中d(i=1,2, ,n-1)为为0-9中的一个数字。中的一个数字。 在十到二进制数转换时,首先记下其符号,然后将数在十到二进制数转换时,首先记下其符号,然后将数字部分字部分d1d2 d3 dn-1转换成二进制数转换成二进制数 。 d1d2 d3 dn-1可用可用如下公式表示:如下公式表示: ( ( d1*10+ d2)*10+ )*10+ dn-1 上面计算得到的是该数
147、的绝对值。如果符号为负,则将上面计算得到的是该数的绝对值。如果符号为负,则将绝对值求补,得到所求二进制数的补码表示,否则绝对绝对值求补,得到所求二进制数的补码表示,否则绝对值就是该数的二进制数补码表示。值就是该数的二进制数补码表示。 设十进制数的符号及其数字的设十进制数的符号及其数字的ASCII码按高位在前,低码按高位在前,低位在后的顺序放在由位在后的顺序放在由SI指示的字节存储器中,十进制数的指示的字节存储器中,十进制数的位数放在位数放在CX中,转换后的结果在中,转换后的结果在AX中,则子程序中,则子程序F10T2可编程如下:可编程如下:LinuxLinux汇编语言开发指南简介汇编语言开发指
148、南简介datasegmentdatdw10signdb?dataendsf10t2 procfarpush dxpush bxmovax,0movsign,0movbl,si;取一;取一ASCII码送码送BLcmpbl,+jzA;若为正号,转;若为正号,转Acmpbl,-jnznext2 ;不是正号、负号,转;不是正号、负号,转next2movsign,1;为负号,;为负号,1送送signdeccx;ASCII码个数减码个数减1LinuxLinux汇编语言开发指南简介汇编语言开发指南简介next1:incsimovbl,sinext2:cmpbl,30hjbD1 ;若不是;若不是0-9的的AS
149、CII码,即码,即 cmp bl,39h ; 为非法数,转为非法数,转D1jaD1 subbl,30h;是字符;是字符0-9,将其真值,将其真值0-9送送bxmovbh,0muldat;(ax)*10送送dx,axjoD1;若溢出,转;若溢出,转D1addax,bx;(;(ax)+(bx)送送ax,jcD1;若有进位,表示溢出,转;若有进位,表示溢出,转D1deccxjnenext1 ;cx不为不为0,转,转next1,继续转换继续转换LinuxLinux汇编语言开发指南简介汇编语言开发指南简介cmpsign,1jneQQ;当被转换数为负数时,(;当被转换数为负数时,(AX)求补)求补nega
150、xQQ:pop bxpopdxretD1: movsi,-1 ;-1送送si表示为非法十进制数或溢出表示为非法十进制数或溢出jmpQQF10T2ENDP 对子程序对子程序f10t2的调用如下:的调用如下: stacksegmentdb200 dup(0)stackendsLinuxLinux汇编语言开发指南简介汇编语言开发指南简介datasegmentbufodb PLEASE INPUT DATA:,0DH,0AH,$buf1db 7db?db7 dup(0)bufferdb0dh,0ah,ERROR!,$dataendscodesegmentassume cs:code,ds:data,s
151、s:stackbegin:movax,datamovds,axmovax,stackmovss,axLinuxLinux汇编语言开发指南简介汇编语言开发指南简介leadx,bufomovah,9int21hleadx,buf1movah,0ahint21hleasi,buf1+2;待转换数的存储器首地址送;待转换数的存储器首地址送SImovcl,si-1 ;待转换数的位数送;待转换数的位数送CXmovch,0callf10t2cmpsi,-1;若为未溢出的合法十进制数,转;若为未溢出的合法十进制数,转exitjneexitleadx,buffer;溢出或非法的十进制数,显示出错;溢出或非法的十进制数,显示出错LinuxLinux汇编语言开发指南简介汇编语言开发指南简介