本文简要说明x86架构及x86汇编语言的使用。
1978年Intel发布第一款16位微处理器8086(也称为iAPX86),之后又发布了80186、80286、80386、80486,因而该系列的CPU架构被称为x86。
下面将介绍80386的一些基本汇编指令,注意这里采用nasm语法。
现代的x86处理器(即80386之后)都有8个32位的通用寄存器(General Purpose Registers, GPR),
通常长的(32位)寄存器都可以被划分成小的读写块,如32位eax
,16位ax
,高8位ah
,低8位al
,注意nasm对标号大小写是敏感的,但对寄存器不会。
由于程序并不知道自己会被加载到哪,因此访存如果用绝对地址将会出错,在执行程序时就需要程序重定位这个操作。
在汇编中通过org
指令实现,如org 0A100h
代表该程序中的所有标号都以0A100h
做偏移
将内存分段,程序只需管偏移地址/相对地址就好了 段地址:偏移地址
程序重定位通过设置代码段CS寄存器和数据段DS寄存器实现
在8086中,地址总线是20位的,需要将段寄存器左移4位(0x10h
,相当于16进制左移1位)变为20位,然后再同偏移地址相加。
两种典型情况
0000H
到000FH
0000H
到F000H
段的划分是自由的,它可以起始于任何16字节对齐的位置,也可以是任意长度,只要不超过64KB。 正是由于段的划分非常自由,使得8086的内存访问也非常随意。 同一个物理地址,或者同一片内存区域,根据需要,可以随意指定一个段来访问它。
声明静态数据需要添加数据大小,包括1Byte的db
,2B的dw
,4B的dd
,注意内存都是连续的,按字节(B)寻址
.DATA
var DB 64
var2 DB ? ; uninitialized byte
DB 10 ; no label, location is var2 + 1.
X DW ? ; 2B uninitialized
Y DD 30000;
str DB 'hello',0; 6 bytes starting at str
如果要访问某一大小的内存,则通过添加修饰词byte
、word
、dword
实现(注意nasm是不存储变量类型的),这里的字(word)通常就指16位,双字(double word)32位
mov byte[ebx], 2
函数调用(call)堆栈组织
eax
、ecx
、edx
)call
会将返回地址eip
压入栈中ebp
推入栈,将esp
的值拷贝入ebp
push ebp
mov ebp, esp
sub esp, 12
函数调用例子如下
.486
.MODEL FLAT
.CODE
PUBLIC _myFunc
_myFunc PROC
; Subroutine Prologue
push ebp ; Save the old base pointer value.
mov ebp, esp ; Set the new base pointer value.
sub esp, 4 ; Make room for one 4-byte local variable.
push edi ; Save the values of registers that the function
push esi ; will modify. This function uses EDI and ESI.
; (no need to save EBX, EBP, or ESP)
; Subroutine Body
mov eax, [ebp+8] ; Move value of parameter 1 into EAX
mov esi, [ebp+12] ; Move value of parameter 2 into ESI
mov edi, [ebp+16] ; Move value of parameter 3 into EDI
mov [ebp-4], edi ; Move EDI into the local variable
add [ebp-4], esi ; Add ESI into the local variable
add eax, [ebp-4] ; Add the contents of the local variable
; into EAX (final result)
; Subroutine Epilogue
pop esi ; Recover register values
pop edi
mov esp, ebp ; Deallocate local variables
pop ebp ; Restore the caller's base pointer value
ret
_myFunc ENDP
END
由于x86指令实在太多,这里只摘录一些常用的指令
mov <ra>, <rb>
push <r>
;pusha
:把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈pop <r>
;popa
:把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈add <ra>, <rb>
、sub
:ra+=rbinc
、dec
cmp <ra>, <rb>
mul <r>
:等价于mul ax, <r>
and
、or
、xor
shl
、sal
、shr
、sar
:h为逻辑,a为算术jmp <label>
call <label>
:过程调用ret
:过程返回je <label>
、jne
:上一算术逻辑运算结果jl
、jg
:小于大于loop
:cx不为0时循环cli
/sti
:关中断/开中断int <num>
:中断iret
:中断返回通过标号加方括号访问内存,如mov ax, [mem]
还有一些比较常见的特殊指令:
leave
:等价于恢复堆栈,即mov esp,ebp\n\t pop ebp
The netwide assembler(NASM)是80x86和x86-64系列的汇编器
label: instruction operands ; comment
message db 'hello, world'
msglen equ ($-message)
zerobuf: times 64 db 0 ; executed n times
mov eax,[ebx*2+ecx+offset]
db 'hello'
等价于db 'h','e','l','l','o',0
%define
%macro prologue 1
push ebp
mov ebp,esp
sub esp,%1
%endmacro