x8陆汇编制程序序基础(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(即使只有3个目的文件不过也急需经过链接才能成为可执行文件因为链接器要修改目的文件中的1些新闻)。这么些顺序只做了一件事正是脱离,退出状态为四。shell中得以echo
$?得到上一条命令的脱离状态。

C语言 1

 

【解释】:汇编程序中以”.”起头的称号不是命令的助记符,不会被翻译成机器指令,而是给汇编器壹些特有的提醒,称为汇编提示或伪操作。

.section .data
.section .text

.section指令把代码划分成多少个段(section),程序被操作系统加载时,各样段被加载到区别的地址,具有不相同的读写执行权限。

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

.text段保存代码,是只读和可实施的,后边那3个指令都属于这几个.text段。

.globl  _start

_start是一个标记(Symbol),符号在汇编制程序序中意味2个地点,能够用在命令中,汇编制程序序通过汇编器的拍卖后全体的号子都被替换到它所代表的地方值。在C中大家可以透过变量名访问一个变量,其实正是读写有些地方的内部存款和储蓄器单元,我们通过函数名调用贰个函数其实正是调转到该函数的第贰条指令所在的地方,所以变量名和函数名都是标志,本质上是代表内部存款和储蓄器地址的。

.globl指令告诉汇编器_start这一个标记要被链接器用到,所以要在对象文件的记号表中给它独特标记。_start就像是C程序的main函数1样卓绝是1体程序的输入,链接器在链接时会查找指标文件中的_start符号代表的地方,把它设置为全体程序的进口地址,所以每种汇编制程序序都要提供一个_start符号并且用.globl表明。假诺2个标志未有用.globl提示评释那个标记就不会被链接器用到。

_start:

_start在此地就像C语言的言语标号1样。汇编器在拍卖汇编制程序序时会总计各种数据对象和每条指令的地方,当汇编器看到如此三个标明时,就把它上边一条指令的地方作为_start那一个符号所表示的地点。而_start这一个标记又比较特殊事一清2楚程序的输入地址,所以下一条指令movl
$一, %eax就成了先后中率先条被实践的一声令下。

movl $1, %eax

那是一条数据传送指令,CPU内部爆发1个数字一,
然后传送到eax寄存器中。mov后边的l表示long,表达是三11个人的传递指令。CPU内部发生的数称为及时数,在汇编制程序序中立马数前边加”$”寄存器前边加”%”,以便跟符号名区分开。

movl $4, %ebx

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

int $0x80

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

  一.
int限令称为软中断指令,能够用那条指令故意产生2个很是。格外的拍卖与中断接近,CPU从用户形式切换来特权格局,然后跳转到内核代码中实践至极处理程序。

  二.
int指令中的马上数0x80是三个参数,在越发处理程序中依据那几个参数决定哪些处理,在linux内核中,int
$0x80这种越发称系统调用(System
Call)。内核提供了广大体系服务供用户程序使用,但这一个类别服务不可能像库函数(比如printf)这样调用,因为在执行用户程序时CPU处于用户格局不可能直接调用内核函数,所以要求通过系统调用切换CPU情势,通过足够处理程序进入基础,用户程序只好通过寄存器传几个参数,之后就要按基本设计好的代码路线走,而不可能由用户程序随心所欲想调那多少个内核函数,那样保险了系统服务被安全的调用,在调用结束后CPU再切换回用户方式,继续执行int指令前边的命令,在用户程序看来就如函数的调用和再次回到一样。

  三.
eax和ebx寄存器的值是传递给系统调用的七个参数,eax的值是系统调用号,一意味着_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]。

 

贰、x八陆的寄存器

  x86的通用寄存器eaxebxecxedxediesi。这么些寄存器在大多数指令中是足以四意使用的。但多少指令限制只好用当中1些寄存器做某种用途,例如除法指令idivl规定被除数在eax寄存器中,edx寄存器必须是0,而除数能够是别的寄存器中。总括结果的商数保存在eax寄存器中(覆盖被除数),余数保存在edx寄存器。

  x86的出奇寄存器ebpespeipeflags。eip是程序计数器。eflags保存总计进度中发生的标志位,包涵进位、溢出、零、负数四个标志位,在x八六的文书档案中那多少个标志位分别称字为CF、OF、ZF、SF。ebp和esp用于爱惜函数调用的栈帧。

  esp为栈指针,用于指向栈的栈顶(下一个压入栈的位移记录的顶部),而ebp为帧指针,指向当前运动记录的最底层。每一个函数的每一次调用,都有它本人单身的3个栈帧,这一个栈帧中维系着所要求的各样新闻。寄存器ebp指向当前的栈帧的底层(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

  注意:ebp指向当前身处系统栈最下边1个栈帧的底层,而不是系统栈的底层。严厉说来,“栈帧尾部”和“栈底”是例外的概念;esp所指的栈帧顶部和系统栈的顶部是同2个职位。

 

三、第四个汇编制程序序

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

.section .data
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,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_items(, %edi,4), %eax
cmpl %ebx, %eax
jle start_loop
movl %eax, %ebx
jmp start_loop
loop_exit:
mov $1, %eax
int $0x80

汇编链接执行,然后echo $?会晤到输出22贰。

 

本条顺序在壹组数中找到一个最大的数,并把它看作程序的淡出状态。那段数在.data段给出:

data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0

 .long指令声美素佳儿组数,每一种数三拾人,相当于C数组。数组起头有个标号data_items,汇编器会把数组的首地址作为data_items符号所表示的地址,data_items类似于C中的数组名。data_items那几个标号未有.globl表明是因为它只在那一个汇编制程序序内部使用,链接器不要求驾驭那个名字的留存。除了.long之外常用的宣示:

  • .byte,也是声称1组数,各种数七人
  • .ascii,例: .ascii “Hello
    World”,注解了13个数,取值为对应字符的ASCII码。和C语言不相同的是这样评释的字符串末尾是未有’\0’字符的。

data_items数组的最终1个数是0,大家在三个循环往复中逐一相比每一个数,遭受0的时候就告一段落循环。在那么些轮回中:

  • edi寄存器保存数组中的当前岗位,每一回比较完多个数就把edi的值加一,指向数组中的下2个数。
  • ebx寄存器保存到近期停止找打客车最大值,要是发现有更加大的数就更新ebx的值。
  • eax寄存器保存当前要比较的数,每一回更新edi之后,就把下二个数读到eax中。

    _start:
    movl $0, %edi

伊始化edi,指向数组的第0个因素。

 

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

那条指令把C语言,数组的第0个因素传送到eax寄存器中。data_items是数组的首地址,edi的值是数组的下标,四表示数组的种种成分占四字节,那么数组中第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地方一。je是多少个准绳跳转指令,它检查eflags中的ZF位,ZF位为1则发出跳转,ZF位为0则不跳转继续执行下一条指令。(条件跳转指令和相比较指令是协作使用的)je的e就表示equal

 

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

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

 

cmpl %ebx, %eax
jle start_loop

把近日数组成分eax和方今停止找到的最大值ebx做比较,若是前者小于等于后者,则最大值未有变,跳转到循环起来相比下1个数,不然继续执行下一条指令。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和MULTIPLIEOdyssey必须是常数,BASE_OR_OFFSET和INDEX必须是寄存器。在有点寻址格局中会省略那4项中的有些项,也正是那么些项是0。

  • 一贯寻址:只利用ADDRESS_OR_OFFSET寻址,例如movl ADDRESS,
    %eax把ADDRESS地址处的三二十位数字传送送到eax寄存器。
  • 变址寻址:movl data_items(,%edi,肆),
    %eax就属于那种格局,用于访问数组很便宜
  • 直接寻址:只使用BASE_OR_OFFSET寻址,例如movl (%eax),
    %ebx,把eax寄存器的值作为地址,把那些地址处的30人数字传送送到ebx寄存器。
  • 基址寻址:只利用ADDRESS_OR_OFFSET和BASE_OR_OFFSET寻址,例如movl
    四(%eax),
    %ebx,用于访问结构体成员比较便于,例如二个结构体的集散地址保存在eax寄存器中,当中三个分子在构造体内偏移量是4字节,要把这一个成员读上去就足以用那条指令。
  • 立马数寻址:正是指令中有贰个操作数是当时数,例:movl $三, %eax。
  • 寄存器寻址:正是指令中有3个操作数是寄存器。在汇编程序中寄存器用助记符来代表,在机器指令中则要用多少个Bit表示寄存器的数码,那多少个Bit与足以用作寄存器的位置,然而和内部存储器地址不在1个地方空间。

 

有关汇编制程序序的Hello
World能够参照笔者的另一篇作品:http://www.cnblogs.com/orlion/p/5316519.html