程序组成
汇编程序由定义好的段组成,一般有如下三个段:
1、 数据段
2、 BSS段
3、 文本段
所有汇编语言必须有文本段。数据段与BSS段是可选的。数据段一般是放置带有初始值的数据元素。BSS段一般使用0值或NULL值初始化的数据元素。这些区一般是局部变量区。
定义段
一般使用.section命令来声明段。.section后面跟上段的类型。一般布局如下
1、BSS段一定是在text段之前。
2、data段可以放在text段之后。但是一般的放法是按照图中所示。
一般的汇编程序的模板如下图所示:
第一个程序
在第一个程序中,我们使用汇编语言来得到关于CPU的一些信息。书上讲得太多,现在只关心一个功能,也就是CPU的字符串信息。
1、 首先把0值放入%eax寄存器中。
2、 cpuid指令根据%eax中的值,输出字符串至%ebx, %edx, %ecx三个字符串。
3、 再把这三个寄存器中的值依次从一个数组头开始放即可。相当于C语言中的char[12]的数组依次从头开始放,每次占四个字符。
可以写出代码如下:可以先编译着试一下。后面再看一下具体意思。
.section .bss
.lcomm output, 12
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $12, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
编译:
$ as -o cpuid.o cpuid.s
$ ld -o cpuid cpuid.o
尽管有的书上说可以直接使用
$gcc -o cpuid cpuid.s
来进行编译,但是我的电脑上不行。我也喜欢采用前面那种方式,因为对于整个流程,特别是编译及链接都有自己的理解。
as是编译,ld是链接。
如果编译链接都没有出错,那么可以执行代码,可以得到以下结果
这里是因为代码中没有处理换行的原因。
可以看到可以正确地输出CPU字符串信息。
现在分段讲解一下程序中的各个功能:
movl $0, %eax
cpuid
这里是把值传入%eax寄存器,以正确地执行相应功能。
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
这里是把cpuid执行的结果放到相应的寄存器中。
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $12, %edx
int $0x80
这里是调用字符串输出功能。
%eax指定系统调用号
%ebx指定要写入的文件描述符
%ecx指向要输出的字符串的开头
%edx指定要输出的字符串的个数
这个中断调用使用的是Linux的软中断功能。通过int $0x80来执行。
movl $1, %eax
movl $0, %ebx
int $0x80
这里则是调用Linux exit中断功能。%eax中的值1表示使用exit函数,而%ebx则是退出值的指定。
gdb调试汇编
使用gdb调试汇编的时候,需要额外在_start:加一条nop指令。并且如果断点要从开头开始,
需要执行break *_start+1代码如下:
.section .bss
.lcomm output, 12
.section .text
.globl _start
_start:
nop
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $12, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
编译也有所不同,
rabbit:/tmp # as -gstabs -o cpuid.o cpuid.s
rabbit:/tmp # ld -o cpuid cpuid.o
使用gdb命令:
$ gdb ./cpuid
则会出现如下画面
接下输入break *_start+1,把断点设置在开头:再执行run命令,可以看到如下结果
接下来再介绍几个命令
s, n: 表示执行下一条指令
q表示退出gdb
s n; 表示执行接下来n条指令,同理n n亦然。后面的n表示数值。
info registers 输出所有寄存器信息。
print/x %ebx把%ebx寄存器按16进制输出。/t表示2进制,/d表示10进制。
x/nyz &memaddress, 输出内存地址memaddressr的n个字段的信息,
y的取值, c 表示char, d 表示10进制,x表示16进制。
z的取值, b表示byte, 8位,h表示16位,w表示32位。
下面表示了x指令的用法。
汇编中使用C函数
下面的代码示例如何使用C函数。
.section .data
format:
.asciz "%s\n"
.section .bss
.lcomm output, 12
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $output, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $output
pushl $format
call printf
addl $8, %esp
pushl $0
call exit
编译链接如下:
$ as -o asc.o asc.s
$ ld -dynamic-linker /lib/ld-linux.so.2 -lc -o asc asc.o
如果出现错说,push不对,那么只要按如下方式进行编译:可以得到正确结果
rabbit:/tmp # as --32 -o asc.o asc.s
rabbit:/tmp # ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/libc.so -o asc asc.o
rabbit:/tmp # ./asc
GenuineIntel