Linux下的汇编开发
热 ★★★★
Linux下的汇编开发
作者:闵涛 文章来源:闵涛的学习笔记 点击数:2356 更新时间:2009/4/22 20:48:21
AS 使用标准的 AT&T 汇编语法,可以用来汇编用 AT&T 格式编写的程序: [xiaowp@gary code]$ as -o hello.o hello.s Linux 平台上另一个经常用到的汇编器是 NASM,它提供了很好的宏指令功能,并能够支持相当多的目标代码格式,包括 bin、a.out、coff、elf、rdf 等。NASM 采用的是人工编写的语法分析器,因而执行速度要比 GAS 快很多,更重要的是它使用的是 Intel 汇编语法,可以用来编译用 Intel 语法格式编写的汇编程序: [xiaowp@gary code]$ nasm -f elf hello.asm 2.链接器 由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过链接器的处理才能生成可执行代码。链接器通常用来将多个目标代码连接成一个可执行代码,这样可以先将整个程序分成几个模块来单独开发,然后才将它们组合(链接)成一个应用程序。 Linux 使用 ld 作为标准的链接程序,它同样也包含在 binutils 软件包中。汇编程序在成功通过 GAS 或 NASM 的编译并生成目标代码后,就可以使用 ld 将其链接成可执行程序了: [xiaowp@gary code]$ ld -s -o hello hello.o 3.调试器 有人说程序不是编出来而是调出来的,足见调试在软件开发中的重要作用,在用汇编语言编写程序时尤其如此。Linux 下调试汇编代码既可以用 GDB、DDD 这类通用的调试器,也可以使用专门用来调试汇编代码的 ALD(Assembly Language Debugger)。 从调试的角度来看,使用 GAS 的好处是可以在生成的目标代码中包含符号表(symbol table),这样就可以使用 GDB 和 DDD 来进行源码级的调试了。要在生成的可执行程序中包含符号表,可以采用下面的方式进行编译和链接: [xiaowp@gary code]$ as --gstabs -o hello.o hello.s [xiaowp@gary code]$ ld -o hello hello.o 执行 as 命令时带上参数 --gstabs 可以告诉汇编器在生成的目标代码中加上符号表,同时需要注意的是,在用 ld 命令进行链接时不要加上 -s 参数,否则目标代码中的符号表在链接时将被删去。 在 GDB 和 DDD 中调试汇编代码和调试 C 语言代码是一样的,你可以通过设置断点来中断程序的运行,查看变量和寄存器的当前值,并可以对代码进行单步跟踪。图1 是在 DDD 中调试汇编代码时的情景: 图1 用 DDD 中调试汇编程序 汇编程序员通常面对的都是一些比较苛刻的软硬件环境,短小精悍的ALD可能更能符合实际的需要,因此下面主要介绍一下如何用ALD来调试汇编程序。首先在命令行方式下执行ald命令来启动调试器,该命令的参数是将要被调试的可执行程序: [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> 当 ALD 的提示符出现之后,用 disassemble 命令对代码段进行反汇编: 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 上述输出信息的第一列是指令对应的地址码,利用它可以设置在程序执行时的断点: ald> break 0x08048088 Breakpoint 1 set for 0x08048088 断点设置好后,使用 run 命令开始执行程序。ALD 在遇到断点时将自动暂停程序的运行,同时会显示所有寄存器的当前值: 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 Flags: PF ZF IF 08048088 CD80 int 0x80 如果需要对汇编代码进行单步调试,可以使用 next 命令: ald> next Hello, world! eax = 0x0000000F ebx = 0x00000000 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 = 0x0804808F eflags = 0x00000346 Flags: PF ZF TF IF 0804808F B801000000 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: &nbs上一页 [1] [2] [3] [4] 下一页
没有相关教程
教程录入:mintao 责任编辑:mintao
上一篇教程: 在Linux下访问MSSQLServer数据库 下一篇教程: learn linux programming with arden:00 铺垫
【字体:小 大 】【发表评论 】【加入收藏 】【告诉好友 】【打印此文 】【关闭窗口 】
注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
网友评论: (只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
同类栏目
赞助链接
500 - 内部服务器错误。
500 - 内部服务器错误。
您查找的资源存在问题,因而无法显示。