x86汇编程序基础(AT&T语法)

一、简单的汇编程序

以下面这段简单的汇编代码为例

.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $4, %ebx
int $0x80

(注意是globl不是global;movl(MOVL)不是mov1(MOV一))

将这段程序保存为demo.s,然后用汇编器as把汇编程序中的助记符翻译成机器指令(汇编指令与机器指令是对应的)生成目标文件demo.o。然后用链接器ld把目标文件demo.o链接成可执行文件demo(虽然只有一个目标文件但是也需要经过链接才能成为可执行文件因为链接器要修改目标文件中的一些信息)。这个程序只做了一件事就是退出,退出状态为4。shell中可以echo $?得到上一条命令的退出状态。

【解释】:汇编程序中以"."开头的名称不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊的指示,称为汇编指示或伪操作。

.section .data
.section .text

.section指示把代码划分成若干个段(section),程序被操作系统加载时,每个段被加载到不同的地址,具有不同的读写执行权限。

.data段保存程序的数据是可读写的,C程序的全局变量也属于.data段。上边的程序没定义数据所以.data是空的。

.text段保存代码,是只读和可执行的,后面那些指令都属于这个.text段。

.globl  _start

_start是一个符号(Symbol),符号在汇编程序中代表一个地址,可以用在指令中,汇编程序经过汇编器的处理后所有的符号都被替换成它所代表的地址值。在C中我们可以通过变量名访问一个变量,其实就是读写某个地址的内存单元,我们通过函数名调用一个函数其实就是调转到该函数的第一条指令所在的地址,所以变量名和函数名都是符号,本质上是代表内存地址的。

.globl指示告诉汇编器_start这个符号要被链接器用到,所以要在目标文件的符号表中给它特殊标记。_start就像C程序的main函数一样特殊是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。如果一个符号没有用.globl指示声明这个符号就不会被链接器用到。

_start:

_start在这里就像C语言的语句标号一样。汇编器在处理汇编程序时会计算每个数据对象和每条指令的地址,当汇编器看到这样一个标号时,就把它下面一条指令的地址作为_start这个符号所代表的地址。而_start这个符号又比较特殊事整个程序的入口地址,所以下一条指令movl $1, %eax就成了程序中第一条被执行的指令。

movl $1, %eax

这是一条数据传送指令,CPU内部产生一个数字1, 然后传送到eax寄存器中。mov后边的l表示long,说明是32位的传送指令。CPU内部产生的数称为立即数,在汇编程序中立即数前面加"$"寄存器前面加"%",以便跟符号名区分开。

movl $4, %ebx

与上条指令类似,生成一个立即数4,传送到ebx寄存器中。

int $0x80

前两条指令都是为这条指令做准备的,执行这条指令时:

  1. int指令称为软中断指令,可以用这条指令故意产生一个异常。异常的处理与中断类似,CPU从用户模式切换到特权模式,然后跳转到内核代码中执行异常处理程序。

  2. int指令中的立即数0x80是一个参数,在异常处理程序中根据这个参数决定如何处理,在linux内核中,int $0x80这种异常称系统调用(System Call)。内核提供了许多系统服务供用户程序使用,但这些系统服务不能像库函数(比如printf)那样调用,因为在执行用户程序时CPU处于用户模式不能直接调用内核函数,所以需要通过系统调用切换CPU模式,通过异常处理程序进入内核,用户程序只能通过寄存器传几个参数,之后就要按内核设计好的代码路线走,而不能由用户程序随心所欲想调那个内核函数,这样保证了系统服务被安全的调用,在调用结束后CPU再切换回用户模式,继续执行int指令后面的指令,在用户程序看来就像函数的调用和返回一样。

  3. eax和ebx寄存器的值是传递给系统调用的两个参数,eax的值是系统调用号,1表示_exit系统调用,ebx的值则是传给_exit系统调用的参数,也就是退出状态。_exit这个系统调用会终止掉当前进程,而不会返回它继续执行。不同的系统调用需要的参数个数也不同,有的会需要ebx、ecx、edx三个寄存器的值做参数,大多数系统调用完成之后是会返回用户程序继续执行的,_exit系统调用特殊。

x86汇编的两种语法:intel语法和AT&T语法
x86汇编一直存在两种不同的语法,在intel的官方文档中使
用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一
直使用AT&T语法,所以本书使用AT&T语法。 mov %edx,%eax 这条
指令如果用intel语法来写,就是 mov eax,edx ,寄存器名不加 % 号,
并且源操作数和目标操作数的位置互换。本书不详细讨论这两种
语法之间的区别,读者可以参考[AssemblyHOWTO]。
介绍x86汇编的书很多,UNIX平台的书都采用AT&T语法,例
如[GroudUp],其它书一般采用intel语法,例如[x86Assembly]。

二、x86的寄存器

  x86的通用寄存器eaxebxecxedxediesi。这些寄存器在大多数指令中是可以任意使用的。但有些指令限制只能用其中某些寄存器做某种用途,例如除法指令idivl规定被除数在eax寄存器中,edx寄存器必须是0,而除数可以是任何寄存器中。计算结果的商数保存在eax寄存器中(覆盖被除数),余数保存在edx寄存器。

  x86的特殊寄存器ebpespeipeflags。eip是程序计数器。eflags保存计算过程中产生的标志位,包括进位、溢出、零、负数四个标志位,在x86的文档中这几个标志位分别称为CF、OF、ZF、SF。ebp和esp用于维护函数调用的栈帧。

三、第二个汇编程序

求一组数最大值的汇编程序:

.section .data
data_items:
.long 3,10,9,29,5,19,9,36,0
.section .text
.globl _start
_start:
movl $0, %edi
movl data_items(,%edi,4), %eax
movl %eax, %ebx
start_loop:
cmpl $0, %eax
je loop_exit
incl &edi
movl data_item(, %edi,4), %eax
cmpl %ebx, %eax
jle start_loop
movl %eax, %ebx
jmp start_loop
loop_exit:
mov $1, %eax
int $0x80

汇编链接执行:

这个程序在一组数中找到一个最大的数,并把它作为程序的退出状态。这段数在.data段给出:

data_items:
.long 3,10,9,29,5,19,9,36,0

.long指示声明一组数,每个数32位,相当于C数组。数组开头有个标号data_items,汇编器会把数组的首地址作为data_items符号所代表的地址,data_items类似于C中的数组名。data_items这个标号没有.globl声明是因为它只在这个汇编程序内部使用,链接器不需要知道这个名字的存在。除了.long之外常用的声明:

  • .byte,也是声明一组数,每个数8位
  • .ascii,例: .ascii "Hello World",声明了11个数,取值为相应字符的ASCII码。和C语言不同的是这样声明的字符串末尾是没有‘\0‘字符的。

data_items数组的最后一个数是0,我们在一个循环中依次比较每个数,碰到0的时候就终止循环。在这个循环中:

  • edi寄存器保存数组中的当前位置,每次比较完一个数就把edi的值加1,指向数组中的下一个数。
  • ebx寄存器保存到目前为止找打的最大值,如果发现有更大的数就更新ebx的值。
  • eax寄存器保存当前要比较的数,每次更新edi之后,就把下一个数读到eax中。
_start:
movl $0, %edi

初始化edi,指向数组的第0个元素。

movl data_items(,%edi,4), %eax

这条指令把数组的第0个元素传送到eax寄存器中。data_items是数组的首地址,edi的值是数组的下标,4表示数组的每个元素占4字节,那么数组中第edi个元素的地址应该是data_items+edi*4。从这个地址读数据,写成指令就是上面那样。

movl %eax, %ebx

ebx的初始值也是数组的第0个元素。

下面进入一个循环,在循环的开头用标号start_loop表示,循环的末尾之后用标号loop_exit表示。

start_loop:
cmpl $0, %eax
je loop_exit

比较eax的值是不是0,如果是0就说明到了数组末尾了,就要跳出循环。cmpl指令将两个操作数相减,但计算结果并不保存,只是根据计算结果改变eflags寄存器中的标志位。如果两个操作数相等,则计算结果为0,eflags中的ZF位置1。je是一个条件跳转指令,它检查eflags中的ZF位,ZF位为1则发生跳转,ZF位为0则不跳转继续执行下一条指令。(条件跳转指令和比较指令是配合使用的)je的e就表示equal

incl %edi
movl data_items(,%edi,4), %eax

将edi的值加1,把数组中的下一个数组传送到eax寄存器中。

cmpl %ebx, %eax
jle start_loop

把当前数组元素eax和目前为止找到的最大值ebx做比较,如果前者小于等于后者,则最大值没有变,跳转到循环开头比较下一个数,否则继续执行下一条指令。jle也是一个条件跳转指令,le表示less than or equal

movl %eax, %ebx
jmp start_loop

更新了最大值ebx然后跳转到循环开头继续比较下一个数。jmp是一个无条件跳转指令,什么条件也不判断直接跳转。loop_exit标号后面的指令用_exit系统调用来退出程序。

四、寻址方式

访问内存时在指令中可以用多种方式表示内存地址。内存寻址在指令中可以表示成如下的通用格式:

ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)

它所表示的地址可以这样计算出来:

FINAL ADDRESS = ADDRESS_OR_OFFSET + BASE_OR_OFFSET + MULTIPLIER * INDEX

其中ADDRESS_OR_OFFSET和MULTIPLIER必须是常数,BASE_OR_OFFSET和INDEX必须是寄存器。在有些寻址方式中会省略这4项中的某些项,相当于这些项是0。

  • 直接寻址:只使用ADDRESS_OR_OFFSET寻址,例如movl ADDRESS, %eax把ADDRESS地址处的32位数传送到eax寄存器。
  • 变址寻址:movl data_items(,%edi,4), %eax就属于这种方式,用于访问数组很方便
  • 间接寻址:只使用BASE_OR_OFFSET寻址,例如movl (%eax), %ebx,把eax寄存器的值看作地址,把这个地址处的32位数传送到ebx寄存器。
  • 基址寻址:只使用ADDRESS_OR_OFFSET和BASE_OR_OFFSET寻址,例如movl 4(%eax), %ebx,用于访问结构体成员比较方便,例如一个结构体的基地址保存在eax寄存器中,其中一个成员在结构体内偏移量是4字节,要把这个成员读上来就可以用这条指令。
  • 立即数寻址:就是指令中有一个操作数是立即数,例:movl $3, %eax。
  • 寄存器寻址:就是指令中有一个操作数是寄存器。在汇编程序中寄存器用助记符来表示,在机器指令中则要用几个Bit表示寄存器的编号,这几个Bit与可以看做寄存器的地址,但是和内存地址不在一个地址空间。

关于汇编程序的Hello World可以参看我的另一篇文章:http://www.cnblogs.com/orlion/p/5316519.html

时间: 2024-10-18 04:16:45

x86汇编程序基础(AT&T语法)的相关文章

js基础--javascript基础概念之语法

掌握一门语言 必须先掌握它的语法! javascript 的语法和C.Java.Perl 的语法有些相似.但是比它们更加宽松. javascript 中的一切都是严格区分大小写的.例如变量: demo 和 Demo 两个变量是完全不同的. javascript 标示符,所谓标示符 是指 变量.函数.属性 的名字或函数的参数.标示符的格式是按照以下规则组合的一个或多个字符. 1.第一个字符必须是字母,下划线,或 $ 符号. 2.其他字符可以是字母.下划线.$ . 或数字. 注意 不能把关键字 保留

JavaScript基础概念与语法

学习了一些最基础的JavaScript语法: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device, initial-scale=1"> <title>Jsstudy</title> &

mysql数据库之基础SQL语句/语法

SQL是现在进入互联网工作人们的必须技能之一,下面分享自己觉得很nice的SQL基本语句,从网上找了,觉得很不错,就分享给大家!简要介绍基础语句: 1.说明:创建数据库  Create DATABASE database-name 2.说明:删除数据库  drop database dbname 3.说明:备份sql server --- 创建 备份数据的 device USE master EXEC sp_addumpdevice 'disk', 'testBack', 'c:\mssql7b

基于JS的DOM 编程基础和Json语法及JS下的AJAX基础

1.在dom编程中:     注意:使用dom操作一般在页面完全载入之后,一般在window_onload事件里操作dom nodeName  表示节点的名称如:<input   type= "button"  value="确定" />    此时nodeName=“input” ; nodeValue  表示节点的值如<p>aaa</p> 此时的nodeValue="aaa",而nodeName="

PHP 学习笔记(一):基础教程:语法,变量,函数,数组,超全局

PHP简介 PHP 脚本在服务器上执行. 什么是 PHP 文件 PHP 文件能够包含文本.HTML.CSS 以及 PHP 代码 PHP 代码在服务器上执行,而结果以纯文本返回浏览器 PHP 文件的后缀是 ".php" PHP能够做什么 PHP 能够生成动态页面内容 PHP 能够创建.打开.读取.写入.删除以及关闭服务器上的文件 PHP 能够接收表单数据 PHP 能够发送并取回 cookies PHP 能够添加.删除.修改数据库中的数据 PHP 能够限制用户访问网站中的某些页面 PHP

javascript(js)基础 dom基础(js语法)未来会删除此博客

<script> //文档中第三个图像:document.images[2] //文档中名为'aa'的表单:document.form['aa'] //层只有唯一 的id:document.layers[id] //微软的层id:docment.all[id] //如果想找到id的left位置并把它赋值给变量bs,在其他浏览器中是这样操作的:document.layers[id].left; //统一标准的dom是这样的:bs=document.getElementByid(id).style

Python自动化 【第七篇】:Python基础-面向对象高级语法、异常处理、Scoket开发基础

本节内容: 1.     面向对象高级语法部分 1.1   静态方法.类方法.属性方法 1.2   类的特殊方法 1.3   反射 2.     异常处理 3.     Socket开发基础 1.     面向对象高级语法部分 1.1   静态方法.类方法.属性方法 1)   静态方法 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法.普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访

SQL SERVER 基础知识及语法总结,从头说起,一点一滴 (一)

什么是SQL? SQL指结构化查询语言 SQL使我们有能力访问数据库 SQL是一种ANSI的标准计算机语言 在SQL的使用上,或多或少也有许多知识点,在这里总结一下关于SQL的基本语法以及一些基础知识点 前      言 首先来谈谈对数据库以及服务器的一些愚见吧! 项目按照类型分大致可以划分为C/S以及B/S方式,前者是客户端/服务器的方式,后者是网页浏览器/服务器的方式,无论哪一种方式都是由前端客户端发送请求数据包给服务器上的应用服务器对数据库进行处理再返回处理结果而实现的,在后台的数据库按照

python基础入门(语法基础)

最近开始整理python的资料,博主建立了一个qq群,希望给大家提供一个交流的同平台 78486745 . 学习编程语言是很有趣的一件事情,但有2点请一定要谨记: 做人靠自己,码代码也必须靠自己.能不能成为python大牛,靠的是平时逻辑的训练和日复一日的码代码练出来的: 多总结.多思考.多查阅.实现需求的方式有多种,但总会有另一个办法是更加高效的! 博文内容属于基础篇,实时更新,如有地方写的不好,还请大家批评指正! python的语法基础 任何一种编程语言都有自己的一套语法,python也不例