C语言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
$?得到上一致漫漫命令的淡出状态。

C语言 1

 

【解释】:汇编程序中因为”.”开头的名不是命令的助记符,不会见为翻成机器指令,而是吃汇编器一些突出之指示,称为汇编指示或者伪操作。

.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用于保护函数调用的栈帧。

  esp啊栈指针,用于指向栈的栈顶(下一个压入栈的移位记录的顶部),而ebp啊帧指针,指向当前走记录的底色。每个函数的每次调用,都生它们好单身的一个栈帧,这个栈帧中保障正所用之各种消息。寄存器ebp指向当前之栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

  注意:ebp指向当前身处系统栈最上面一个栈帧的脚,而无是系统栈的底层。严格说来,“栈帧底部”和“栈底”是例外之定义;esp所指的栈帧顶部及系统栈的顶部是与一个职。

 

其三、第二只汇编程序

求平组数最老价值的汇编程序:

.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 $?会看输出222。

 

此程序于同组数着找到一个极端老之高频,并拿它们当次的淡出状态。这段往往以.data段于闹:

data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,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