关于汇编语言的总结, 原则是只记录要点

实例

整数加减

.386
.model flat, stdcall
;flat: 内存模式, stdcall: 子程序调用规范
.stack 4096
;栈大小
ExitProcess PROTO, dwExitCode:DWORD
;标准 windows 服务
.code
main PROC
mov eax, 5
add eax, 6

INVOKE ExitProcess, 0
main ENDP
END main

子程序

注意问题

  1. 清除堆栈参数
  2. 保存寄存器现场, 可以用uses eax...
  3. 局部变量
;c 调用规范
main proc
push 5
push 6
call addtwo
add esp, 8;移除堆栈参数
mov eax, ebx

y_param equ [ebp+12]
x_param equ [ebp+8]
addtwo proc
push ebp
mov ebp, esp;
mov eax, y_param
add eax, x_param
pop ebp
ret
mysub endp
;stdcall 规范
main proc
push 5
push 6
call addtwo
mov eax, ebx

y_param equ [ebp+12]
x_param equ [ebp+8]
addtwo proc
push ebp
mov ebp, esp
mov eax, y_param
add eax, x_param
pop ebp
ret 8;清除堆栈
mysub endp
;局部变量
x_local equ dword ptr [ebp-4]
y_local equ dword ptr [ebp-8]
addtwo proc
push ebp
mov ebp, esp
;创建局部变量
sub esp, 8
mov x_local, 10
mov y_local, 20

;删除局部变量
mov esp, ebp

pop ebp
ret 8;清除堆栈
mysub endp
截屏2021-06-07 下午10.39.50

汇编要点总结

操作模式

保护模式

所有指令特性可用, 分配给程序的独立内存区域叫

32 位保护模式下, 可寻址 4GB 地址空间

虚拟 8086 模式

保护模式的一个子模式

实地址模式

程序直接访问系统内存和硬件设备

寻址 1MB 地址空间

系统管理模式

电源管理, 系统安全, 通常由计算机制造商实现

寄存器

  • 数据寄存器组(可以用作 16, 8 位, 但是此时不能用作指示器, 变址寄存器?)
    • EAX 累加器, 乘除指令默认使用, 乘法乘数和积, 除法被除数和商
    • EBX 基址寄存器
    • ECX 计数寄存器, 默认循环计数
    • EDX 数据寄存器, 乘法高位, 除法余数
  • 指示器变址寄存器组(存放偏移地址, 用作指示器或者变址寄存器, 可用作 16, 不能用作 8)
    • ESI 源操作数指示器
    • EDI 目的操作数指示器
    • EBP 基址寄存器
    • ESP 专用堆栈指示器, 一般不做数据寄存器
  • 段寄存器
    • CS 代码段寄存器
    • SS
    • DS
    • ES, FS, GS
  • 指令指针
    • EIP 保护下一条将要执行的指令的地址
  • 标志寄存器 EFLAGS
    • zf: 零标志位, 相关指令执行后结果为 0 则 zf=1, 否则 0 mov ax,1 sub ax,1
    • pf: 奇偶标志位, 结果中 1 为偶数 pf=1, 否则 0
    • sf: 符号标志位, 结果为, sf=1, 否则为 0
    • cf: 进位标志位, 在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高有效位向更高位的进位值/借位值,产生进位或向更高位借位都会使CF=1
    • of: 溢出标志位, 有符号运算结果太大或太小 of=1, 否则 0

数据类型

byte 8, word 16, dword 32, qword 64

这里写图片描述

32 位入栈操作栈顶指针-4

指令

mov

;mov 不能从内存到内存
var word ?
mov ax, var;将 var 内存的值赋给 ax 寄存器, 而不是地址, 所以 mov 指令叫数据传送指令
;movzx 0 扩展
;movsx 符号扩展
;xchg 交换两个操作数的内容
array byte 10h, 20h, 30h, 40h
mov al, array;将 array 的第一个字节传送到 al
mov al, [array+1];将 array 的第二个字节传送到 al
mov al, [esi];解析 esi 中的偏移量, 将数据传到 al 中, 单独使用[esi]需要 ptr 限制大小

算数指令

;自增自减 inc dec
inc ax

;加法 add
add eax, var; eax+var->eax

;减法 sub
sub eax, var;eax-var->eax

;取负 neg
neg eax

;乘法 mul 无符号数乘法
mov al, 5h
mov bl, 10h

mul bl;bl*al->ax
;乘法 imul 有符号数乘法
;单操作数
imul bx;ax*bx->dx:ax
;双操作数
imul ax, var;ax*var->ax 有截断
;三操作数 imul reg, reg/mem, imm
imul ax, bx, 8;bx*8->ax

;除法 div 无符号数除法
mov ax, 0083h
mov bl, 2
div bl;al = ax/bl, ah = ax%bl
;除法 idiv 有符号数除法
DIV (unsigned divide) 无符号数除法

格式:DIV SRC

执行的操作:

  字节操作:16位被除数在AX,8位除数为源操作数,结果的8位商在AL中,8位余数在AH中。表示为

  (AL)<-(AX)/(SRC) 的商
(AH) <-(AX)/(SRC) 的余数

  字操作:32位被除数放在DX,AX中。其中DX为高位字,16位除数为源操作数,结果的16位端在AX中,16位余数在DX中。表示为
(AX)<-(DX,AX)/(SRC) 的商
(DX)<-(DX,AX)/(SRC) 的余数

  双字操作:64位被除数在EDX,EAX中,其中EDX为高位双字,32位除数为源操作数,结果的32位商在EAX中,32位余数在EDX中,表示为
(EAX)<-(EDX,EAX)/(SRC) 的商
(EDX)<-(EDX,EAX)/(SRC) 的余数。
商和余数均为无符号数。



;单操作数
.data
byteval sbyte -48
.code
mov al, byteval
cbw;扩展符号
mov bl, +5
idiv bl; al = -9, ah = -3

;布尔运算: and, or, xor, not, test=and 但是不改变目标操作数

;移位
;shl 左移 sal 算数左移 rol 循环左移
;shr 右移补 0 sar 算数右移补符号位 ror 循环右移

循环

;loop ecx 每次循环-1
mov ax, 0
mov ecx, 5; 初始化为 0 不停止
L1:
inc ax
loop L1
;loopz = loope, 等于 ecx=ecx-1, if ecx>0 and zf=1 then jump to destination

跳转

;cmp jcond
cmp eax, 5
je L1
;jcond
;ABOVE, BELOW 无符号, GREATER LESS 有符号
JA; JUMP WHEN ABOVE
JNA; JUMP WHEN NOT ABOVE
JAE; JUMP WHEN ABOVE OR EQUAL
JNAE; JUMP WHEN NOT ABOVE OR EQUAL 不大于, 且不等于

JB; JUMP WHEN BELOW
JNB; JUMP WHEN NOT BELOW
JBE; JUMP WHEN BELOW OR EQUAL
JNBE; JUMP WHEN NOT BELOW OR EQUAL

JG; JUMP WHEN GREATER
JNG; JUMP WHEN NOT GREATER
JGE; JUMP WHEN GREATER OR EQUAL
JNGE; JUMP WHEN NOT GREATER OR EQUAL

JL; JUMP WHEN LESS
JNL; JUMP WHEN NOT LESS
JLE; JUMP WHEN LESS OR EQUAL
JNLE; JUMP WHEN NOT LESS OR EQUAL

JE; JUMP WHEN EQUAL
JZ; JUMP WHEN HAS ZERO FLAG JZ=JE
JNE; JUMP WHEN NOT EQUAL
JNZ; JUMP WHEN NOT HAS ZERO FLAG
JO; JUMP WHEN HAS OVERFLOW FLAG
JC; JUMP WHEN HAS CARRY FLAG
;pf: 奇偶标志位, 结果中 1 为偶数 pf=1, 否则 0
JP; JUMP WHEN HAS PARITY(奇偶) FLAG 偶校验
JNP; JUMP WHEN NOT HAS PARITY FLAG 奇校验
JS; JUMP WHEN HAS SIGN FLAG
JNS; JUMP WHEN NOT HAS SIGN FLAG

子程序与堆栈

;push 先减少 esp, 再将操作数复制到堆栈
;pop 先将操作数复制, 再增加 esp
;pushfd 保存 flags, popfd 恢复 flags
;pushad popad 32 位, pusha popa 16 位

;call 1.调用之后的地址压入堆栈 2.子程序地址加载到 eip
;ret 1.esp 指向的数值弹出到 eip

;uses 保护寄存器, 首尾加 push pop, 但是子程序中不能使用 esp+偏移量
arraysum proc uses esi ecx
mov eax, 0
L1:
add eax, [esi]
add esi, type dword
loop L1
ret
arraysum endp

;enter numbytes, nestinglevel/0
;1.push ebp 2.mov ebp, esp 3.sub esp, numbytes
mysub proc
enter 8, 0

;leave
;1.mov esp, ebp 2.pop ebp

;invoke
push type array
push lengthof array
push offset array
call dumparray
;等效于, 其中 offset array 可以换为 addr array
invoke dumparray, offset array, lengthof array, type array

;proto 指定程序的外部过程
exitprocess proto
.code
mov ecx, 0
call exitprocess
;函数定义
arraysum proc uses esi ecx
ptrarray:ptr dword,
szarray:dword
arraysum endp
;函数声明
arraysum proto, ptrarray:ptr dword, szarrya:dword

字符串

;重复指令
;repe 如果相等继续, 以 ecx 为计数器

;movsb, movsw, movsd 将 esi 内存数据复制到 edi 内存位置
cld;清除方向标志位, cld 正向, std 反向
mov esi, offset string1
mov edi, offset string2
mov ecx, 10
rep movsb;将 esi 中的十个字节传送到 edi 寻址的内存位置(string2)

;cmpsb, cmpsw, cmpsd 比较 esi 和 edi 寻址的内存数据
.data
source dword 1234h
target dword 5678h
.code
mov esi, offset source
mov edi, offset target
cld
mov ecx, lengthof source
repe cmpsd;比较双字, 如果相等就重复, 直到 ecx=0 或者发现不相等

;scasb, scasw, scasd 比较累加器 al, ax, wax 与 edi 寻址的内存数据
.data
alpha byte "abcdefgh", 0
.code
mov edi, offset alpha
mov al, 'f'
mov ecx, lengthof alpha
cld
repne scasb;如果不相等就继续比较
jnz quit;如果未发现字符就退出

;stosb, stosw, stosd 将累加器内容保存到 edi寻址的内存位置
.data
count = 100
string1 byte count dup(?)
.code
mov al, 0ffh
mov edi, offset string1
mov ecx, count
cld
rep stosb;用 al 将 string1 填充

;lodsb, lodsw, lodsd 将 esi 寻址的内存数据加载到累加器
;将数组中的每一个元素乘一个常数
.data
array dword 1,2,3,4,5,6,7,8,9,10
multiplier dword 10
.code
main proc
cld
mov esi, offset array
mov edi, esi
mov ecx, lengthof array
L1:lodsd
mul multiplier
stosd
loop L1
exit
main endp
end main

其他

;等号伪指令, 将符号和一个整数表达式连接起来
count = 100
;EQU 伪指令, 将符号和一个整数表达式或者任意文本连接起来
count EQU 100
count equ <100>;文本替换

;$当前地址计数器

;lea 返回间接操作数的地址
makearray proc
push ebp
mov ebp, esp
sub esp, 32
lea esi, [ebp-30]
;mov esi, offset [ebp-30]不可, 因为 offset 只能用于编译时已知的地址
mov ecx, 30
L1:
mov byte ptr [esi], '*'
inc esi
loop L1
add esp, 32
pop ebp
ret
makearray endp

;offset 返回一个变量与其所在段起始地址之间的距离
mov esi, offset var; esi 中为 var 变量的偏移地址
mov esi, offset array + 4;先得到 array 的偏移量再加 4

;ptr 重写操作数默认的大小类型
.data
myDouble dword 12345678h
.code
mov ax, word ptr myDouble;ax = 5678h 小端
;直接 mov ax, myDouble 是不允许的

;type 返回操作数或数组中每个元素的大小(字节)
;lengthof 返回数组中元素的个数
;sizeof 返回数组初始化时使用的字节数
;typedef 创建用户定义类型
pbyte typedef ptr byte

dosbox

mount c ~/Desktop/netclass/asm/asm/dosbox
c:
dir

基础:

数制转换

  • 使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程

  • 普遍地说,每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。

  • DB, DW, DD, DQ, DT 依次为 1, 2, 4, 8, 10

  • x86/amd64汇编指令的两大风格分别是Intel汇编与AT&T汇编,分别被Microsoft Windows/Visual C++GNU/Gas采用(Gas也可使用Intel汇编风格)

  • 项目 Intel风格(dosbox使用的是这种) AT&T风格
    操作数顺序 目标操作数在前 源操作数在前
    寄存器 原样 加%前缀
    立即数 原样 加$前缀
    16进制立即数 用后缀B与H分别表示二进制与十六进制 对于16进制字母开头的要加前缀0 加前缀0x
    访问内存长度的表示 前缀BYTE PTR, WORD PTR, DWORD PTR和QWORD PTR表示字节,字,双字和四字 后缀b,w,l,q表示字节,字,双字和四字
    引用全局或静态变量var的值 [var] var
    引用全局或静态变量var的地址 var $var
    引用局部变量 需要基于栈指针(rsp)
    绝对寻址 [imm] imm
    间接寻址 [reg] (%reg)
    基址相对寻址 [reg +imm] imm(%reg)
    变址寻址 [base+index] (base,index)
    变址寻址 imm[base+index] imm(base,index)
    比例变址寻址 imm[base + index * scale ] imm(base, index, scale)
    scale只能是1,2,4,8其中的一个数字(1省略不写就是普通变址寻址)
    代码注释 单行注释用;+注释内容。例如:mov rax, rdx ;这里是注释
    注意 这里imm为立即数,base和index为寄存器,scale为伸缩量

区分 地址 和 数

  • 举例

    DS: [1000h]; 这是一个地址, 位置是 1000h
    3000h; 这是一个数, 大小是 3000h

    • 助记符--->机器指令
    • 变量--->操作数存放地址
    • 指令前的标号--->该指令的存放地址

为什么要分段(内存, 虚拟内存, 分段部件, 分页部件)

  • 历史

    • 1978年 推出 16 位 cpu8086, 内外数据线为 16 位, 地址总线为 20 位, 主存寻址 1MB
    • 1982年 推出 cpu80286, 内外数据线为 16 位, 地址总线为 24 位, 主存寻址 16MB
    • 1985年 推出32位 cpu80386, 内外数据线为 32 位, 地址总线为 32 位, 主存寻址 4GB(1MB*2^12)
  • 物理原因

    • 总线 20 位 ---> 寻址 1MB
    • 总线 32 位 ---> 寻址 4G
    • 段寄存器为 16 位 ---> 段的大小为 64K
    • 最低端 80X86 16 位虚拟机中, 内部结构是 16 位, 主线是 20 位, 为了解决这一问题:
      • 将 1MB(20 位)的主存按 64KB(16 位)分段
      • 设置四个段寄存器 CS, DS, SS, ES 保存段首址(20 位的高 16 位), 将这个 16 位(左移四位再变成 20 位)加上数据的偏移地址就得到了物理地址
      • 其中 CS--->IP, SS--->SP, 一般情况下不需要定义附加数据段, 如果必须定义, 最简单的方法是让附加数据段与数据段重合.
    • 32 位暂时没看😬

内存中的数据存放

  • 高八位在在高地址, 低八位在低地址

寄存器寻址

  • 寄存器寻址 MOV AX, BX
  • 寄存器间接寻址 MOV AX, [SI] ; AX=地址为 SI 的值 的值
  • 变址寻址 MOV AL, [R*F] + V; 其中 F 应该是为了应对字节, 字, 双字的
  • 基址加变址寻址 MOV AX, [BR+IR*F+V]; 默认段寄存器由 BR 决定, 为了表示矩阵
  • 立即寻址 MOV AX, 036H
  • 直接寻址 MOV DS:[20H], CL

子程序

  • NEAR FAR
    • NEAR 可省略
    • FAR
    • 区别主要在于 NEAR 只是把 ip 入栈, 把 ea 赋值给 ip, FAR 在 NEAR 的基础上还要把 CS 入栈(最先入 cs, 再入 ip)
  • RET 根据 NEAR 还是 FAR 出栈
  • 传递参数
    • 寄存器法
    • 约定单元法
    • 堆栈法

模块化处理

F2T10.ASM
NAME F2T10
PUBLIC F2T10; 这里的 public 指明了 F2T10 是可以被其他模块调用的, 该语句可以放在任何地方
DATA SEGMENT USE16 PARA PUBLIC 'DATA'
;段名 segment 使用类型 定位方式 组合方式 '类别'
;定位方式中para为默认, 定义段在什么样的起始边界开始
;类别的作用是生成 exe 的时候进行分组(分配空间)
;组合方式连在一起, public 同类别段会放在一起
...
DATA ENDS

CODE SEGMENT USE16 PARA PUBLIC 'CODE'
ASSUME CS:CODE, DS:DATA
F2T10 PROC
...
F2T10 ENDP
CODE ENDS
END
-----------------------------------------
MAIN.ASM
;以上是一个子模块, 下面的是主模块
NAME MAIN
EXTRN F2T10:NEAR; 这里指明 F2T10 是外部模块的, 例如子程序, 如果两个代码块的类别名相同, 相当于在一个段里, 用 EXTRN F2R10:NEAR, 否则用 far
IF1
;IF XXX ... ELSE ... ENDIF 如果条件成立就执行块中的语句, 其中if 有几个固定搭配
INCLUDE MACRO.LIB
ENDIF
.386
DATA SEGMENT USE16 PARA PUBLIC 'DATA'
...
DATA ENDS

CODE SEGMENT USE16 PARA PUBLIC 'CODE'
ASSUME CS:CODE, DS:DATA
START:...
CODE ENDS
START END

masm main.asm
masm F2T10.asm
然后执行:LINK MAIN+F2T10;生成 main.exe

字符串操作

  • 默认:
    • 源串指示器 SI
    • 目的串指示器 DI
    • 重复次数 CX
    • SCAS 搜索值 AX
    • LODS 目的地址 AX
    • STOS 源地址
  • MOVS
  • CMPS
    • REPE CMPS 未比较完且相等时继续
    • REPNE CMPS 未比较完且不相等时继续
  • SCAS 在 DI 中搜索 AL
  • LODS
  • STOS

输入输出

I/O 空间

  • IN
    • IN AL/AX/EAX, 立即数
    • IN AL/AX/EAX, DX
  • OUT 同上

中断

win32

  • 32 位
  • 输入输出退出等需要调用 api
    • 动态链接
    • user32.lib
    • kernel32.lib

指令合集

MOV OPD, OPS; MOVE DEST SRC

XLAT; TRANSLATE ([BX+AL])->AL

LEA OPD, OPS; LOAD EFFECTIVE ADDRESS 立即寻址 等价于 MOV OPD, OFFSET OPS, 其中 OPD 必须是 16/32 位寄存器

;标志位?

NEG OPD; NEGTIVE 将 OPD 的每一位取反最后加一, 包括符号位, 得到结论: 负数"取补码"得到绝对值

IMUL OPD, OPS;INTEGER MULTIPLICATION (OPD)*(OPS)->OPD
IMUL OPD, OPS, N; (OPS)*N->OPD
IMUL OPS;
(AL)*(OPS)->AX
(AX)*(OPS)->DX, AX
(EAX)*(OPS)->EDX, EAX
MUL

DIV OPS;
(AX)/(OPS)->AL 商, AH 余数
(DX, AX)/(OPS)->AX 商, AH 余数
(EDX, EAX)/(OPS)->EAX商, EDX余数
IDIV

NOT OPD; 取反
AND OPD, OPS; 逻辑与
TEST OPD, OPS
常见用法: TEST ECX, ECX 判断 ecx 是否为空, 如果为空则 zf=1
总结: test 是逻辑与运算, cmp 是 sub 运算, cmp a1, a2 执行操作 a1-a2
XOR OPD, OPS; 异或

SAL OPD, N 或者 SHL OPD, N; ARITHMATIC SHIFT LEFT, SHIFT LEFT 左移, 逻辑算数都一样
SHR OPD, N; 逻辑右移, 加 0
SAR OPD, N; 算数右移, 加符号位

ROL OPD, N; 循环左移
ROR OPD, N
RCL OPD, N; ROTATE LEFT WITH CARRY 带进位循环左移
RCR OPD, N




INT; INTERRUPT

DW; DEFINE WORD

PROC; PROCEDURE

ENDS; END SEGMENT

PTR; POINTER

MOVSX; EXTENDED MOVE WITH SIGN DATA
MOVZX; EXTENDED MOVE WITH ZERO DATA


***********************


PUSH OPS; SP=SP-1

POP OPD; SP=SP+1

DIV;https://blog.csdn.net/loovejava/article/details/7044242

IMUL OPD, OPS; 有符号数乘法
IMUL OPD, OPS, N; OPS*N->OPD
IMUL OPS; AL*OPS->AX 字节
AX*OPS->DX,AX 字
EAX*OPS->EDX,EAX 双字
MUL;无符号乘法,用法同上

ADD AX,10;把 ax 加上 10 再存到 ax 里

SUB

LOOP 标号

逻辑移位, 总是用 0 补充
SHL EDX, 7;EDX 左移 7 位
shr ;右移

算数移位, 用符号位补充
sar;右移

CBW ;将 AL 中的符号扩展到 AH 里

;意义 无符号比较 有符号比较
> JA, JNBE JG, JNLE
>= JAE, JNB JNC, JGE, JNL
< JB, JNAE JC, JL, JN, JNGE
< JBE, JNA JLE, JNG

CMP BYTE PTR[SI],"#" ;ptr前面的类型有byte(字节)、word(字)、dword(双字)、qword(四字)、tbyte(十字节)、far(远类型)和near(近类型)

LEA
lea指令
load effective address, 加载有效地址,可以将有效地址传送到指定的的寄存器。指令形式是从存储器读数据到寄存器, 效果是将存储器的有效地址写入到目的操作数, 简单说, 就是C语言中的”&”.
mov指令
在CPU内或CPU和存储器之间传送字或字节,它传送的信息可以从寄存器到寄存器,立即数到寄存器,立即数到存储单元,从存储单元到寄存器,从寄存器到存储单元,从寄存器或存储单元到除CS外的段寄存器(注意立即数不能直接送段寄存器),从段寄存器到寄存器或存储单元。
但是注意
(1) MOV指令中的源操作数绝对不能是立即数和代码段CS寄存器;
(2) MOV指令中绝对不允许在两个存储单元之间直接传送数据;
(3) MOV指令中绝对不允许在两个段寄存器之间直接传送数据;
(4) MOV指令不会影响标志位
使用[]区别
第二操作数加不加中括号[]的区别就是:
lea对变量没有影响是取地址,对寄存器来说加[]时取值,第二操作数不加[]非法
mov对变量来说没有影响是取值,对寄存器来说是加[]时取地址,第二操作数不加[]是取值


判断正负
mov ax, your_number ; 将要判断的数存入寄存器ax中
test ax, 8000h ; 测试ax的最高位是否为1
jnz negative ; 如果最高位为1,跳转到negative标签

; 如果最高位不是1,则继续执行下面的代码
positive:
; 这里是正数的处理代码
jmp end

negative:
; 这里是负数的处理代码
jmp end

end:
; 程序结束