《汇编语言程序设计》摘录--第四章

作者:(美)布鲁姆 出版社:机械工业出版社

###第四章###汇编语言程序范例

**4.1**程序的组成

汇编语言由定义好的段构成,每个段都有不同的目的。

常用段:

  · 数据段

  · bss段

  · 文本段

所有汇编语言程序中必须有文本段。文本段是在可执行程序内声明指令码的地方,也就是比如mov,push,pop这些指令码助记符(简称指令码)声明的地方。数据和bss段可选的,但是在程序中经常使用到。数据段声明带有初值的数据元素,这些数据元素用作程序中的变量。bss段声明使用零(或NULL)初始化的数据元素。这些数据元素用作汇编语言程序中的缓冲区。

   4.1.1 声明段

GNU汇编器使用 .section命令声明段。只有一个参数:声明段的类型。

———————

| .section .data  |

| .section .bss    |

| .section .text   |

________________

一个汇编语言程序结构布局大致如上所示。

一般情况下,bss段总是安排在文本段之前,但是数据段可以移动到文本段之后,虽然这不是什么标准。除了完成程序逻辑和实现的功能,汇编语言程序还应该易读。

4.1.2 定义起始点

当汇编语言被转化为可执行文件时,连接器必须知道指令码的起始点什么。但是对于只有单一指令路径的简单程序,找到起始点相对容易。但是对于使用分散在源代码各个位置的若干函数的更加复杂的程序,发现程序从哪里开始不是件容易的事情。

对此,GNU汇编器定义了起始标签:_start。_start标签用于表明程序应该从哪条指令开始运行。另外,如果连接器找不到这个标签,将会发生错误。

 <除了在应用程序中声明起始标签之外,还需要为外部应用程序提供入口点,这是用.globl命令完成>

.globl命令声明外部程序可以访问的程序标签。如果编写被外部汇编程序或者C语言程序用的一组工具,就应该使用.globl命令声明每个函数段标签。

来一个模板:

.section .data

< initialized data here >

.section .bss

< uninitialized data here >


.section .text

.globl _start

_start:

< instruction code  goes here   >

接下来展示如何从汇编语言程序源代码构建应用程序。

**4.2**创建简单的程序

我们创建一个重点在单一指令码的简单应用程序。CPUID指令码用于运行程序处理器的信息。可以获得厂商和型号信息,并且显示出来。

4.2.1  CPUID指令

CPUID是一条汇编语言指令,不容易从高级语言执行它。它请求CPU的特定信息,并且把返回信息放回到特定寄存器中的低级指令。

cpuid使用单一寄存器作为输入。eax寄存器用于决定cpuid指令生成什么信息。根据eax中的值,cpuid指令在ebx,ecx,edx寄存器中生成关于处理器的不同信息。信息以一些列位值和标志的形式返回,必须解释出他们的正确含义。

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

eax值 cpuid输出

———————————————————————————————-

0 厂商ID字符串和支持的最大cpuid选项值

1 处理器类型,系列,型号和分步信息

2 缓存配置

3 处理器序列号

4 缓存配置

5 监视信息

80000000h 扩展的厂商id字符串和支持的级别

80000001h 扩展的处理器类型,系列,型号和分步信息

80000002h~800000004h 扩展的处理器名称字符串

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

现在我们使用0选项,从处理器获取简单的厂商ID字符串。当0值被放入eax寄存器并且执行CPUID指令时,处理器把厂商id返回到ebx,edx,ecx寄存器中

·ebx包含字符串最低4字节

·edx包含字符串中间4字节

·ecx包含字符串最高4字节

按照小尾数格式存放在寄存器中。小尾数存放是Intel处理器存放数据的方式:简单地说就是低字节首先存放到寄存器中,然后再一次存放中间字节和高字节。

4.2.2范例程序

了解了cpuid指令如何工作后,现在编写程序实现这个过程。

.section .data

output:

.ascii “The CPU is ‘xxxxxx’\n”

.section .text

.globl _start

_start:

movl $0, %eax          #使eax加载0值,

cpuid #然后运行cpuid指令;其中的0值指定的是cpuid指令的输出选项(这里输出的是CPU厂商id字符串)

movl $output, %edx     #创建一个指针。处理output时会用到真个指针:也就是将标签output的地址储存到edx寄存器中。

movl %ebx,  28(%edx)  #括号外的数字表示相对于output标签的偏移量。

movl %edx,  32(%edx)   #这些数字和edx的值相加就确定了ebx,edx,ecx中的值被写入的地址。

movl %ecx,  36(%edx)

movl $4, %eax #Linux内核系统调用号。这句话是说调用write()这个系统调用

movl $1, %ebx #这时描述符1,表示stdout

movl $output, %ecx        #要输出的字符串起始地址

movl $42, %edx #字符串长度

int $0x80 #软中断

movl $1 %eax #系统调用号1,这句话相当于调用exit()

movl $0, %ebx                #向shell返回0,表示成功推出程序

int $0x80 #软中断

然后就是涉及到编译和链接的问题,不在赘述

**4.3** 调试程序

1.单步运行程序

使用编译参数-gstabs,可以使得可执行程序包含必要的调试信息。

为了单步运行程序,必须设置断点。简单的说,断点设置在程序中调试器希望程序停止运行的地方。

可以在下列情况下停止程序运行:

· 到达某个标签

· 到达源代码中某个行号

· 数据值到达某个特定的值

· 函数执行了特定的次数

现在我们将断点设置到程序代码的开头。需要指出的是,汇编语言中断点的设定要指定对于最近标签的相对位置。

格式:

break *label + offset

label是源代码中的标签,offset是执行应该停止的地方相对于这个标签的行数。

为了在第一条指令处设置断点,然后启动程序,输入入戏命令:

     break *_start

现在程序在_start标签处停了下来,并且指出下一条指令是movl $0, %eax

接着可以使用  next或者 step 命令一步一步执行程序。当我们感兴趣的段落执行之后,可以使用 cont 指令继续执行直到程序结束。

2.查看数据

既然知道了程序怎么让程序在特定位置停止下来,现在来看看怎么检查停止时的数据元素。

gdb命令有以下:

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

数据命令                               描述

——————————————————————————————————

info register                                                          查看所有寄存器的值

print                 显示特定寄存器或者程序变量值

x                                                                            显示特定内存位置的内容

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

ok!现在我们来详细跟踪这个程序的运行,这个跟踪过程我们要查看各个寄存器、各个程序变量值、各个内存地址的变化。

->设置断点:break *_start

运行起来:run          显示了即将运行:movl $0, %eax

查看各个寄存器:info register

显示通用寄存器(eax,ecx, ebx, edx)都为0

重点关注eip,他指向了下一条指令的地址。

单步运行:step,知道下一条指令是movl $output, %edx

下面说个例子,比如现在我们感兴趣,output标签的内存地址到底是多少?有没有被存放到edx寄存器?

查看初始edx寄存器值:print/x $edx     显示:0x49656e69

查看output标签内存地址(注意只是内存地址):print/x &output   显示:0xB0490ac

继续运行:next

查看寄存器:info register    发现edx寄存器确实被改写成了output标签的内存地址。

通过以上知道了查看寄存器、程序变量的内存位置、查看指定寄存器值的方法。

但是我们只是知道了如何查看变量的内存位置,却还不知道内存位置的内容。也就是内存位置到底存放的是什么还不知道。

使用命令:x

格式:x/nyz

其中n是要显示的字段数。

y是输出格式,可以是:

· c显示字符

· d用于显示十进制

· x用于显示十六机制

z是要显示的字段的长度:

· b用于字节

· h用于16位字

· w用于32位字

例如,要显示output标签的内存位置的值:

x/42cb

表示以字符模式显示变量output变量的前42个字节,&表示output是一个内存位置。

这个特性很有价值。

(未完待续)

=========接下来是第四章关于调用C语言库函数的问题===========


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值