简介:这是一份个人总结的8086机器语言解码示例笔记,面向正在编写8086汇编器、反汇编器或希望从机器码层面深入理解x86指令编码的开发者。笔记系统梳理了指令格式、16位通用寄存器编号、寻址模式、操作码、立即数、字节/字/双字等基础概念,重点讲解固定编码指令与双操作数指令编码规则,并覆盖d/w位、MOD与R/M组合关系等关键细节;MOV指令部分给出了寄存器互传、内存与寄存器互传、立即数传送、段寄存器传送等多种机器码示例,如mov word [bx+si+0x1BCD],0x1234对应0C7h 80h 0CDh 1Bh 34h 12h,便于对照学习。笔记还整理了固定编码的1字节指令(如AAA),并提示NASM汇编验证时的版本兼容问题,帮助避免踩坑。整份资源打包为一个docx文档,压缩包仅1.94MB;已有127人学习,适合需要快速掌握8086机器码编码规律并落实到汇编器编写中的技术人员查阅参考。
1. 8086 机器语言解码笔记:从一条 mov 反推出六个机器字节
做汇编器、调试器或者逆向 16 位启动代码的人,早晚会卡在同一道坎上:手头只有 Intel 手册里那些 mod/reg/rm 抽象规则,真到了要把mov word [bx+si+0x1BCD],0x1234写成0xC7 0x80 0xCD 0x1B 0x34 0x12这六个字节时,方向位、字节序、位移长度任何一个记错,机器码就全错。这份 8086 机器语言解码笔记的价值在于,它把 MOV、ADD、PUSH、POP、XCHG、IN/OUT 这些常用指令从操作码到 modrm 逐字节拆开,每条都给了等价的db伪指令写法,还能用 NASM 编译后做二进制对照。适合三类人:写汇编器或者反汇编器需要查编码表的人,研究 x86 启动流程想读懂原始字节的人,以及被 16 位寻址模式绕晕的学生。
2. 编码骨架:MOD/REG/R/M 三位与 W/D 位怎么决定一条指令的形态
2.1 一条 8086 机器指令由几块拼成
8086 的机器指令不像 ARM 那样定长,它是可变长的,最短 1 字节(比如AAA就是固定的0x37),最长能到 8 字节。拆开看,一条完整的指令通常由这几块组成:
操作码(opcode)在最前面,告诉 CPU 这条指令要干什么;寻址模式字节(modrm)紧跟其后,描述操作数在哪;再往后是位移量(disp)和立即数(imm),这两块不一定都有,有的话注意长度为 8 位或 16 位。还有一类前缀字节,比如总线锁定前缀F0h、段超越前缀3Eh,它们出现在整条指令的最前面,不参与操作数编码。
笔记里那个最长的例子很有代表性:
F0 3E 81 80 CD AB 34 12 lock add word [ds:bx+si-0x5433],0x1234这里F0是 lock 前缀,3E是 DS 段超越前缀,81是 ADD r/m16,imm16 的操作码,80是 modrm 字节,CD AB是 16 位位移量(小端序,实际值是 0xABCD,按有符号数解读就是 -0x5433),34 12是 16 位立即数(小端序,实际值 0x1234)。数一下正好 8 字节,一条指令把前缀、操作码、寻址模式、偏移常量、立即数全占了。
理解这个骨架是解码的前提。所谓解码,就是拿到一串字节后,先认前缀,再认操作码,然后看 modrm 决定操作数,最后按长度切出位移和立即数。反过来编码就是把这个过程倒着做一遍。
2.2 MOD 字段与 R/M 字段:操作数的地址怎么算
modrm 字节是整个 8086 编码体系里最绕的部分,它只有 8 位,却要同时表达三样信息:高位 2 位是 MOD(寻址模式),中间 3 位是 REG/分组号,低位 3 位是 R/M(寄存器或内存操作数)。
MOD 两位的含义如下:
| MOD | 含义 | 位移量 |
|---|---|---|
| 00 | 无位移或直接地址 | 0 字节 |
| 01 | 8 位位移量,需符号扩展 | 1 字节 |
| 10 | 16 位位移量 | 2 字节 |
| 11 | 操作数是寄存器而非内存 | 无 |
当 MOD 不是 11 时,R/M 三位决定基址与变址的组合方式,这是 8086 特有的「基址 + 变址 + 位移」寻址:
| R/M | 有效地址(MOD=00/01/10) |
|---|---|
| 000 | [bx+si]+disp |
| 001 | [bx+di]+disp |
| 010 | [bp+si]+disp |
| 011 | [bp+di]+disp |
| 100 | [si]+disp |
| 101 | [di]+disp |
| 110 | [bp]+disp(MOD=00 时变为直接地址 disp16) |
| 111 | [bx]+disp |
注意最后一行 R/M=110 的例外:当 MOD=00 时它不表示[bp],而是表示直接地址(也叫做绝对地址),位移量固定 16 位。这是 8086 编码里最容易被忽略的一个旁路,写汇编器时 mod=00、r/m=110 组合要单独处理成内存直接寻址。
当 MOD=11 时,R/M 不再表示内存,而是直接对应寄存器编号。16 位通用寄存器编号是 AX=000、CX=001、DX=010、BX=011、SP=100、BP=101、SI=110、DI=111,8 位寄存器编号是 AL=000、CL=001、DL=010、BL=011、AH=100、CH=101、DH=110、BH=111。
2.3 W 位、D 位与方向:三个 bit 决定指令形态
除了 modrm,操作码自身还藏着两个关键位。W 位决定操作宽度,W=1 是字操作(16 位),W=0 是字节操作(8 位)。D 位决定方向:D=1 时 REG 字段表示目的操作数,内存或 R/M 表示源;D=0 时反过来,REG 是源,R/M 是目的。
这两个位不是独立出现的,它们以 bit 形式嵌在操作码里。拿 MOV 举例,100010dw是通用寄存器与内存/寄存器互传的基本模板:
8Bh(10001011b)= D=1、W=1,REG 是目的89h(10001001b)= D=0、W=1,REG 是源8Ah(10001010b)= D=1、W=0,字节操作88h(10001000b)= D=0、W=0
笔记里给了这么一对写法:
db 8bh,11_000_001b ; mov ax,cx db 8ah,11_000_001b ; mov al,cl第一个:8B的 D/W 位是 11,REG=001(CX),R/M=000(AX),D=1 所以结果是mov ax,cx。第二个:8A的 D/W 位是 10,REG=001(CL),R/M=000(AL),结果mov al,cl。同一份 modrm,操作码差一个 bit,操作数宽度和方向全变了。
我一般建议初学者先背规则而不是背编码:看操作码最后两位,倒数第二位是 D,最后一位是 W。D=1 时先查 REG 字段是哪个寄存器,再按 MOD/R/M 找另一个操作数,方向是从 REG 到 M/R;D=0 时反着读。这个习惯养成后,后面解 ADD、SUB、CMP 这些同族指令会快很多。
2.4 双操作数指令的编码约束与手工解码实例
双操作数指令有几个硬性约束,笔记里罗列得很清楚:目的和源不能同时是内存单元;当位移量是字时占 2 字节,立即数是 16 位时同样占 2 字节;D=1 时 REG 是目的,源由 MOD 与 R/M 组合决定;W=1 表示字操作否则是字节操作。
拿mov ax,cx反推,机器码就是:8B加0xC1。0xC1展开成二进制是11_000_001b,MOD=11 表示操作数是寄存器,R/M=001 表示 CX,REG=000 表示 AX,D=1 表示 AX 是目的。所以完整写法是db 8bh,11_000_001b。用 NASM 验证的话,[cpu 8086]+[BITS 16]环境下这条指令汇编出来恰好两个字节,和 db 伪指令完全一致。
提示:MOD=11 时 REG 与 R/M 都是寄存器编号,与 8 位/16 位寄存器表一一对应。解码时看到 MOD=11 就不用再关心内存地址怎么算了,直接查寄存器编号表即可。
3. MOV 解码实战:九类编码、长短格式与 NASM 对照验证
3.1 MOV 九类编码拆解
MOV 是学习 8086 编码最好的入口,因为它覆盖面最广,笔记里按操作数组合把它分成了九类。我整理成一张速查表,左侧是 Intel 语法,右侧是等价 db 写法:
| 操作 | 等价机器码 | 说明 |
|---|---|---|
mov ax,cx | 8B C1 | 16 位寄存器互传,通用模板 |
mov al,cl | 8A C1 | 8 位寄存器互传 |
mov ax,[bx+si+0x1234] | 8B 80 34 12 | 内存到 16 位寄存器 |
mov [bx+si+0x1234],ax | 89 80 34 12 | 16 位寄存器到内存,注意 D=0 |
mov word ax,0x1234 | C7 C0 34 12 | 立即数到 16 位寄存器(长格式) |
mov word [bx+si+0x1BCD],0x1234 | C7 80 CD 1B 34 12 | 立即数到内存 |
mov bx,0x1234 | BB 34 12 | 立即数到寄存器(短格式) |
mov bl,0x34 | B3 34 | 立即数到 8 位寄存器(短格式) |
mov ax,[1234h] | A1 34 12 | 直接地址到 AX |
mov [1234h],al | A2 34 12 | AL 到直接地址 |
mov es,ax | 8E C0 | 通用寄存器到段寄存器 |
mov ax,es | 8C C0 | 段寄存器到通用寄存器 |
这套表看起来很散,但背后规律清晰:寄存器互传走8B/89模板;立即数与内存/寄存器用C6/C7组操作码;立即数到累加器之外某特定寄存器时,用B0~BF的短格式;直接地址与 AX/AL 之间用A0~A3专用操作码;段寄存器单独走8E/8C。
有个细节值得单独提:mov word ax,0x1234在笔记里的写法是db 0c7h,11_000_000b,0x34,0x12,也就是C7 C0 34 12。这里的C0是 modrm,MOD=11、R/M=000 表示 AX;REG 字段虽然是 000,但对于C7这个操作码来说它不是寄存器编号,而是功能分组号 /0,表示 MOV。理解这一点很关键:同样是 modrm=0xC0,跟着8B时 REG 表示 CX,跟着C7时 REG 只是分组号。所以解码时操作码决定 modrm 的解读方式,不能死记。
3.2 跟着做一遍:用 NASM 把指令编译成原始字节
笔记最实用的地方在于每一条编码都能用 NASM 验证,而不是只给结论。我按笔记的步骤完整走一遍:
[root@ORA9 cs7]# nasm -v NASM version 2.10.04 compiled on Jun 7 2021注意这里版本是 2.10.04,笔记特别标注了不要用 nasm-0.98。旧版不识别11_000_000b这种带下划线分组的二进制常量,直接用会把汇编器搞崩或者编出错误字节。建议环境里装 NASM 2.x 以上。
接着创建测试文件 a.asm:
[cpu 8086] [BITS 16] mov word [bx+si+0x1BCD],0x1234 db 0c7h,80h,0xcd,0x1b,0x34,0x12 mov al,0x34 db 0B0h,0x34这里的db是 NASM 的 define bytes 伪指令,直接把后面的十六进制数原样写进输出文件。[cpu 8086]告诉 NASM 只允许 8086 指令集,[BITS 16]指定 16 位模式。
然后用-f bin生成纯二进制文件,再用xxd看每个字节:
nasm -f bin a.asm -o a.bin xxd a.bin-f bin参数很关键,它让 NASM 输出不含 ELF 头、不含段表的裸字节,这样才能和 db 伪指令逐字节对比。预期输出里你会看到c7 80 cd 1b 34 12出现两次——一次来自 mov 指令本身,一次来自 db 伪指令,两者完全相同才算验证通过。
如果不用-f bin而用默认的 ELF 格式,xxd看到的是一大堆文件头,机器码混在里面根本对不上,这是新手最常见的验证失败原因。
3.3 从 db 反推指令:把笔记当解码手册用
笔记的另一个用法是反查。比如你手头有一段未知机器码0xC6 0xC0 0x34,不知道对应什么指令,可以翻笔记这一条:
db 0c6h,11_000_000b,0x34 ; mov al,0x34C6是 MOV r/m8,imm8 的操作码,C0的 MOD=11 表示操作数是寄存器,R/M=000 对应 AL,立即数34就是值本身。所以结论是mov al,0x34。注意C6与C7只差最后一位 W,C6表示 8 位立即数目标,C7表示 16 位。
这套反推方法对读 BIOS 代码尤其有用,因为很多老固件直接就是裸二进制,没有符号表可用。我自己的习惯是:把一个字节段复制出来,先数前缀,再按操作码区间查表,最后用ndisasm验证一遍,三分钟能解五六条指令。
3.4 长短格式怎么选:不只是省一个字节的问题
MOV 立即数到寄存器有两套编码,B0~BF短格式只需 2~3 字节,C6/C7长格式需要 3~6 字节。为什么 Intel 要设计两套?因为 8086 时代内存带宽和取指时间都很珍贵,短格式能让最频繁的常量装载指令少占一半空间。比如mov bx,0x1234用短格式BB 34 12只有 3 字节,用长格式C7 C3 34 12要 4 字节,多了一个 modrm 字节。
但是短格式只对特定寄存器生效,B8~BF的高 3 位编码死的寄存器编号从 AX 到 DI。如果目的是内存单元,就只能走C6/C7。写汇编器时通常会做个优化判断:目的操作数是寄存器且立即数可用短格式时优先用短格式,否则回退长格式。
注意:
mov al,0x34的短格式是B0 34,但笔记里出现了db 0c6h,11_000_000b,0x34这种等价长格式写法,两者汇编结果语义相同、字节不同。验证时如果预期字节对不上,先检查是不是长短格式混用了。
4. 算术与堆栈指令解码:ADD/PUSH/POP 的 d 位翻转规律
4.1 ADD 同族指令:一个 opcode 模板带出四个变体
ADD 的编码模板是000000dw,比 MOV 的模板简单,因为 ADD 固定不涉及段寄存器操作数。笔记里的几个例子把 d 位的作用展示得很完整:
db 000000_1_1b,10_000_000b,34h,12h ; add ax,[bx+si+0x1234] db 000000_1_0b,10_000_000b,34h,12h ; add al,[bx+si+0x1234] db 000000_1_1b,11_000_001b ; add ax,cx db 000000_0_1b,10_000_000b,34h,12h ; add [bx+si+0x1234],ax第一行000000_1_1b即03h,D=1、W=1,REG 是目的。modrm=80h,展开是10_000_000b:MOD=10 表示带 16 位位移,REG=000 表示 AX,R/M=000 表示[bx+si],所以是add ax,[bx+si+0x1234],位移量 0x1234 按小端序放在后面。
第四行000000_0_1b即01h,D=0,方向反转,REG=AX 变成源,内存单元变成目的,所以是add [bx+si+0x1234],ax。
这里有个很实用的观察:03h与01h、02h与00h之间只是 D 位不同,方向完全相反。解码时看到操作码是01、03、29、2B这类成对出现的数字,直接套 D 位规则,不用再翻手册。
4.2 PUSH/POP:短格式与长格式并存的操作数族
PUSH/POP 有个特殊规则:操作数总是 16 位,不存在字节压栈,所以编码里根本没有 W 位的位置。它同时提供寄存器短格式和通用长格式两条路。
短格式编码是01010_xxx,xxx 是 16 位寄存器编号:
| 汇编指令 | 机器码 | 说明 |
|---|---|---|
push ax | 50 | 01010_000 |
push di | 57 | 01010_111 |
pop ax | 58 | 01011_000 |
pop di | 5F | 01011_111 |
长格式则用FF /6(PUSH)和8F /0(POP),后面跟一个 modrm 字节描述内存或寄存器操作数:
db 0ffh,11_110_000b ; push ax db 8fh,11_000_000b ; pop ax db 8fh,10_000_000b,34h,12h ; pop word [bx+si+1234h]FF是单字节操作码,但 REG 字段的 110 被手册定义为 PUSH 的功能号;8F的 REG=000 定义为 POP。这套「操作码 + modrm 的 REG 字段分组号」的机制在 8086 里非常普遍,C6/C7、F6/F7、80/81 都用同一套路。看到FF开头不要直接猜是 INC 或 DEC,先看 modrm 的 REG 三位再定指令。
段寄存器的 PUSH/POP 编码也值得记一下,push cs是0E,pop es是07,pop ss是17,pop ds是1F。这些是固定编码,不经过 modrm。
4.3 XCHG 与 IN/OUT:固定编码与隐含操作数
XCHG 有两套编码。与 AX 交换的短格式是90+reg,90本身是xchg ax,ax,91是xchg ax,cx,92是xchg ax,dx,依此类推。用笔记中的写法:
db 10010_010b ; xchg ax,dx 即 0x92 db 10010_001b ; xchg ax,cx 即 0x91而通用寄存器与内存/寄存器交换用1000011w加 modrm,比如86h/87h:
db 1000011_1b,10_001_000b,34h,12h ; xchg cx,[bx+si+1234h] db 1000011_0b,10_001_000b,34h,12h ; xchg cl,[bx+si+1234h]IN/OUT 属于典型的固定编码加隐含操作数。IN 从端口读数到 AX/AL,端口号可以立即数给出,也可以放在 DX 寄存器里:
| 汇编指令 | 机器码 | 说明 |
|---|---|---|
in al,20h | E4 20 | 固定端口到 AL |
in ax,20h | E5 20 | 固定端口到 AX |
in al,dx | EC | DX 端口到 AL |
in ax,dx | ED | DX 端口到 AX |
out 20h,al | E6 20 | AL 到固定端口 |
out 20h,ax | E7 20 | AX 到固定端口 |
out dx,al | EE | AL 到 DX 端口 |
out dx,ax | EF | AX 到 DX 端口 |
这类指令特征很明显:操作码低 3 位不同组合对应 AX/AL 与 IN/OUT 方向,且E4/E5/E6/E7带一个立即数字节,EC/ED/EE/EF不带。解码时先看第二个字节是否存在,就能区分是立即数端口还是 DX 端口。
4.4 遇到未见过指令时怎么定位
笔记里还整理了一批 1 字节固定编码指令,解码效率极高:37是 AAA,D7是 XLAT(把[ds:bx+al]的内容送到 AL),9F是 LAHF(FLAG 低字节送 AH),9E是 SAHF,9C是 PUSHF,9D是 POPF。遇到单字节且不在寄存器编码范围内的操作码,优先查这张表。
LEA/LDS/LES 可以作为理解「modrm 语义随操作码变化」的补充例。8D是 LEA,只取内存有效地址送入寄存器;C5是 LDS,从内存连续取两个字,分别送通用寄存器和 DS;C4是 LES,目标段寄存器换成 ES:
db 10001101b,10_001_000b,34h,12h ; lea cx,[bx+si+0x1234] db 11000101b,10_001_000b,34h,12h ; lds cx,[bx+si+0x1234] db 11000100b,10_001_000b,34h,12h ; les cx,[bx+si+0x1234]三个操作码不同,后面的 modrm 一模一样,REG=001 表示 CX,R/M=000 表示[bx+si],位移 0x1234。区别只在 LEA 不访问内存,LDS/LES 要读两次内存分别装载段寄存器和通用寄存器。
5. 解码避坑指南:字节序、方向位与 NASM 版本的四个翻车点
5.1 现象:NASM 编译 db 伪指令报错,且带下划线的二进制常量解析异常
我照着笔记第一次跑的时候,用了一个老的 NASM 0.98,编译db 0c7h,11_000_000b,0x34直接报错,换成11000000b不带下划线又能过。原因:NASM 0.98 不支持二进制常量内部的下划线分组语法,11_000_000b被当成非法 token,而 NASM 2.x 才加入这个特性。
解决:升级到 2.x 版本,或者统一把下划线去掉写成11000000b。笔记里特别标注了版本要求,实测 NASM 2.10.04 编译这些编码没有任何问题。如果项目要跨机器跑,建议在 Makefile 里加版本检查,避免别人用旧版 NASM 编出错误字节。
5.2 现象:mov word [bx+si+0x1BCD],0x1234 手工排字节时,位移量或立即数高低字节颠倒
我第一次手工编码这条指令,按直觉把位移写成了1B CD,立即数写成12 34,结果反汇编出来的地址和值完全不对:0xCD1B和0x3412,整整错了一个数量级。原因:8086 是小端序,16 位位移量和立即数都是低字节在前、高字节在后,0x1BCD 的正确排布是CD 1B,0x1234 是34 12。
解决:写编码器时先做一个统一的小端序处理函数,所有 disp16 和 imm16 都经过它输出,不要人在脑子里倒序。验证时用xxd逐字节比对,机器码里CD 1B出现在 modrm 之后、立即数之前,顺序是 opcode、modrm、disp 低、disp 高、imm 低、imm 高。
5.3 现象:两个寄存器互传的 MOV 机器码方向搞反
mov cx,ax和mov ax,cx的 modrm 都是0xC1附近,方向由操作码 D 位决定。我最初按「REG 是源」来解读 8B,结果把8B C1解成了mov cx,ax,实际它应该是mov ax,cx。原因:没记住 D=1 表示 REG 是目的而不是源。
解决:把 D 位的语义写死在注释里,D=1 时 REG 是目的、M/R 是源,D=0 时反过来。记忆锚点:8B 的二进制是1000 1011,末尾11中第二个 1 是 D,D=1 时目的 REG 在 modrm 里,这条可以作为默认假设先查,确认不对再看 D=0 的情况。
5.4 现象:add esp,0x1234这类 32 位寄存器指令在 8086 模式下汇编失败或生成异常字节
如果往[cpu 8086]文件里混入 32 位寄存器操作数,NASM 会直接报错,因为 8086 没有 esp/eax 这些扩展寄存器。原因:[cpu 8086]限制了指令集范围,32 位指令属于后续 x86 扩展。
解决:研究 8086 编码时强制坚持[cpu 8086]+[BITS 16]成对出现,需要验证 32 位指令时另建一个编译环境,不要混在一个文件里。另一个坑是 BITS 设置:如果忘了写[BITS 16],NASM 在某些输出格式下默认按 32 位模式编码,同样的mov ax,cx会被编成带 66 前缀的变体,字节数完全不同。
5.5 现象:用 objdump 反汇编裸 bin 时结果乱套,指令识别全错
我拿objdump -D -b binary去反汇编一个 16 位裸二进制,输出的指令全是乱码,比如把push ax解成inc bp。原因:objdump 默认按当前架构位宽反汇编,二进制文件没有 ELF 头,它不知道这是 16 位代码,按 32 位模式把两条 1 字节指令拼成了一条畸形指令。
解决:用 NASM 自带的ndisasm指定-b 16反汇编,它不依赖文件头,纯按字节流解析。例如ndisasm -b 16 a.bin,输出的每一行都会标出偏移和机器码,比 objdump 适合验证 8086 裸字节。如果非用 objdump 不可,需要加-m i8086参数指定架构。
6. 一条八字节指令的手工解码:F03E8180CDAB3412 的完整验证
6.1 用 xxd -r 把十六进制回灌成二进制
拿到一段十六进制机器码,最直接的验证方式是把回灌成二进制,再反汇编看结果。命令如下:
echo "F03E8180CDAB3412" | xxd -r -p > raw.bin ndisasm -b 16 raw.binxxd -r -p把纯十六进制文本按对转成字节,-b 16让 ndisasm 按 16 位模式解析。输出应该是一条指令:F03E8180CDAB3412 lock add word [ds:bx+si-0x5433],0x1234。如果 ndisasm 输出两条以上指令,说明你给的字节序列有问题,重新检查前缀个数和立即数长度。
6.2 逐字节拆解
F0 是 LOCK 前缀,告知 CPU 这条指令执行期间锁定总线;3E 是 DS 段超越前缀,显式指定段寄存器为 DS,去掉 3E 编码相同,但加上可以让反汇编输出带ds:前缀。81 是 ADD r/m16,imm16 的操作码,W 位隐含在 81 这个分组中,按字操作处理。80 是 modrm 字节,MOD=10 表示地址含 16 位位移,REG=000 表示功能分组 /0(ADD),R/M=000 表示基址加变址[bx+si]。CD AB 是位移量,小端序按 AB CD 读回 0xABCD,作为有符号数是负数:0x10000 - 0xABCD = 0x5433,所以写成 -0x5433。34 12 是立即数 0x1234。合起来就是完整的lock add word [ds:bx+si-0x5433],0x1234。
6.3 核对负位移量的方法
位移量是负数时最容易核对出错,因为小端序的原始字节和数学上的负数形式对不上。我的习惯是先用无符号数读出0xABCD,再判断它是否大于 0x8000,大于则按补码取负数0xABCD - 0x10000 = -0x5433。另一个交叉验证技巧是手工改一个字节:把 CD 改成 00,AB 保持,重新反汇编看地址是否变回+0xAB00,这种方法能快速定位位移量切错了位置。
从那以后,我每次手工排完一段 8086 机器码,都会强制走一遍xxd -r -p回灌加ndisasm -b 16反汇编的对照流程,确认反汇编结果与原始汇编指令完全一致才继续往下走。解码这种事,一条指令错了后面全跟着错,早发现早省事。希望帮到你。
本文还有配套的精品资源,点击获取