访问变量
与其他等级的编程语言一样,汇编语言能够用许多方式来访问变量。变量有三种基本的存储方式。
1. 全局变量/静态变量- 在程序数据区(program data section)分配
2. 局部变量/参数- 在栈上分配
3. 堆变量- 在堆上分配
全局,静态变量
全局变量存储在一个固定的地址上(至少对于程序来说,他们是固定的)。访问这些变量的最通常的方式是在指令中明确指出那个固定的地址。
MOV EAX,[1234134H] ; loads EAX with value stored at location 12341234H
INC DWORD PTR TEST2!_nCount ; increments DWORD variable nCount
注意,在symbolic信息可用的时候,debugger会去使用它。
局部变量,参数
局部变量和参数存在于栈上,并且是通过EBP(有时候是ESP)来访问的。优化过的代码通常会清除掉对栈基指针(frame pointer)的依赖,在这样的情况下ESP寄存器被用来访问局部变量,而EBP可以被用来做一个额外的通用寄存器来使用。当你使用一个标准栈基指针的时候,指令看起来应该是这样的。
MOV EAX,[EBP+8] ; load EAX with argument
MOV EAX,[EBP-4] ; load EAX with local variable
有一个记忆的小窍门,当EBP没有作为通用寄存器使用的时候,也就是绝大多数时候,当位移是正的时候,访问的是参数。当位移是负的时候,访问的是局部变量。
注意,典型的第一个传递给函数的参数是EBP+8
堆变量
堆变量存在于堆上,他们是通过指针来访问的。典型情况下需要不只一条指令来访问堆变量。
MOV ESI, TEST2!_m_pFileList ; load the pointer
MOV EAX, [Esi+4] ; read second DWORD (pszName) in heap
另一个需要注意的是,大多数编译器会将经常访问的变量放到寄存器中,以便于提高访问速度。尤其是精简指令计算机。
执行流控制
控制流命令要不就是有条件的(条件满足的时候),要不就是无条件的。这些语句支持函数调用,if-then-else,switch case等高级的语言成分。
无条件跳转指令
1. JMP命令
这个命令简单的设置EIP寄存器为下一条指令的地址。没有任何数据会被存储到栈上,并且不会设置任何标志位。JMP被用在固定的指令分支上。大多数的if-then-else语句族至少需要一条JMP指令。
2. CALL命令
这条指令先存储EIP的值到栈上,然后设置EIP为下一条指令的地址。将EIP压栈允许程序在结束了函数调用之后,回来继续执行CALL语句后面的语句。
对于JMP和CALL指令来说,操作数可以是固定的地址,寄存器的值,或者一个指向分支地址的指针。
3. RET命令
RET指令将当前栈上的值赋给EIP寄存器。该命令用来为传递给栈的参数修复栈指针。
4. INT命令
当INT命令的操作数是一个中断号的时候,该指令会引发一个软件中断。这个与CALL指令差不多,不同之处是EFLAGS寄存器被压入栈中。还有,如果是在user mode中被调用,在切换到kernel mode时也会发生将EFLAG寄存器压栈的操作。中断函数结束的时候,随着RETI指令的执行,EFLAGS寄存器和EIP都会从栈中恢复。
条件跳转指令
1, LOOP Adress
LOOP指令被用来实现高级语言中的循环。直到ECX(计数器)的值为0的时候,它才会走向分支地址。如果ECX不是0,那么ECX会被减一,然后继续循环操作。
XOR EAX,EAX ; clear EAX register
MOV ECX, 5 ; load loop count
START:
ADD EAX,1 ; add one to eax
LOOP START
2. JNX,JE等等
根据条件来跳转的指令会去判断所指定的条件是否为真,若果是就执行跳转。比如,JNZ(jump not zero),操作数中指定的地址直到ZERO标志位被设置为1的时候才会被转过去。这些指令主要被用在if语句块中。
XOR EAX,EAX ; clear eax
MOV ECX,5
START:
ADD EAX,1 ; add one to EAX
DEC ECX ; decrement loop counter
JNZ START