汇编语言基础之二 - 各种寻址和过程进出简介

指令标记法

Prefix Instruction [operand 1], [operand 2] ; comment

注意,不能让内存同时作为目的操作数和源操作数。

 

操作数

一般指令的操作数可以有0~2个。

隐式操作数- Implicit Operands

隐式操作数是由指令本身指定的。CLI和STI就是这样的不需要指出操作数的指令。CLI和STI指令设置和清除在EFlags寄存器中的中断标志位。CLI和STI永远只操作那个bit位。

 

寄存器寻址- Register Operands

也就是指令使用寄存器中的值作为源或目的操作数。

CALL    EDI              ; EDI contains address to call

MOV     EAX, EBX     ; EAX<-EBX EAX = destination EBX = source

INC      EAX             ; EAX=EAX+1 – EAX=source and destination

 

立即数- Immediate Operands

立即数是指令的一部分,直接指定了操作数的值。立即数允许用户设置一个常量到变量中,或者引用一个固定的内存地址。

MOV     EAX,177      ; load EAX with 177

MOV     CL,0FFH      ; load CL with 255

CALL    12341234H ; call routine at location 12341234H

 

IO操作数- I/O Operands

IO操作数不在用户态模式程序中使用。IO指令被用来引用IO map中的设备。这些包括绝大多数的硬件设备比如PICS,串行口,并行口,磁盘控制器等等。

IN       AL, 04H    ; read port location 4 into AL register

OUT    04H, AL    ; write AL register to port location 4

 

内存引用操作数

内存引用操作数是指通过许多种内存寻址方式来获取操作数的值的方式。

1. 绝对寻址- Direct Addressing

指的是地址本身用作操作数。这被用来指出程序占用的固定地址,比如说实例的全局变量。

MOV      AL, [12341234H]

INC       DWORD PTR [12341234H]

 

2. 间接寻址- Based Addressing

指使用寄存器来存放地址来作为操作数的方式。这经常被用来解析指针所指向的变量。

MOV      AL,[ECX]

DEC      DWORD PTR [ESI]

 

3. 带有偏移量的间接寻址- Base Plus Displacement Addressing

跟间接寻址差不多,不同之处就是要在寄存器的地址上加上一个额外的偏移量。这种方法用来访问一个结构之中的变量。寄存器中的指针指向这个结构的起始地址,位移能够允许引用到正确的结构元素。这种方式比不得不用指针直接指向每一个要被访问的元素好,因为经常出现一段代码中访问一个结构体的多个元素的情况。这样,寄存器中的指针值只需设置一次,只有偏移量改改就好了。

LEA    EDX,[ESP+0x4]

MOV   ECX, [EBP-0x10]

 

4. 基址+偏移量寻址- Index Plus Displacement Addressing

这种寻址方式与带偏移量的间接寻址一样,区别就是寄存器与固定的偏移量的角色互换了。现在基址是固定的位置,加上一个在寄存器中的偏移量。这种方式主要用于访问静态内存中的数组中的元素。

MOV     EAX,1234124H[ESI]

 

5. 变址寻址- Base Plus Displacement Plus Index Addressing

这是由多种寻址方式混合成的寻址方式。它综合了前面的两种方式。用来访问声明在栈上或者动态分配在堆上的地址。寄存器指向基址,另一个寄存器保存偏移量,还有一个固定的偏移量可以被包含进来。

MOV EAX,[EBP+8][ESI]

INC WORD PTR [EBX+EAX*2]

 

 

过程调用的进入和退出

在进入和离开一个程序的时候,代表性的栈框架(stack frame)会被建立,从而使程序能够方便的访问参数和局部变量。Intel处理器使用EBP和ESP两个寄存器来完成这项工作。下面的信息就show出了调用子函数的时候,一个栈框架是如何建立的例子。

 

过程调用部分

MOV    EAX, Argument 1  ; load EAX with value of Argument 1

PUSH  EAX                      ; push first argument onto stack

CALL   sub1

在调用函数之前,参数被压栈。分解PUSH指令,它做了下面的事情:

ESP <- ESP – 4 ; decrement stack pointer

SS:[ESP] <- operand ; load operand into location pointed to by ESP

分解CALL指令,下面是call的指令:

PUSH EIP                     ; push return address on stack

EIP <- destination       ; EIP set to first instruction on the sub1 routine

这时栈看起来应该是这样子的:

????

   

EIP(也就是返回值)

<-

ESP

Argument 1

   

????

   

 

过程入口部分

典型的过程入口看起来应该是这样子的:

PUSH    EBP          ; save the base pointer

MOV     EBP,ESP    ; setup new stack frame

SUB      ESP,24      ; allocate local variables

发生了三件事,第一,我们保存了EBP寄存器的值;多数的编译器假设EBP寄存器不会被调用的过程破坏。第二,我们建立了栈框架,工作拷贝ESP到EBP寄存器,我们为过程做到了这一点。第三,我们为局部变量创造了空间,这些空间是分配在栈上的。现在栈看上去应该是这样的:

     

????

   

Local Var n

<-

ESP

     
     
     

Local Var 1

   

Saved EBP

<-

EBP

EIP(返回值)

   

Argument 1

   

????

   

 

访问参数和局部变量

要访问参数,可以在过程的任何地方使用如下的指令:

MOV    EAX,[EBP+8]    ; read argument one into EAX register

要访问局部变量,使用如下的指令即可:

MOV    EAX,[EBP-4]     ; read first local variable

 

过程退出

典型的退出部分看起来像下面这样:

MOV    ESP,EBP  ; restore stack pointer

POP    EBP          ; restore base pointer

RET     4              ; return

退出一个过程先要恢复ESP,恢复的过程是把它设置成为与EBP的值相同即可。然后恢复EBP到它刚刚进入这个过程调用时的样子,该值被早些时候压入了栈中。最后的指令是RET,分析RET指令如下:

EIP <- POP() ; reset our instruction pointer

ESP <- ESP + count ; fixup stack for arguments

你可能感兴趣的:(汇编语言)