【汇编学习】第二章:程序起步

程序组成

汇编程序由定义好的段组成,一般有如下三个段:

1、  数据段

2、  BSS段

3、  文本段

所有汇编语言必须有文本段。数据段与BSS段是可选的。数据段一般是放置带有初始值的数据元素。BSS段一般使用0值或NULL值初始化的数据元素。这些区一般是局部变量区。

定义段

一般使用.section命令来声明段。.section后面跟上段的类型。一般布局如下

 

【汇编学习】第二章:程序起步_第1张图片

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命令,可以看到如下结果

【汇编学习】第二章:程序起步_第2张图片

接下来再介绍几个命令

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
 
 

 
 

 
 
 
 
 
 
 

你可能感兴趣的:(【汇编学习】第二章:程序起步)