From cd2d7a4507aa13f5131f71a232aa3bc04911d61f Mon Sep 17 00:00:00 2001 From: lniwn Date: Mon, 29 Jul 2019 23:15:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/posts/2018/Win32汇编学习.md | 572 ------------------------ 1 file changed, 572 deletions(-) delete mode 100644 content/posts/2018/Win32汇编学习.md diff --git a/content/posts/2018/Win32汇编学习.md b/content/posts/2018/Win32汇编学习.md deleted file mode 100644 index 54800e1..0000000 --- a/content/posts/2018/Win32汇编学习.md +++ /dev/null @@ -1,572 +0,0 @@ ---- -title: Win32汇编学习 -date: 2018-02-12 17:48:00 -tags: [win32, 汇编] -categories: windows ---- - -本文记录了C函数调用过程,以及常用的汇编函数指令集。 - - - -## 函数调用过程 - -栈帧,即stack frame,其本质就是一种栈,只是这种栈专门用于保存函数调用过程中的各种信息,栈帧有栈顶(TOS)和栈底之分,其中栈顶的地址最低,栈底的地址最高,增长方向为高地址向低地址方向。在x86-32bit中,用esp(extended stack pointer)一直指向栈顶,ebp(extended base pointer)指向栈底。 - -![栈帧方向](stackframe.png) - -一般来说,ebp和esp之间的区域叫做栈帧,每调用一个函数,就会生成一个新的栈帧。在函数调用过程中,我们将调用函数称为“调用者(caller)”,将被调用的函数称为“被调用者(callee)”。 - -函数调用过程如下: - -```asm -push ebp ; 保存ebp的值 -mov ebp,esp ; 将esp的值赋给ebp,使新的ebp指向栈顶 -sub esp,0d8h ; 分配额外的空间给局部变量,对于大内存,使用call __alloca_probe (010FD4EAh) 的方式来分配 -``` - -windows平台,返回值保存在eax中。 - -## x86汇编指令 - -### 寄存器 - -![x86寄存器](x86-registers.png) - -X86处理器中有8个32位的通用寄存器。由于历史的原因,EAX通常用于计算,ECX通常用于循环变量计数。ESP和EBP有专门用途,ESP指示栈指针(用于指示栈顶位置),而EBP则是基址指针(用于指示子程序或函数调用的基址指针)。如图中所示,EAX、EBX、ECX和EDX的前两个高位字节和后两个低位字节可以独立使用,其中两位低字节又被独立分为H和L部分,这样做的原因主要是考虑兼容16位的程序,具体兼容匹配细节请查阅相关文献。 - -应用寄存器时,其名称大小写是不敏感的,如EAX和eax没有区别。 - -### 内存和寻址模式 - -#### 声明静态数据区 - -可以在X86汇编语言中用汇编指令.DATA声明静态数据区(类似于全局变量),数据以单字节、双字节、或双字(4字节)的方式存放,分别用DB,DW, DD指令表示声明内存的长度。在汇编语言中,相邻定义的标签在内存中是连续存放的。 - -| .DATA | | | -| ----- | -------- | ------------------------------------- | -| var | DB 64 | ;声明一个字节,并将数值64放入此字节中 | -| var2 | DB ? | ; 声明一个未初始化的字节. | -| | DB 10 | ; 声明一个没有label的字节,其值为10. | -| X | DW ? | ; 声明一个双字节,未初始化. | -| Y | DD 30000 | ; 声明一个4字节,其值为30000. | - -还可以声明连续的数据和数组,声明数组时使用DUP关键字。 - -| .DATA | | | -| ----- | ------------- | ------------------------------------------------------------ | -| Z | DD 1, 2, 3 | ; Declare three 4-byte values, initialized to 1, 2, and 3. The value of location Z + 8 will be 3. | -| bytes | DB 10 DUP(?) | ; Declare 10 uninitialized bytes starting at location *bytes*. | -| arr | DD 100 DUP(0) | ; Declare 100 4-byte words starting at location arr, all initialized to 0 | -| str | DB 'hello',0 | ; Declare 6 bytes starting at the address str, initialized to the ASCII character values for hello and the null (0) byte. | - -#### 寻址模式 - -现代X86处理器具有232字节的寻址空间。在上面的例子中,我们用标签(label)表示内存区域,这些标签在实际汇编时,均被32位的实际地址代替。除了支持这种直接的内存区域描述,X86还提供了一种灵活的内存寻址方式,即利用最多两个32位的寄存器和一个32位的有符号常数相加计算一个内存地址,其中一个寄存器可以左移1、2或3位以表述更大的空间。下面例子是汇编程序中常见的方式。 - -| 指令 | 说明 | -| -------------------- | ----------------------------------------------- | -| mov eax, [ebx] | ; 将ebx值指示的内存地址中的4个字节传送到eax中 | -| mov [var], ebx | ; 将ebx的内容传送到var的值指示的内存地址中. | -| mov eax, [esi-4] | ; 将esi-4值指示的内存地址中的4个字节传送到eax中 | -| mov [esi+eax], cl | ; 将cl的值传送到esi+eax的值指示的内存地址中 | -| mov edx, [esi+4*ebx] | ; 将esi+4*ebx值指示的内存中的4个字节传送到edx | - -#### 长度规定 - -在声明内存大小时,在汇编语言中,一般用DB,DW,DD均可声明的内存空间大小,这种现实声明能够很好地指导汇编器分配内存空间,但是,对于`mov [ebx], 2`,如果没有特殊的标识,则不确定常数2是单字节、双字节,还是双字。对于这种情况,X86提供了三个指示规则标记,分别为`BYTE PTR`, `WORD PTR`, and `DWORD PTR`,如上面例子写成:`mov BYTE PTR [ebx], 2`, `mov WORD PTR [ebx], 2`, `mov DWORD PTR [ebx], 2`,则意思非常清晰。 - -### 汇编指令 - -汇编指令通常可以分为数据传送指令、逻辑计算指令和控制流指令。本节将讲述其中最重要的指令,以下标记分别表示寄存器、内存和常数。 - -| 标识 | 描述 | -| ------- | --------------------------------------------------------- | -| | 32位寄存器 (EAX, EBX, ECX, EDX, ESI, EDI, ESP, or EBP) | -| | 16位寄存器 (AX, BX, CX, or DX) | -| | 8位寄存器(AH, BH, CH, DH, AL, BL, CL, or DL) | -| | 任何寄存器 | -| | 内存地址 (e.g., [eax], [var + 4], or dword ptr [eax+ebx]) | -| | 32为常数 | -| | 16位常数 | -| | 8位常数 | -| | 任何8位、16位或32位常数 | - -#### 数据传送指令 - -- **mov** — Move (Opcodes: 88, 89, 8A, 8B, 8C, 8E, ...) - -mov指令将第二个操作数(可以是寄存器的内容、内存中的内容或值)复制到第一个操作数(寄存器或内存)。mov不能用于直接从内存复制到内存,其语法如下所示: - -``` asm -mov , -mov , -mov , -mov , -mov , -``` - -*Examples* -`mov eax, ebx — 将ebx的值拷贝到eax` -`mov byte ptr [var], 5 — 将5保存找var指示内存中的一个字节中` - -- **push**— Push stack (Opcodes: FF, 89, 8A, 8B, 8C, 8E, ...) - -push指令将操作数压入内存的栈中,栈是程序设计中一种非常重要的数据结构,其主要用于函数调用过程中,其中ESP只是栈顶。在压栈前,首先将ESP值减4(X86栈增长方向与内存地址编号增长方向相反),然后将操作数内容压入ESP指示的位置。其语法如下所示: - -```asm -push -push -push -``` - -*Examples* -`push eax — 将eax内容压栈` -`push [var] — 将var指示的4直接内容压栈` - -- **pop**— Pop stack - -pop指令与push指令相反,它执行的是出栈的工作。它首先将ESP指示的地址中的内容出栈,然后将ESP值加4. 其语法如下所示: - -```asm -pop -pop -``` - -*Examples* -`pop edi — pop the top element of the stack into EDI.` -`pop [ebx] — pop the top element of the stack into memory at the four bytes starting at location EBX.` - -- **lea**— Load effective address - - lea实际上是一个载入有效地址指令,将第二个操作数表示的地址载入到第一个操作数(寄存器)中。其语法如下所示: - -*Syntax* -`lea ,` - -*Examples* -`lea eax, [var] — var指示的地址载入eax中.` -`lea edi, [ebx+4*esi] — ebx+4*esi表示的地址载入到edi中,这实际是上面所说的寻址模式的一种表示方式.` - -#### 算术和逻辑指令 - -- **add**— Integer Addition - -add指令将两个操作数相加,且将相加后的结果保存到第一个操作数中。其语法如下所示: - -```asm -add , -add , -add , -add , -add , -``` - -*Examples* -`add eax, 10 — EAX ← EAX + 10` -`add BYTE PTR [var], 10 — 10与var指示的内存中的一个byte的值相加,并将结果保存在var指示的内存中` - -- **sub**— Integer Subtraction - -sub指令指示第一个操作数减去第二个操作数,并将相减后的值保存在第一个操作数,其语法如下所示: - -```asm -sub , -sub , -sub , -sub , -sub , -``` - -*Examples* -`sub al, ah — AL ← AL - AH` -`sub eax, 216 — eax中的值减26,并将计算值保存在eax中` - -- **inc, dec**— Increment, Decrement - -inc,dec分别表示将操作数自加1,自减1,其语法如下所示: - -```asm -inc -inc -dec -dec -``` - -*Examples* -`dec eax — eax中的值自减1.` -`inc DWORD PTR [var] — *var指示内存中的一个4-byte值自加1`* - -- **imul**— Integer Multiplication - -整数相乘指令,它有两种指令格式,一种为两个操作数,将两个操作数的值相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器;第二种格式为三个操作数,其语义为:将第二个和第三个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器。其语法如下所示: - -```asm -imul , -imul , -imul ,, -imul ,, -``` - -*Examples* - -`imul eax, [var] — eax→ eax * [var]` - -`imul esi, edi, 25 — ESI → EDI * 25` - -- **idiv**— Integer Division - -idiv指令完成整数除法操作,idiv只有一个操作数,此操作数为除数,而被除数则为EDX:EAX中的内容(一个64位的整数),操作的结果有两部分:商和余数,其中商放在eax寄存器中,而余数则放在edx寄存器中。其语法如下所示: - -*Syntax* -`idiv ` -`idiv ` - -*Examples* - -`idiv ebx` - -`idiv DWORD PTR [var]` - -- **and, or, xor**— Bitwise logical and, or and exclusive or - -逻辑与、逻辑或、逻辑异或操作指令,用于操作数的位操作,操作结果放在第一个操作数中。其语法如下所示: - -```asm -and , -and , -and , -and , -and , -or , -or , -or , -or , -or , -xor , -xor , -xor , -xor , -xor , -``` - -*Examples* -`and eax, 0fH — 将eax中的钱28位全部置为0,最后4位保持不变.` -`xor edx, edx — 设置edx中的内容为0.` - -- **not**— Bitwise Logical Not - -位翻转指令,将操作数中的每一位翻转,即0->1, 1->0。其语法如下所示: - -`not ` -`not ` - -*Example* -`not BYTE PTR [var] — *将var指示的一个字节中的所有位翻转*.` - -- **neg**— Negate - -取负指令。语法为: - -`neg ` -`neg ` - -*Example* -`neg eax — EAX → - EAX` - -- **shl, shr**— Shift Left, Shift Right - -位移指令,有两个操作数,第一个操作数表示被操作数,第二个操作数指示位移的数量。其语法如下所示: - -```asm -shl , -shl , -shl , -shl , -shr , -shr , -shr , -shr , - -``` - -*Examples* - -`shl eax, 1 — Multiply the value of EAX by 2 (if the most significant bit is 0),左移1位,相当于乘以2` - -`shr ebx, cl — Store in EBX the floor of result of dividing the value of EBX by 2*n* where *n* is the value in CL.` - -#### 控制转移指令 - -X86处理器维持着一个指示当前执行指令的指令指针(IP),当一条指令执行后,此指针自动指向下一条指令。IP寄存器不能直接操作,但是可以用控制流指令更新。 - -> ``` -> mov esi, [ebp+8] -> begin: xor ecx, ecx -> mov eax, [esi] -> -> ``` - -如第二条指令用begin指示,这种标签的方法在某种程度上简化了汇编程序设计,控制流指令通过标签实现程序指令跳转。 - -- **jmp** — Jump - -控制转移到label所指示的地址,(从label中取出执行执行),如下所示: - -`jmp