2008-07-24 14:50:00 wanghuiqing15202 阅读数 1061
  • ARM裸机1期加强版(linux

    1、 现场编写调试 2、假设学员0基础: 只会简单C语言 3、ARM裸板为主线,硬件知识,c语言为辅线,4、Linux命令,ARM硬件、汇编,重定位,链接脚本,gcc、Makefile 5、全新软件以及ubuntu系统

    7569 人正在学习 去看看 韦东山

 [wanghq@FundTrade ~]$ gcc -S hello.c         
[wanghq@FundTrade ~]$ gcc --gstabs -g  hello.s    
[wanghq@FundTrade ~]$ gdb a.out

 

 

GNU gdb Red Hat Linux (6.3.0.0-1.132.EL4rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) l
1               .file   "hello.c"
2               .section        .rodata
3       .LC0:
4               .string "orld"
5       .LC1:
6               .string "%s/n"
7               .text
8       .globl main
9               .type   main, @function
10      main:
(gdb) b 11
Breakpoint 1 at 0x8048368: file hello.s, line 11.
(gdb) r
Starting program: /usr/wanghq/a.out

Breakpoint 1, main () at hello.s:11
11              pushl   %ebp
Current language:  auto; currently asm
(gdb) n
12              movl    %esp, %ebp
(gdb) p $ebp
$1 = (void *) 0xbffa7e58
(gdb) p $esp
$2 = (void *) 0xbffa7df8
(gdb) n
main () at hello.s:13
13              subl    $8, %esp
(gdb)
main () at hello.s:14
14              andl    $-16, %esp
(gdb)
15              movl    $0, %eax
(gdb)
16              addl    $15, %eax
(gdb)
17              addl    $15, %eax
(gdb)
18              shrl    $4, %eax
(gdb)
19              sall    $4, %eax
(gdb)
20              subl    %eax, %esp
(gdb) p $esp
$3 = (void *) 0xbffa7df0
(gdb) n
main () at hello.s:21
21              subl    $8, %esp
(gdb) p $esp
$4 = (void *) 0xbffa7de0
(gdb) n
main () at hello.s:22
22              pushl   $.LC0
(gdb) p $esp
$5 = (void *) 0xbffa7dd8
(gdb) n
main () at hello.s:23
23              pushl   $.LC1
(gdb) p $esp
$6 = (void *) 0xbffa7dd4
(gdb) l
18              shrl    $4, %eax
19              sall    $4, %eax
20              subl    %eax, %esp
21              subl    $8, %esp
22              pushl   $.LC0
23              pushl   $.LC1
24              call    printf
25              addl    $16, %esp
26              movl    $0, %eax
27              leave
(gdb) n
main () at hello.s:24
24              call    printf
(gdb) p $esp
$7 = (void *) 0xbffa7dd0
(gdb) n
orld
25              addl    $16, %esp
(gdb) p $esp
$8 = (void *) 0xbffa7dd0
(gdb) n
main () at hello.s:26
26              movl    $0, %eax
(gdb) p $esp
$9 = (void *) 0xbffa7de0
(gdb) n
27              leave
(gdb) p $esp
$10 = (void *) 0xbffa7de0

 

 

 

hello.c文件

 

#include <stdio.h>

int main()
{
    printf("%s/n", "orld");
    return 0;
}

 

hello.s文件

 

    .file   "hello.c"
    .section    .rodata
.LC0:
    .string "orld"
.LC1:
    .string "%s/n"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    andl    $-16, %esp
    movl    $0, %eax
    addl    $15, %eax
    addl    $15, %eax
    shrl    $4, %eax
    sall    $4, %eax
    subl    %eax, %esp
    subl    $8, %esp
    pushl   $.LC0
    pushl   $.LC1
    call    printf
    addl    $16, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .section    .note.GNU-stack,"",@progbits
    .ident  "GCC: (GNU) 3.4.6 20060404 (Red Hat 3.4.6-3)"

 

 

 

 

 

 

 

2016-11-25 18:28:50 mozun1 阅读数 1424
  • ARM裸机1期加强版(linux

    1、 现场编写调试 2、假设学员0基础: 只会简单C语言 3、ARM裸板为主线,硬件知识,c语言为辅线,4、Linux命令,ARM硬件、汇编,重定位,链接脚本,gcc、Makefile 5、全新软件以及ubuntu系统

    7569 人正在学习 去看看 韦东山
反汇编有有以下几种方法:
1.使用gcc -S test.c 或者gcc -S test.c>out.txt
2.使用gdb调试,在调试中输入disass 函数名 就可以
3.objdump -D test 一般常用1,2两种,
~~~~~C语言代码example.c
int triangle( int width, int height)
{
int arr{0,1,2,3,4};
int area;
area = width * height /2;
return (area);
}
void main()
{
triangle(5,4);

}

~~~~~gdb反汇编代码
$ gdb example
(gdb) disass main
Dump of assembler code for function main:
0x080483f6 <+0>:    push   %ebp
0x080483f7 <+1>:    mov    %esp,%ebp
0x080483f9 <+3>:    sub    $0x8,%esp


目标码格式列表
--demangle
-C 将底层的符号名解码成用户级名字,除了去掉所有开头的下划线之外,还使得C++函数名以可理解的方式显示出来。

—disassemble或者-d 反汇编可执行段

.—dissassemble-all或者-D 反汇编所有段

--debugging显示调试信息。企图解析保存在文件中的调试信息并以C语言的语法显示出来。仅仅支持某些类型的调试信息。

--prefix-addresses反汇编的时候,显示每一行的完整地址。这是一种比较老的反汇编格式。显示效果并不理想,但可能会用到其中的某些显示,自己可以对比。

--disassemble-zeroes  一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编

-EB -EL  指定字节序  --endian={big|little}这个选项将影响反汇编出来的指令。little-endian就是我们当年在dos下玩汇编的时候常说的高位在高地址,x86都是这种。

--file-headers或者  -f 显示objfile中每个文件的整体头部摘要信息。

--section-headers;--headers 或者-h 显示目标文件各个section的头部摘要信息。

--info或者-i 显示对于 -b 或者 -m 选项可用的架构和目标格式列表。显示支持的目标文件格式和CPU架构


--section=name或者-j name 仅仅显示指定section的信息

--line-numbers或者-l 用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求编译时使用了-g之  类的调试编译选项。

--architecture=machine 或者-m machine指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述架构信息的时候(比如S-records),这个选项很有用。可以用-i选项列出这里能够指定的架构
--reloc或者-r 显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇编后的格式显示出来。

--dynamic-reloc    -R 显示文件的动态重定位入口,仅仅对于动态目标文件有意义,比如某些共享库。

--full-contents  -s 显示指定section的完整内容。

objdump --section=.text -s inet.o | more
--source    -S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。
--show-raw-insn反汇编的时候,显示每条汇编指令对应的机器码,除非指定了--prefix-addresses,这将是缺省选项。

--no-show-raw-insn


-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,

   效果比较明显。隐含了-d参数。

-l 用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用

  使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求

  编译时使用了-g之类的调试编译选项。

-j name 仅仅显示指定section的信息


如何使用linux下objdump命令对任意一个二进制文件进行反汇编?

可以使用如下命令:

objdump -D -b binary -m i386 a.bin

-D表示对全部文件进行反汇编,-b表示二进制,-m表示指令集架构,a.bin就是我们要反汇编的二进制文件

objdump -m可以查看更多支持的指令集架构,如i386:x86-64,i8086等

另外上面的所有objdump命令的参数同样适用于arm-linux-objdump。

同时我们也可以指定big-endian或little-endian(-EB或-EL),我们可以指定从某一个位置开始反汇编等。


objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍:

objdump -f test  显示test的文件头信息

objdump -d test  反汇编test中的需要执行指令的那些section

objdump -D test  与-d类似,但反汇编test中的所有section

objdump -h test  显示test的Section Header信息

objdump -x test  显示test的全部Header信息

objdump -s test  除了显示test的全部Header信息,还显示他们对应的十六进制文件代码



使用arm-linux 工具链里面的arm-linux-objdump 就能反汇编

cd到bin文件所在的目录, 在命令行下输入:

arm-linux-objdump -D -b binary -m arm xxx.bin > xxx.asm

参数:

-D 反编译所有代码

-m 主机类型, arm

-b 文件格式, binary

对于ELF格式的文件只要一个-D参数即可

就可以把xxx.bin反汇编到xxx.asm文件


Arm-linux-objdump –D elf_file > dis_file 或者

Arm-linux-objdump –D –b binary –m arm bin_file > dis_file


内存地址反向查找出问题的程序:

<1>.通过汇编去查找.
Linux 平台:
  1. 在程序信号处理部分, 加入代码捕捉引起错误点的地址,简单来说,方法就是在注册自己的信号处理函数,在这个函数中加入获取内存错误地址的代码,并把结果写到一个日志文件中。
  2. 编译 DEBUG 版本 程序 (compile 时用 -g , 生成可执行文件后不用 strip 去掉symbol 信息)
  3. 在程序出问题时, 查看日志记录, 得到错误点的地址.
  4. 用objdump -S 导出Debug 版本的汇编代码, 查找错误地址, 则得出那条语句出错.
windows 下c 语言调试
  1. release 版编译/连接选项, 把"generate debug info/" 打钩选择
  2.dumpbin /DISASM /OUT:dump.out.txt.1 prep.exe 可反编译exe文件
  3.得到程序非法地址(可从管理工具-》事件查看器里得到),与汇编比较。


对目标文件:***.o:

arm-none-Linux-gnueabi-objdump -D  ./kernel/sched.o > sched.S


对可执行文件***.bin:

arm-linux-objdump -D -b binary -m arm xxx.bin > xxx.asm


为了运行ARM汇编代码,需要使用交叉编译器arm-linux-gcc对ARM汇编代码进行编译。下载交叉编译器安装完毕后,对ARM汇编代码进行编译。

arm-linux-gcc main.s -o main -nostdlib

编译选项“-nostdlib”表示不使用任何运行时库文件,编译生成的可执行文件main只能在ARM体系结构的系统上运行。

通常认为,产生异常的地址是lr寄存器的值,从上面的异常信息可以看到[lr]的值是c01a4e30。

接下来,我们可以通过内核镜像文件反汇编来找到这个地址。内核编译完成后,会在内核代码根目录下生成vmlinux文件,我们可以通过以下命令来反汇编:

arm-none-eabi-objdump -Dz -S vmlinux >linux.dump

值得注意的是,arm-none-eabi-objdump的参数-S表示尽可能的把原来的代码和反汇编出来的代码一起呈现出来,-S参数需要结合 arm-linux-gcc编译参数-g,才能达到反汇编时同时输出原来的代码。所以,我在linux内核代码根目录的Makefile中增加-g编译参 数:

KBUILD_CFLAGS   := -g -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
     -fno-strict-aliasing -fno-common \
     -Werror-implicit-function-declaration \
     -Wno-format-security \
     -fno-delete-null-pointer-checks

修改Makefile后,重新编译内核,在根目录中生成的vmlinux文件就会包含了原来的代码信息,因此,该文件的大小也比原来大一倍!

最后执行“arm-none-eabi-objdump -Dz-S vmlinux >linux.dump”,由于加入了-g编译参数,执行这个反汇编命令需要很长时间(本人在虚拟机上执行,花了近6个小时!),反汇编出来的linux.dump文件也比原来的44MB增大到惊人的503MB。

(博主加:这里有一点要注意,如果是ko模块文件,反汇编时如果想看到ko文件的某函数反汇编代码,该函数不能加static关键字修饰,而且module_init修饰的入口函数,其名字即为module_init)

接下来可以用UltraEdit打开linux.dump文件,查找“c01a4e30”字符串。

最后定位到的信息是:

==================================================================================================================

/*
 * tasklet handling tty stuff outside the interrupt handler.
 */
static void atmel_tasklet_func(unsigned long data)
{
c01a4e20: e92d45f0  push {r4, r5, r6, r7, r8, sl, lr}
c01a4e24: e24dd01c  sub sp, sp, #28 ; 0x1c
c01a4e28: e1a04000  mov r4, r0
 /* The interrupt handler does not take the lock */
 spin_lock(&port->lock);

 if (atmel_use_pdc_tx(port))
  atmel_tx_pdc(port);
 else if (atmel_use_dma_tx(port))
c01a4e2c: ebfffda1  bl c01a44b8 <atmel_use_dma_tx>
c01a4e30: e3500000  cmp r0, #0 ; 0x0
c01a4e34: e5943034  ldr r3, [r4, #52]
c01a4e38: 0a00007b  beq c01a502c <atmel_tasklet_func+0x20c>

==================================================================================================================

可以看出来,异常的产生位于atmel_tasklet_func函数的 else if (atmel_use_dma_tx(port))一行

估计atmel_use_dma_tx(port)的“port”参数为空指针所致!






2005-08-02 14:29:00 expert2000 阅读数 1669
  • ARM裸机1期加强版(linux

    1、 现场编写调试 2、假设学员0基础: 只会简单C语言 3、ARM裸板为主线,硬件知识,c语言为辅线,4、Linux命令,ARM硬件、汇编,重定位,链接脚本,gcc、Makefile 5、全新软件以及ubuntu系统

    7569 人正在学习 去看看 韦东山

LINUX下汇编的编译:

nasm -f elf hello.asm    生成hello.o

ld  -o hello hello.o         生成可执行的elf格式文件hello,通过./hello执行

如果输入nasm hello.asm,得到二进制可执行文件hello

LINUX下汇编的调试:

调试主要是用的ALD(Assembly Language Debugger)

ald hello                           载入要调试的程序

disassemble -S .test    显示被调试程序的信息。第一行是命令地址,第二行是机器码,第三行是命令。

break  命令地址             设置断点,程序执行到该地址停止,并显示此时寄存器和标志位的值

设置完断点,用run命令执行程序,到断点处停止。继续执行到下一个断点,用continue,单步执行,用next。

退出ald,用quit,得到帮助信息,用help.

WINDOWS下汇编编译。

得到二进制可执行文件。

nasm -f bin hello.asm hello.com

2013-05-21 20:36:00 iteye_7332 阅读数 59
  • ARM裸机1期加强版(linux

    1、 现场编写调试 2、假设学员0基础: 只会简单C语言 3、ARM裸板为主线,硬件知识,c语言为辅线,4、Linux命令,ARM硬件、汇编,重定位,链接脚本,gcc、Makefile 5、全新软件以及ubuntu系统

    7569 人正在学习 去看看 韦东山

三、Linux 汇编工具


Linux平台下的汇编工具虽然种类很多,但同 DOS/Windows一样,最基本的仍然是汇编器、连接器和调试器。

1.汇编器
汇编器(assembler)的作用是将用汇编语言编写的源程序转换成二进制形式的目标代码。Linux平台的标准汇编器是
GAS,它是 GCC所依赖的后台汇编工具,通常包含在 binutils软件包中。GAS 使用标准的 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

使用宏

清单 3演示本节讨论的概念;它接受用户名作为输入并返回一句问候语。

清单 3.读取字符串并向用户显示问候语的程序

行号 NASM GAS
001 section .data .section .data
002
003 prompt_str db 'Enter your name: ' prompt_str:
004 .ascii "Enter Your Name: "
005 ; $ is the location counter pstr_end:
006 STR_SIZE equ $ -prompt_str .set STR_SIZE, pstr_end -prompt_str
007
008 greet_str db 'Hello ' greet_str:
009 .ascii "Hello "
010
011 GSTR_SIZE equ $ -greet_str gstr_end:
012 .set GSTR_SIZE, gstr_end -greet_str
013
014 section .bss .section .bss
015
016 ; Reserve 32 bytes of memory // Reserve 32 bytes of memory
017 buff resb 32 .lcomm buff, 32
018
019 ; A macro with two parameters // A macro with two parameters
020 ; Implements the write system call // implements the write system call
021 %macro write 2 .macro write str, str_size
022 mov eax, 4 movl $4, %eax
023 mov ebx, 1 movl $1, %ebx
024 mov ecx, %1 movl \str, %ecx


025 mov edx, %2 movl \str_size, %edx
026 int 80h int $0x80
027 %endmacro .endm
028
029
030 ; Implements the read system call // Implements the read system call
031 %macro read 2 .macro read buff, buff_size
032 mov eax, 3 movl $3, %eax
033 mov ebx, 0 movl $0, %ebx
034 mov ecx, %1 movl \buff, %ecx
035 mov edx, %2 movl \buff_size, %edx
036 int 80h int $0x80
037 %endmacro .endm
038
039
040 section .text .section .text
041
042 global _start .globl _start
043
044 _start: _start:
045 write prompt_str, STR_SIZE write $prompt_str, $STR_SIZE
046 read buff, 32 read $buff, $32
047
048 ; Read returns the length in eax // Read returns the length in eax
049 push eax pushl %eax
050
051 ; Print the hello text // Print the hello text
052 write greet_str, GSTR_SIZE write $greet_str, $GSTR_SIZE
053
054 pop edx popl %edx
055
056 ; edx = length returned by read // edx = length returned by read
057 write buff, edx write $buff, %edx
058
059 _exit: _exit:
060 mov eax, 1 movl $1, %eax
061 mov ebx, 0 movl $0, %ebx
062 int 80h int $0x80

本节要讨论宏以及 NASM和 GAS 对它们的支持。但是,在讨论宏之前,先与其他几个特性做一下比较。

清单 3演示了未初始化内存的概念,这是用 .bss部分指令(第
14行)定义的。BSS 代表 “ block storage segment”
(原来是以一个符号开头的块),BSS 部分中保留的内存在程序启动时初始化为零。BSS 部分中的对象只有一个名称和
大小,没有值。与数据部分中不同,BSS 部分中声明的变量并不实际占用空间。
NASM使用 resb、resw和 resd关键字在 BSS 部分中分配字节、字和双字空间。GAS 使用 .lcomm关键字分配字
节级空间。请注意在这个程序的两个版本中声明变量名的方式。在 NASM中,变量名前面加 resb(或 resw
或 resd)关键字,后面是要保留的空间量;在 GAS 中,变量名放在 .lcomm关键字的后面,然后是一个逗号和要
保留的空间量。
NASM:varname resb size
GAS:.lcomm varname, size
清单 3还演示了位置计数器的概念(第
6行)。 NASM提供特殊的变量($和 $$变量)来操作位置计数器。在 GAS
中,无法操作位置计数器,必须使用标签计算下一个存储位置(数据、指令等等)。
例如,为了计算一个字符串的长度,在 NASM中会使用以下指令:

prompt_str db 'Enter your name: '
STR_SIZE equ $ - prompt_str ; $ is the location counter


$提供位置计数器的当前值,从这个位置计数器中减去标签的值(所有变量名都是标签),就会得出标签的声明和当前位
置之间的字节数。equ用来将变量 STR_SIZE的值设置为后面的表达式。GAS 中使用的相似指令如下:
prompt_str:

.ascii "Enter Your Name: "


pstr_end:


.set STR_SIZE, pstr_end - prompt_str
末尾标签(pstr_end)给出下一个位置地址,减去启始标签地址就得出大小。还要注意,这里使用 .set将变量
STR_SIZE 的值设置为逗号后面的表达式。也可以使用对应的 .equ。在 NASM中,没有与 GAS 的 set指令对应的指
令。
正如前面提到的,清单 3使用了宏(第
21行)。在 NASM和 GAS 中存在不同的宏技术,包括单行宏和宏重载,但是
这里只关注基本类型。宏在汇编程序中的一个常见用途是提高代码的清晰度。通过创建可重用的宏,可以避免重复输入相
同的代码段;这不但可以避免重复,而且可以减少代码量,从而提高代码的可读性。
NASM使用 %beginmacro指令声明宏,用 %endmacro指令结束声明。%beginmacro指令后面是宏的名称。宏
名称后面是一个数字,这是这个宏需要的宏参数数量。在 NASM中,宏参数是从 1开始连续编号的。也就是说,宏的第
一个参数是 %1,第二个是 %2,第三个是 %3,以此类推。例如:

%beginmacro macroname 2
mov eax, %1
mov ebx, %2


%endmacro
这创建一个有两个参数的宏,第一个参数是 %1,第二个参数是 %2。因此,对上面的宏的调用如下所示:
macroname 5, 6

还可以创建没有参数的宏,在这种情况下不指定任何数字。
现在看看
GAS 如何使用宏。GAS 提供
.macro和 .endm指令来创建宏。.macro指令后面跟着宏名称,后面可以有
参数,也可以没有参数。在 GAS 中,宏参数是按名称指定的。例如:

.macro macroname arg1, arg2
movl \arg1, %eax
movl \arg2, %ebx


.endm

当在宏中使用宏参数名称时,在名称前面加上一个反斜线。如果不这么做,链接器会把名称当作标签而不是参数,因此会
报告错误。

回页首

函数、外部例程和堆栈
本节的示例程序在一个整数数组上实现选择排序。

清单 4.在整数数组上实现选择排序

行号 NASM GAS
001 section .data .section .data
002
003 array db array:
004 89, 10, 67, 1, 4, 27, 12, 34, .byte 89, 10, 67, 1, 4, 27, 12,
005 86, 3 34, 86, 3
006
007 ARRAY_SIZE equ $ -array array_end:
008 .equ ARRAY_SIZE, array_end -array
009
010 array_fmt db " %d", 0 array_fmt:
011 .asciz " %d"
012
013 usort_str db "unsorted array:", 0 usort_str:
014 .asciz "unsorted array:"
015
016 sort_str db "sorted array:", 0 sort_str:
017 .asciz "sorted array:"
018
019 newline db 10, 0 newline:
020 .asciz "\n"
021
022
023 section .text .section .text
024 extern puts
025
026 global _start .globl _start
027
028 _start: _start:
029
030 push usort_str pushl $usort_str
031 call puts call puts
032 add esp, 4 addl $4, %esp
033
034 push ARRAY_SIZE pushl $ARRAY_SIZE

035 push array pushl $array
036 push array_fmt pushl $array_fmt
037 call print_array10 call print_array10
038 add esp, 12 addl $12, %esp
039
040 push ARRAY_SIZE pushl $ARRAY_SIZE
041 push array pushl $array
042 call sort_routine20 call sort_routine20
043
044 ; Adjust the stack pointer # Adjust the stack pointer
045 add esp, 8 addl $8, %esp
046
047 push sort_str pushl $sort_str
048 call puts call puts
049 add esp, 4 addl $4, %esp
050
051 push ARRAY_SIZE pushl $ARRAY_SIZE
052 push array pushl $array
053 push array_fmt pushl $array_fmt
054 call print_array10 call print_array10
055 add esp, 12 addl $12, %esp
056 jmp _exit jmp _exit
057
058 extern printf
059
060 print_array10: print_array10:
061 push ebp pushl %ebp
062 mov ebp, esp movl %esp, %ebp
063 sub esp, 4 subl $4, %esp
064 mov edx, [ebp + 8] movl 8(%ebp), %edx
065 mov ebx, [ebp + 12] movl 12(%ebp), %ebx
066 mov ecx, [ebp + 16] movl 16(%ebp), %ecx
067
068 mov esi, 0 movl $0, %esi
069
070 push_loop: push_loop:
071 mov [ebp -4], ecx movl %ecx, -4(%ebp)
072 mov edx, [ebp + 8] movl 8(%ebp), %edx
073 xor eax, eax xorl %eax, %eax
074 mov al, byte [ebx + esi] movb (%ebx, %esi, 1), %al
075 push eax pushl %eax
076 push edx pushl %edx


077
078 call printf call printf
079 add esp, 8 addl $8, %esp
080 mov ecx, [ebp -4] movl -4(%ebp), %ecx
081 inc esi incl %esi
082 loop push_loop loop push_loop
083
084 push newline pushl $newline
085 call printf call printf
086 add esp, 4 addl $4, %esp
087 mov esp, ebp movl %ebp, %esp
088 pop ebp popl %ebp
089 ret ret
090
091 sort_routine20: sort_routine20:
092 push ebp pushl %ebp
093 mov ebp, esp movl %esp, %ebp
094
095 ; Allocate a word of space in stack # Allocate a word of space in stack
096 sub esp, 4 subl $4, %esp
097
098 ; Get the address of the array # Get the address of the array
099 mov ebx, [ebp + 8] movl 8(%ebp), %ebx
100
101 ; Store array size # Store array size
102 mov ecx, [ebp + 12] movl 12(%ebp), %ecx
103 dec ecx decl %ecx
104
105 ; Prepare for outer loop here # Prepare for outer loop here
106 xor esi, esi xorl %esi, %esi
107
108 outer_loop: outer_loop:
109 ; This stores the min index # This stores the min index
110 mov [ebp -4], esi movl %esi, -4(%ebp)
111 mov edi, esi movl %esi, %edi
112 inc edi incl %edi
113
114 inner_loop: inner_loop:
115 cmp edi, ARRAY_SIZE cmpl $ARRAY_SIZE, %edi
116 jge swap_vars jge swap_vars
117 xor al, al xorb %al, %al
118 mov edx, [ebp -4] movl -4(%ebp), %edx


mov al, byte [ebx + edx]
cmp byte [ebx + edi], al
jge check_next
mov [ebp -4], edi
check_next:
inc edi
jmp inner_loop
swap_vars:
mov edi, [ebp -4]
mov dl, byte [ebx + edi]
mov al, byte [ebx + esi]
mov byte [ebx + esi], dl
mov byte [ebx + edi], al
inc esi
loop outer_loop
mov esp, ebp
pop ebp
ret
_exit:
mov eax, 1
mov ebx, 0
int 80h
movb (%ebx, %edx, 1), %al
cmpb %al, (%ebx, %edi, 1)
jge check_next
movl %edi, -4(%ebp)
check_next:
incl %edi
jmp inner_loop
swap_vars:
movl -4(%ebp), %edi
movb (%ebx, %edi, 1), %dl
movb (%ebx, %esi, 1), %al
movb %dl, (%ebx, %esi, 1)
movb %al, (%ebx, %edi, 1)
incl %esi
loop outer_loop
movl %ebp, %esp
popl %ebp
ret
_exit:
movl $1, %eax
movl 0, %ebx
int $0x80

初看起来清单 4似乎非常复杂,实际上它是非常简单的。这个清单演示了函数、各种内存寻址方案、堆栈和库函数的使用方
法。这个程序对包含 10个数字的数组进行排序,并使用外部 C库函数 puts和 printf输出未排序数组和已排序数
组的完整内容。为了实现模块化和介绍函数的概念,排序例程本身实现为一个单独的过程,数组输出例程也是这样。我们
来逐一分析一下。
在声明数据之后,这个程序首先执行对 puts的调用(第
31行)。puts函数在控制台上显示一个字符串。它惟一的参
数是要显示的字符串的地址,通过将字符串的地址压入堆栈(第
30行),将这个参数传递给它。
在 NASM中,任何不属于我们的程序但是需要在链接时解析的标签都必须预先定义,这就是 extern关键字的作用
(第
24行)。GAS 没有这样的要求。在此之后,字符串的地址 usort_str被压入堆栈(第
30行)。在 NASM中,
内存变量(比如 usort_str)代表内存位置本身,所以 push usort_str这样的调用实际上是将地址压入堆栈
的顶部。但是在 GAS 中,变量 usort_str必须加上前缀$,这样它才会被当作地址。如果不加前缀 $,那么会将内存
变量代表的实际字节压入堆栈,而不是地址。
因为在堆栈中压入一个变量会让堆栈指针移动一个双字,所以给堆栈指针加 4(双字的大小)(第
32行)。
现在将三个参数压入堆栈,并调用 print_array10函数(第
37行)。在 NASM和 GAS 中声明函数的方法是相同
的。它们仅仅是通过
call指令调用的标签。


在调用函数之后,ESP代表堆栈的顶部。esp + 4代表返回地址,esp + 8代表函数的第一个参数。在堆栈指针上加
上双字变量的大小(即 esp + 12、esp + 16等等),就可以访问所有后续参数。
在函数内部,通过将 esp复制到 ebp(第
62行)创建一个局部堆栈框架。和程序中的处理一样,还可以为局部变量
分配空间(第
63行)。方法是从 esp中减去所需的字节数。esp – 4表示为一个局部变量分配
4字节的空间,只要
堆栈中有足够的空间容纳局部变量,就可以继续分配。
清单 4演示了基间接寻址模式(第
64行),也就是首先取得一个基地址,然后在它上面加一个偏移量,从而到达最终
的地址。在清单的 NASM部分中,[ebp + 8]和 [ebp – 4](第
71行)就是基间接寻址模式的示例。在 GAS 中,
寻址方法更简单一些:4(%ebp)和 -4(%ebp)。
在 print_array10例程中,在 push_loop标签后面可以看到另一种寻址模式(第
74行)。在 NASM和 GAS
中的表示方法如下:
NASM:mov al, byte [ebx + esi]
GAS:movb (%ebx, %esi, 1), %al
这种寻址模式称为基索引寻址模式。这里有三项数据:一个是基地址,第二个是索引寄存器,第三个是乘数。因为不可能
决定从一个内存位置开始访问的字节数,所以需要用一个方法计算访问的内存量。NASM使用字节操作符告诉汇编器要移
动一个字节的数据。在 GAS 中,用一个乘数和助记符中的 b、w或 l后缀(例如 movb)来解决这个问题。初看上去
GAS 的语法似乎有点儿复杂。
GAS 中基索引寻址模式的一般形式如下:

%segment:ADDRESS (, index, multiplier)


%segment:(offset, index, multiplier)


%segment:ADDRESS(base, index, multiplier)


使用这个公式计算最终的地址:

ADDRESS or offset + base + index * multiplier.


因此,要想访问一个字节,就使用乘数 1;对于字,乘数是 2;对于双字,乘数是 4。当然,NASM使用的语法比较简单。
上面的公式在 NASM中表示为:

Segment:[ADDRESS or offset + index * multiplier]
为了访问
1、2或 4字节的内存,在这个内存地址前面分别加上 byte、word或 dword。
其他方面
清单 5读取命令行参数的列表,将它们存储在内存中,然后输出它们。

清单 5.读取命令行参数,将它们存储在内存中,然后输出它们

行号 NASM GAS
001 section .data .section .data
002
003 ; Command table to store at most // Command table to store at most
004 ; 10 command line arguments // 10 command line arguments
005 cmd_tbl: cmd_tbl:
006 %rep 10 .rept 10
007 dd 0 .long 0
008 %endrep .endr
009
010 section .text .section .text


011
012 global _start .globl _start
013
014 _start: _start:
015 ; Set up the stack frame // Set up the stack frame
016 mov ebp, esp movl %esp, %ebp
017 ; Top of stack contains the // Top of stack contains the
018 ; number of command line arguments. // number of command line arguments.
019 ; The default value is 1 // The default value is 1
020 mov ecx, [ebp] movl (%ebp), %ecx
021
022 ; Exit if arguments are more than 10 // Exit if arguments are more than 10
023 cmp ecx, 10 cmpl $10, %ecx
024 jg _exit jg _exit
025
026 mov esi, 1 movl $1, %esi
027 mov edi, 0 movl $0, %edi
028
029 ; Store the command line arguments // Store the command line arguments
030 ; in the command table // in the command table
031 store_loop: store_loop:
032 mov eax, [ebp + esi * 4] movl (%ebp, %esi, 4), %eax
033 mov [cmd_tbl + edi * 4], eax movl %eax, cmd_tbl( , %edi, 4)
034 inc esi incl %esi
035 inc edi incl %edi
036 loop store_loop loop store_loop
037
038 mov ecx, edi movl %edi, %ecx
039 mov esi, 0 movl $0, %esi
040
041 extern puts
042
043 print_loop: print_loop:
044 ; Make some local space // Make some local space
045 sub esp, 4 subl $4, %esp
046 ; puts function corrupts ecx // puts functions corrupts ecx
047 mov [ebp -4], ecx movl %ecx, -4(%ebp)
048 mov eax, [cmd_tbl + esi * 4] movl cmd_tbl( , %esi, 4), %eax
049 push eax pushl %eax
050 call puts call puts
051 add esp, 4 addl $4, %esp
052 mov ecx, [ebp -4] movl -4(%ebp), %ecx


053 inc esi incl %esi
054 loop print_loop loop print_loop
055
056 jmp _exit jmp _exit
057
058 _exit: _exit:
059 mov eax, 1 movl $1, %eax
060 mov ebx, 0 movl $0, %ebx
061 int 80h int $0x80

清单 5演示在汇编程序中重复执行指令的方法。很自然,这种结构称为重复结构。在 GAS 中,重复结构以 .rept指令
开头(第
6行)。用一个 .endr指令结束这个指令(第
8行)。.rept后面是一个数字,它指定 .rept/.endr结
构中表达式重复执行的次数。这个结构中的任何指令都相当于编写这个指令 count次,每次重复占据单独的一行。
例如,如果次数是 3:

.rept 3
movl $2, %eax
.endr


就相当于:

movl $2, %eax
movl $2, %eax
movl $2, %eax
在 NASM中,在预处理器级使用相似的结构。它以 %rep指令开头,以 %endrep结尾。%rep指令后面是一个表达式
(在 GAS 中 .rept指令后面是一个数字):
%rep <expression>

nop%endrep
在 NASM中还有另一种结构,times指令。与 %rep相似,它也在汇编级起作用,后面也是一个表达式。例如,上面
的 %rep结构相当于:
times <expression> nop

以下代码:

%rep 3
mov eax, 2
%endrep


相当于:

times 3 mov eax, 2


它们都相当于:

mov eax, 2
mov eax, 2
mov eax, 2
在清单 5中,使用 .rept(或 %rep)指令为 10个双字创建内存数据区。然后,从堆栈一个个地访问命令行参数,并
将它们存储在内存区中,直到命令表填满。


在这两种汇编器中,访问命令行参数的方法是相似的。ESP(堆栈顶部)存储传递给程序的命令行参数数量,默认值是
1(表示没有命令行参数)。esp + 4存储第一个命令行参数,这总是从命令行调用的程序的名称。esp + 8、esp +
12等存储后续命令行参数。
还要注意清单 5中从两边访问内存命令表的方法。这里使用内存间接寻址模式(第
31行)访问命令表,还使用了 ESI(和 EDI)中的偏移量和一个乘数。因此,NASM中的 [cmd_tbl + esi * 4]相当于 GAS 中的 cmd_tbl(,
%esi, 4)。

四、系统调用

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

2011-12-22 21:18:50 lcgweb 阅读数 762
  • ARM裸机1期加强版(linux

    1、 现场编写调试 2、假设学员0基础: 只会简单C语言 3、ARM裸板为主线,硬件知识,c语言为辅线,4、Linux命令,ARM硬件、汇编,重定位,链接脚本,gcc、Makefile 5、全新软件以及ubuntu系统

    7569 人正在学习 去看看 韦东山

Linux下汇编调试器GDB的使用


      GDB 是GNU开源组织发布的一个强大的Linux/Unix下的程序调试工具。大家是否早已习惯了Windows下图形界面方式像VC、BCB等IDE的调试器,但如果你是在Linux平台下做软件调试,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。
先来看个实例:

  1. reader@cg:~/source $ gdb -q
  2. (gdb) set dis intel
  3. (gdb) quit
  4. reader@cg:~/source $ echo "set dis intel" > ~/.gdbinit
  5. reader@cg:~/source $ cat ~/.gdbinit
  6. set dis intel
  7. reader@cg:~/source $

现在将GDB配置成为了使用Intel语法,让我们首先来认识Intel语法。在Intel语语法中,汇编指令一般遵循下面这种形式:
operation <destination>, <source>
目的操作数和源操作数可以是寄存器、内存地址或数值。操作通常是直观的助记符:mov操作会将源操作数中的值移动到目的操作数中,sub操作会减去,inc指令会增加等。例如,下面的指令将会扣ESP中的值移动到EBP中,然后从ESP中减去8(结果存储在ESP中)。
8048375:        89 e5                 mov    ebp,esp
8048377:        83 ec 08              sub    esp,0x8
还有用于控制执行流程的操作。cmp操作用于对数值进行比较,并且基本上所有以j为首字母的操作都用于转移到代码的不同部分(转移到哪一部分取决于比较的结果)。下面的例子中,首先将位于EBP中的一个4字节的值减去4与数值9进行比较。下一条指令是如果小于等于则转移的简写,它参考的是前一个比较的结果。如果那个数小于或等于9,那么程序就会转移到Ox8048393处的指令执行。否则,就转向下一条无条件转移指令执行。如果那个数不小于或等于9,那么程序执行就会转移到Ox80483a6处。
804838b:        83 7d fc 09           cmp    DWORD PTR [ebp-4],0x9
804838f:        7e 02                 jle    8048393 <main+0x1f>
8048391:        eb 13                 jmp    80483a6 <main+0x32>
这些例子来自于我们先前的反汇编,并且我们已经将调试工具配置为使用Intel语法,所以让我们使用调试工具在汇编指令级别上单步调试第一个程序吧。
GCC编译程序可以使用-g标记来包含附加的调试信息,这些调试信息会使得GDB能够访问源代码。

  1. reader@cg:~/source $ gcc -g firstprog.c
  2. reader@cg:~/source $ ls -l a.out
  3. -rwxr-xr-x 1 matrix users 11977 Jul 4 17:29 a.out
  4. reader@cg:~/source $ gdb -q ./a.out
  5. Using host libthread_db library "/lib/libthread_db.so.1".
  6. (gdb) list
  7. 1 #include <stdio.h>
  8. 2
  9. 3 int main()
  10. 4 {
  11. 5 int i;
  12. 6 for(i=0; i < 10; i++)
  13. 7 {
  14. 8 printf("Hello, world!\n");
  15. 9 }
  16. 10 }
  17. (gdb) disassemble main
  18. Dump of assembler code for function main():
  19. 0x08048384 <main+0>: push ebp
  20. 0x08048385 <main+1>: mov ebp,esp
  21. 0x08048387 <main+3>: sub esp,0x8
  22. 0x0804838a <main+6>: and esp,0xfffffff0
  23. 0x0804838d <main+9>: mov eax,0x0
  24. 0x08048392 <main+14>: sub esp,eax
  25. 0x08048394 <main+16>: mov DWORD PTR [ebp-4],0x0
  26. 0x0804839b <main+23>: cmp DWORD PTR [ebp-4],0x9
  27. 0x0804839f <main+27>: jle 0x80483a3 <main+31>
  28. 0x080483a1 <main+29>: jmp 0x80483b6 <main+50>
  29. 0x080483a3 <main+31>: mov DWORD PTR [esp],0x80484d4
  30. 0x080483aa <main+38>: call 0x80482a8 <_init+56>
  31. 0x080483af <main+43>: lea eax,[ebp-4]
  32. 0x080483b2 <main+46>: inc DWORD PTR [eax]
  33. 0x080483b4 <main+48>: jmp 0x804839b <main+23>
  34. 0x080483b6 <main+50>: leave
  35. 0x080483b7 <main+51>: ret
  36. End of assembler dump.
  37. (gdb) break main
  38. Breakpoint 1 at 0x8048394: file firstprog.c, line 6.
  39. (gdb) run
  40. Starting program: /cg/a.out
  41. Breakpoint 1, main() at firstprog.c:6
  42. 6 for(i=0; i < 10; i++)
  43. (gdb) info register eip
  44. eip 0x8048394 0x8048394
  45. (gdb)

先列出了源代码,并且显示了main()函数的反汇编。然后,在main()函数开始的地方设置了一个断点,并且开始运行程序。这个断点只是告诉调试工具当程序运行到该点时暂停程序的执行。由于断点设置在了main()函数开始的地方,实际上在执行main()中的任何指令之前,程序会到达该断点并且暂停。然后,显示了EIP(指令指针)的值。
EIP包含一个内存地址,该地址指向main()函数的反汇编指令中的一条指令(以粗体显示)。我们把在这之前的指令(以斜体显示)一起称为函数序言,它们由编译程序生成,用于为main()函数的局部变量设置内存空间。在C语言中需要声明变量的部分原因就是辅助建立这部分代码。调试工具知道这部分代码是自动产生的,并且聪明地将其跳过。我们随后会详细讨论函数序言,但现在我们可以从GDB中获得它的一些信息并暂时将其跳过。
GDB调试工具提供了一个直接检查内存的方法,即使用命令x,它是检查(examine)的简写。对于任何Hacker来说,检查内存都是一项很关键的技术。大多数Hacker的漏洞发掘很像魔术——它们似乎令人惊讶并且不可思议,除非您知道这些戏法和误导。但是使用像GDB这样的调试工具,程序执行的每个方面都可以被确定地检查、暂停、单步跟踪并且可以随心所欲地重复。因为一个正在运行的程序的主体是处理器和若干内存段,所以检查内存是查看到底正在干什么的首要方法。

linux 汇编

阅读数 688

没有更多推荐了,返回首页