Cheatsheet
编程 tips#
add dl, '0'转字符格式add dl, 20h大写转小写sub dl, 20h小写转大写- 32 位整数转 16 进制输出
- 每次
rol -> push -> and 0Fh -> ... -> pop实现从高到低,每次取四位 - 逻辑判断
cmp al, 10,选择输出一个数字或者字母
- 每次
- 二进制变量转十进制输出
- 每次进行
64 / 32 -> 32除法运算防止溢出 - 使用栈进行反向输出,让个位最后输出
- 需要统计 push 的次数
- 每次进行
- 删除字符串前面的字符
- 使用
repe scasb来跳过等于al的字符
- 使用
strcmprepe cmpsb- 如果最后一次相等,则全等,且过完了整个字符串
- 否则,不全等,停在了第一个不相等字符串的后面
- 内存清零
rep stosb来进行批量置零
- 过滤字符串字符
- 首先设置好
cld,确认di, si地址正确,然后每一个字符都lodsb ... stosb来实现
- 首先设置好
指令表#
- 数据传送指令
- 通用数据传送指令
mov,push,pop,xchgmov dest, src- 不能给
cs赋值 - 不影响标志位
- 两个操作数不能都是内存变量
- 等宽修饰!!
- 不能将常数赋值给段寄存器
- 不能引用
ip, fl寄存器
- 不能给
push op将src压入堆栈并更新sp- 不影响标志位
- 不支持 8 位操作数
pop op弹出给op,注意事项和push一样xchg src1, src2交换两个操作数的值- 不影响标志位
- 不能有段寄存器
- 输入输出指令
in, out用于端口操作in al, port从端口读取到alport< 0FFh 的时候才能用常数,否则要用 dx 寄存器,in al, dx
out port, al将al写入到端口,和in一样注意事项
- 地址传送指令
lea, lds, leslea dest, srcload effective address 加载偏移地址lds dest, src将src偏移地址放到dest,段地址放在dsles dest, src将src偏移地址放到dest,段地址放在es
- 标志寄存器传送指令
lahf, sahf, pushf, popflahf将fl低 8 位赋值给ah(load AH with Flags)sahf将ah赋值给fl低 8 位 (Store AH in Flags)pushf(d)加 d 是eflpopf(d)加 d 是efl
- 通用数据传送指令
- 转换指令
- 扩充指令
cbwal->axcwdax->dx:axcdqeax->edx:eaxmovsx dest, src将src符号扩充放到destmovzx dest, src将src零扩充放到destxlat把byte ptr ds:[bx+al]赋值给al- 先构造
ds:bx->array - 然后在
al中写入字节下标,使用xlat取这个字节
- 先构造
- 扩充指令
- 算数运算指令
- 加法指令
add dest, srcinc op不影响 CFadc dest, srcdest = dest + src + CF
- 减法指令
sub dest, srcsbb dest, srcdest = dest - src - CF- 计算借位减法也应该从低位算到高位
dec op不影响 CFneg opcmp op1, op2temp = op1 - op2- 影响标志位,但是并不保存结果
- 后面常伴随条件跳转指令
- 乘法指令
mul src无符号数乘法src8 位,ax = al * srcsrc16 位,dx:ax = ax * srcsrc32 位,edx:eax = eax * src
imul有符号数乘法imul src和mul src用法相同imul src1, src2src1 = src1 * src2imul src1, src2, immsrc1 = src1 * src2 * imm
- 除法指令
div op无符号数除法- 8-bit,
ax / op = al...ah - 16-bit,
dx:ax / op = ax...dx - 32-bit,
edx:eax / op = eax...edx - 溢出
- 除数为 0,或者商过大导致商寄存器无法容纳
- CPU 会在除法前面插入
int 00h打印 overflow 并停止程序 - 可以调整
dx:[0]的int 00h中断矢量来改变行为
- 8-bit,
idiv op有符号数除法,和div一样
- 浮点运算指令
- fp 寄存器都是 80-bit,一共有
st(0), ..., st(7)八个 fld op(float load) 将一个浮点数压入堆栈fild op(float int load) 将一个整数转换为浮点数压入堆栈fst dest(float store) 将st(0)保存到一个浮点类型变量或小数寄存器fstp dest(float store and pop) 一样的操作,只是多了一个 pop 的步骤fstp st一般用来当 pop 用
- fp 寄存器都是 80-bit,一共有
- 加法指令
- 十进制调整指令
- 压缩 BCD 码调整指令
daa(decimal adjust after addition) 将al在加法之后重新调整为 BCD 码das(decimal adjust after substraction) 将al在剑法之后重新调整为 BCD 码
-
非压缩 BCD 码调整指令
aaa(ASCII adjust after addition) 非压缩 BCD 码加法调整aas(ASCII adjust after substraction) 减法调整aam(ASCII adjust after mul) 乘法调整aad(ASCII adjust after div)
- 压缩 BCD 码调整指令
- 逻辑运算和移位指令
- 逻辑运算指令
and dest, srcor dest, srcxor dest, srcnot optest dest, src进行 and 运算但是不保留结果
- 移位指令 最后移出的一位永远放到 CF
shl dest, count逻辑左移- 没有
.386的话,只能是shl reg, 1
- 没有
shr dest, count逻辑右移sal dest, count算数左移,和 SHL 完全一样srl dest, count算数右移,左边补符号位rol dest, count循环左移,最后移出又移入的数还是放在 CFror dest, count循环右移rcl dest, count带进位循环左移,右边补 CF,左边移到 CF,相当于 CF 作为多余的一位参与循环移位了rcr dest, count带进位循环右移
- 逻辑运算指令
- 字符串操作指令
- 字符串复制指令
rep movsb/w/d(mov string in byte)movsb先复制再移动 SI DIbyte ptr es:[di] = byte ptr ds:[si]分别是 data segment:source index 和 extra segment:destination indexif (!DF) { di++; si++; } else { di--; si--; }w d 分别为 2 4
rep- 先判断 CX 是不是 0,如果是就结束
- 进行一次操作,然后 CX--
rep movsb将ds:[si]指向的字符串复制到es:[di],CX 指定复制字符数量,DF 制定复制方向
- 字符串比较指令
repe/ne cmpsb/w/dcmpsb先比较再移动 SI DIcmp byte ptr ds:[si], byte ptr es:[di],状态保存在 FLif (!DF) { di++; si++; } else { di--; si--; }
repe/nerepeat if equal/not equal- 先判断 CX 是不是 0,如果是就结束
- 进行指令操作
- CX-- 不影响标志位
- 判断 ZF 是否为 0,再决定是终止还是继续
strcmp: repe cmpsb- 初始化 CX 为字符串长度
repe cmpsb- 如果最后一次是 equal,那么遇到
\0退出,字符串全等 - 否则,找到不相等的字符,由于
cmpsb比较之后一定会移动 DI SI,所以dec si, dec di才能找到第一个不同的字符位置
- 搜索字符串指令
repe/ne scasb/w/dscasb只和es:[di]有关了,其实就是 cmpsb 里的源字符串变成了 al/ax/eaxcmp al, byte ptr es:[di]if (!DF) di++; else di--;
strlen: repne scasb可以用来求字符串长度al = 0- 初始化 CX=0FFFFh,表示最大搜索长度
- 搜到 0 的时候,根据 repne 的流程,还会进行 CX--
- 使用
not cx得到搜索过的字符数量,还需要dec cx得到没有\0的字符数量
- 写入字符串指令
rep stosb/w/dstosb将 al/ax/eax 写入es:[di]byte ptr es:[di] = alif (!DF) di++; else di--;
- 可以用于数组清零
cx=len; al=0; rep stosb- 同时使用
stosb/dcx=len; push cx; shr cx, 2rep stosdpop cx; and cx, 3rep stosb
- 同时使用
- 读取字符串指令
lodsb/w/d- 从
ds:[si]读取一个字符到 al/ax/eax,并按照 DF 指示移动 SI - 简化了数组遍历过程,可以用于过滤字符
- 从
- 字符串复制指令
- 控制转移指令
- 无条件跳转指令
jmpjmp short destEBXXjmp near ptr destE9XXXXjmp far ptr destEAXXXXXXXX
- 条件跳转指令
jcc跳转距离全都是 1 字节,也就是 short jumpja = jnbejb = jc = jnaejae = jnc = jnbjbe = jnajg = jnlejl = jngejge = jnljle = jngjz = jejnz = jnejp = jpe有奇偶校验位则跳转,也就是运算结果低 8 位的 1 有偶数个jnp = jpojump is no parityjcxzjump if cx is zerojecxzjump if ecx is zero
- 无条件跳转指令
- 循环指令
loop dest- CX--
- 如果 CX 不为 0,则跳转 dest
- loop 循环:循环体,cx--,判断;rep 系列循环:判断,循环体,cx--
-
这就会导致如果初始 CX=0,实际上会执行 FFFF 次
-
loopz dest == loope dest- CX--
- 如果
ZF==1 && CX!=0,才跳转到 dest
loopnz dest
- 子程序调用与返回指令
call near ptr destE8 XXXX近调用back_addr = ip + 3push back_addrid = back_addr + delta
retn近返回- 流程
pop back_addrip = back_addr
retn idata16用于堆栈平衡,将调用函数时压入的参数弹出,这样主程序就不需要管理堆栈平衡了pop back_addrsp += idata16idata16是函数参数所占的字节数,不算返回地址的ip = back_addr
-
注意
- 标号定义、
name proc...name endp定义和name proc near...name endp定义的函数中,ret 默认就是 retn name proc far...name endp定义的函数中,ret 默认是 retf,所以 retn 不能简写成 ret
- 标号定义、
- 流程
call far ptr dest9A XXXX XXXX远调用- 流程
push cspush ip+5这样在堆栈里的 far ptr 也满足了小端规则ip = dest_lcs = dest_h
- dest 可以是远标号
name label far/ name proc far,也可以是 mem32call dword ptr es:[di]
- 流程
retf idata16远返回,并同时参数堆栈平衡
- 中断和中断返回指令
int idata8CD XXpushfpush cspush ip+2ip = word ptr 0000:[idata8 * 4]cs = word ptr 0000:[idata8 * 4 + 2]
int 3CC软件端点中断pushfpush cspush ip+1ip = word ptr 0000:[000Ch]cs = word ptr 0000:[000Eh]
intoCE溢出中断 interrupt on overflow- 先判断 OF 是否为 1,是才接着执行
pushfpush cspush ip+1ip = word ptr 0000:[0010h]cs = word ptr 0000:[0012h]
iret中断返回pop ippop cspopf
| 短跳转 | 近跳转 | 远跳转 | |
|---|---|---|---|
| 指令格式 | jmp short dest |
jmp near ptr dest |
jmp far ptr dest |
| 机器码 | EB |
E9 |
EA |
| 示例 | EB06 |
E93412 |
EA78563412 |
| 范围 | [80h, 7Fh] |
[8000h, 7FFFh] |
[0000:0000, FFFF:FFFF] |
| 相对地址 | dest-($+2) |
dest-($+3) |
绝对地址 |
| 备注 | 相对偏移 | 相对偏移,小端规则 | 远指针,小端规则 |
| 跳转指令 | 条件 | 解释 | 跳转指令 | 条件 | 解释 | |
|---|---|---|---|---|---|---|
je |
ZF==0 |
jne |
ZF!=0 |
|||
jae (above or eq) |
CF==0 |
没有借位,说明第一个大 | jge |
SF==OF |
正数且没有溢出/负数且有溢出(实际应该是正数) | |
ja (above) |
CF==0 && ZF==0 |
没有借位,而且不相等 | jg (greater) |
SF==OF && ZF!=0 |
满足 jge 且不相等 |
|
jbe |
CF==1 |
有借位,说明第二个大 | jle |
SF!=OF |
负数且没有溢出/正数且有溢出(实际应该是负数) | |
jb |
CF==1 && ZF!=0 |
有借位,而且不相等 | jl |
SF!=OF && ZF!=0 |
满足 jle 且不相等 |
寄存器#
ax, bx, cx, dx, sp, bp, si, dibx, bp, si, di用来表示偏移地址,可以放在[]内ax, bx, cx, dx称为通用寄存器,常用于算数、逻辑运算
cs, ds, es, ss用来表示段地址cs:ip指向当前将要执行的指令,ip是指令指针(instruction pointer),cs是代码段寄存器ss:sp指向堆栈顶端,其中sp是堆栈指针(stack pointer),ss是堆栈段寄存器es附加段寄存器,和ds一样,可以表示一个数据段的地址
ipPC 指针fl标志寄存器
标志寄存器#
ZF零标志SF符号标志OF溢出标志PF奇偶校验标志AF辅助进位标志和 BCD 码的调整有关DF方向标志std; cld;
IF中断标志,IF=0 禁止硬件中断sti; cli
TF陷阱标志,TF=1 进入单步模式,每一条指令后面都会跟着一条int 01h- 只能使用
pushf popf来操作,例如pushf; pop ax; or ax, 100h; push ax; popf - 调试器利用单步模式来获得控制权
- 只能使用
内存空间#
| 地址范围 | 用途 | 大小 |
|---|---|---|
[0000:0000, 9000:0000] |
操作系统和用户程序 | 640K |
[A000:0000, A000:FFFF] |
映射显卡内存 图形模式 | 64K |
[B000:0000, B000:7FFF] |
映射显卡内存 | 32K |
[B800:0000, B800:7FFF] |
映射显卡内存 文本模式 | 32K |
[C000:0000, F000:FFFF] |
映射 ROM | 320K |
常用中断#
int 21h
| AH | 功能 | 调用参数 | 返回参数 |
|---|---|---|---|
| 00 | 程序终止 | CS=程序段前缀 | |
| 01 | 键盘输入并回显 | AL=输入字符 | |
| 02 | 显示输出 | DL=输出字符 | |
| 09 | 显示字符串 | DS:DX=字符串地址 | |
| 4C | 带返回码结束 | AL=返回码 |
int 00h除法 overflow 错误中断int 10hah = 0, al = 13h显卡切换到 320x200 图形模式- 显卡的文本模式直接这样就行
mov ax, 0B800h; mov es, ax; mov al, 'A'; mov ah, 71h; mov es:[di], ax;- 注意这里高位的内存地址更大,应该是颜色,低位才是字符
int 16hah = 0从键盘读取一个键的编码,放到ax