转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 数据库 >> SyBase >> 正文
Linux系统汇编语言开发指南         ★★★★

Linux系统汇编语言开发指南

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1461 更新时间:2009/4/22 23:08:53
mov eax, 0x1

若想获得 ALD 支持的所有调试命令的详细列表,可以使用 help 命令:


ald> help
Commands may be abbreviated.
If a blank command is entered, the last command is repeated.
Type `help <command>'''' for more specific information on <command>.

General commands
attach         clear          continue       detach         disassemble
enter          examine        file           help           load
next           quit           register       run            set
step           unload         window         write

Breakpoint related commands
break          delete         disable        enable         ignore
lbreak         tbreak

五、系统调用

即便是最简单的汇编程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运算,否则将很难避免使用系统调用,事实上除了系统调用不同之外,各种操作系统的汇编编程往往都是很类似的。

在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。其中通过汇编语言来直接调用系统调用,是最高效地使用 Linux 内核服务的方法,因为最终生成的程序不需要与任何库进行链接,而是直接和内核通信。

和 DOS 一样,Linux 下的系统调用也是通过中断(int 0x80)来实现的。在执行 int 80 指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。

所有的系统调用功能号都可以在文件 /usr/include/bits/syscall.h 中找到,为了便于使用,它们是用 SYS_<name> 这样的宏来定义的,如 SYS_write、SYS_exit 等。例如,经常用到的 write 函数是如下定义的:


ssize_t write(int fd, const void *buf, size_t count);

该函数的功能最终是通过 SYS_write 这一系统调用来实现的。根据上面的约定,参数 fb、buf 和 count 分别存在寄存器 ebx、ecx 和 edx 中,而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完毕后,返回值可以从寄存器 eax 中获得。

或许你已经发现,在进行系统调用时至多只有 5 个寄存器能够用来保存参数,难道所有系统调用的参数个数都不超过 5 吗?当然不是,例如 mmap 函数就有 6 个参数,这些参数最后都需要传递给系统调用 SYS_mmap:


void  *  mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

当一个系统调用所需的参数个数大于 5 时,执行int 0x80 指令时仍需将系统调用功能号保存在寄存器 eax 中,所不同的只是全部参数应该依次放在一块连续的内存区域里,同时在寄存器 ebx 中保存指向该内存区域的指针。系统调用完成之后,返回值仍将保存在寄存器 eax 中。

由于只是需要一块连续的内存区域来保存系统调用的参数,因此完全可以像普通的函数调用一样使用栈(stack)来传递系统调用所需的参数。但要注意一点,Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。如果采用栈来传递系统调用所需的参数,在执行int 0x80 指令时还应该将栈指针的当前值复制到寄存器 ebx中。

六、命令行参数

在 Linux 操作系统中,当一个可执行程序通过命令行启动时,其所需的参数将被保存到栈中:首先是 argc,然后是指向各个命令行参数的指针数组 argv,最后是指向环境变量的指针数据 envp。在编写汇编语言程序时,很多时候需要对这些参数进行处理,下面的代码示范了如何在汇编代码中进行命令行参数的处理:

例3. 处理命令行参数


# args.s
.text
.globl _start
        
_start:
        popl	%ecx		# argc

vnext:
        popl	%ecx		# argv
        test 	%ecx, %ecx      # 空指针表明结束
        jz	exit

        movl	%ecx, %ebx
        xorl	%edx, %edx
strlen:
        movb	(%ebx), %al
        inc	%edx
        inc	%ebx
        test	%al, %al
        jnz	strlen
        movb	$10, -1(%ebx)

        movl	$4, %eax        # 系统调用号(sys_write) 
        movl	$1, %ebx        # 文件描述符(stdout) 
        int	$0x80

        jmp	vnext

exit:
        movl	$1,%eax         # 系统调用号(sys_exit) 
        xorl	%ebx, %ebx      # 退出代码
        int 	$0x80
		
        ret

七、GCC 内联汇编

用汇编编写的程序虽然运行速度快,但开发速度非常慢,效率也很低。如果只是想对关键代码段进行优化,或许更好的办法是将汇编指令嵌入到 C 语言程序中,从而充分利用高级语言和汇编语言各自的特点。但一般来讲,在 C 代码中嵌入汇编语句要比"纯粹"的汇编语言代码复杂得多,因为需要解决如何分配寄存器,以及如何与C代码中的变量相结合等问题。

GCC 提供了很好的内联汇编支持,最基本的格式是:


__asm__("asm statements");

例如:


__asm__("nop"); 

如果需要同时执行多条汇编语句,则应该用"\\n\\t"将各个语句分隔开,例如:


__asm__( "pushl %%eax \\n\\t"
         "movl $0, %%eax \\n\\t"
         "popl %eax");

通常嵌入到 C 代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式:


__asm__("asm statements" : outputs : inputs : registers-modified);

插入到 C 代码中的汇编语句是以":"分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。

在将汇编语句嵌入到C代码中时,操作数如何与C代码中的变量相结合是个很大的问题。GCC采用如下方法来解决这个问题:程序员提供具体的指令,而对寄存器的使用则只需给出"样板"和约束条件就可以了,具体如何将寄存器与变量结合起来完全由GCC和GAS来负责。

在GCC内联汇编语句的指令部中,加上前缀''''%''''的数字(如%0,%1)表示的就是需要使用寄存器的"样板"操作数。指令部中使用了几个样板操作数,就表明有几个变量需要与寄存器相结合,这样GCC和GAS在编译和汇编时会根据后面给定的约束条件进行恰当的处理。由于样板操作数也使用''''%''''作为前缀,因此在涉及到具体的寄存器时,寄存器名前面应该加上两个''''%'''',以免产生混淆。

紧跟在指令部后面的是输出部,是规定输出变量如何与样板操作数进行结合的条件,每个条件称为一个"约束",必要时可以包含多个约束,相互之间用逗号分隔开就可以了。每个输出约束都以''''=''''号开始,然后紧跟一个对操作数类型进行说明的字后,最后是如何与变量相结合的约束。凡是与输出部中说明的操作数相结合的寄存器或操作数本身,在执行完嵌入的汇编代码后均不保留执行之前的内容,这是GCC在调度寄存器时所使用的依据。

输出部后面是输入部,输入约束的格式和输出约束相似,但不带''''=''''号。如果一个输入约束要求使用寄存器,则GCC在预处理时就会为之分配一个寄存器,并插入必要的指令将操作数装入该寄存器。与输入部中说明的操作数结合的寄存器或操作数本身,在执行完嵌入的汇编代码后也不保留执行之前的内容。

有时在进行某些操作时,除了要用到进行数据输入和输出的寄存器外,还要使用多个寄存器来保存中间计算结果,这样就难免会破坏原有寄存器的内容。在GCC内联汇编格式中的最后一个部分中,可以对将产生副作用的寄存器进行说明,以便GCC能够采用相应的措施。

下面是一个内联汇编的简单例子:

例4.内联汇编


/* inline.c */

int main()
{
    int a = 10, b = 0;

    __asm__ __volatile__("movl %1, %%eax;\\n\\r"
                         "movl %%eax, %0;"
                         :"=r"(b)      /* 输出 */    
                         :"r"(a)       /* 输入 */
                         :"%eax");     /* 不受影响的寄存器 */
    
    printf("Result: %d, %d\\n", a, b);
}

上面的程序完成将变量a的值赋予变量b,有几点需要说明:

  • 变量b是输出操作数,通过%0来引用,而变量a是输入操作数,通过%1来引用。
  • 输入操作数和输出操作数都使用r进行约束,表示将变量a和变量b存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符''''=''''。
  • 在内联汇编语句中使用寄存器eax时,寄存器名前应该加两个''''%'''',即%%eax。内联汇编中使用%0、%1等来标识变量,任何只带一个''''%''''的标识符都看成是操作数,而不是寄存器。
  • 内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。
  • 由于变量b被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。

在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上''''%''''作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为32位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:

限定符 意义 "m"、"v"、"o" 内存单元 "r" 任何寄存器 "q" 寄存器eax、ebx、ecx、edx之一 "i"、"h" 直接操作数 "E"和"F" 浮点数 "g" 任意 "a"、"b"、"c"、"d" 分别表示寄存器eax、ebx、ecx和edx "S"和"D" 寄存器esi、edi "I" 常数(0至31)

八、小结

Linux操作系统是用C语言编写的,汇编只在必要的时候才被人们想到,但它却是减少代码尺寸和优化代码性能的一种非常重要的手段,特别是在与硬件直接交互的时候,汇编可以说是最佳的选择。Linux提供了非常优秀的工具来支持汇编程序的开发,使用GCC的内联汇编能够充分地发挥C语言和汇编语言各自的优点。

九、参考资料

  1. 在网站http://linuxassembly.org上可以找到大量的Linux汇编资源。
  2. 软件包binutils提供了as和ld等实用工具,其相关信息可以在网站http://sources.redhat.com/binutils/上找到。
  3. NASM是Intel格式的汇编器,其相关信息可以在网站http://nasm.sourceforge.net上找到。
  4. ALD是一个短小精悍的汇编调试器,其相关信息可以在网站http://dunx1.irt.drexel.edu/~psa22/ald.html上找到。
  5. intel2gas是一个能够将Intel汇编格式转换成AT&T汇编格式的小工具,其相关信息可以在网站http://www.niksula.cs.hut.fi/~mtiihone/intel2gas/上找到。
  6. IBM developerWorks上有一篇介绍GCC内联汇编的文章(http://www-900.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index_eng.shtml)。
  7. 本文代码下载:代码

关于作者
本文作者肖文鹏是北京理工大学计算机系的一名硕士研究生,主要从事操作系统和分布式计算环境的研究,喜爱Linux和Python。你可以通过xiaowp@263.net与他取得联系。

上一页  [1] [2] 


没有相关教程
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · Sql Server  · MySql
    · Access  · ORACLE
    · SyBase  · 其他
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台