8086机器语言解码:从字节流倒推指令编码与ModR/M寻址

发布时间:2026/10/27 19:06:20
8086机器语言解码:从字节流倒推指令编码与ModR/M寻址 简介这份笔记围绕8086机器语言解码展开面向需要编写8086汇编器、理解x86早期指令编码规则的学习者与开发者重点解决操作码与寻址模式如何对应机器码、双操作数指令如何组织等问题。内容从指令格式、八个16位通用寄存器、五种寻址模式、操作码与立即数讲起延伸到字节/字/双字、固定编码指令及MOV等双操作数指令的编码方法并给出用NASM与db字节对照验证的示例涉及段超越前缀、总线锁定前缀、最长8字节指令和寄存器编号等细节。笔记中还提示不要使用nasm-0.98版本因其不能识别分组二进制常量并展示MOV在寄存器、内存、段寄存器与立即数之间传送时的多种编码形态便于逐字节比对。压缩包仅含1个docx文档约1.94MB便于集中查阅与整理。已有127人学习适合作为汇编器实现、机器码核对和指令解码练习的参考笔记。1. 8086 机器语言解码一张从字节流倒推助记符的地图8086 机器语言解码说到底就是把一串十六进制数字按位切开再拿几张表把切出来的编号翻译回寄存器名。它没什么玄学规则全部固定在位域里一条指令最长 8 个字节从左往右依次是前缀、操作码、ModR/M、位移量、立即数每一段占几位、什么时候出现、出现多长都由前一段的位决定。把这条链走通C7 80 CD 1B 34 12就不再是天书而是mov word [bxsi0x1BCD],0x1234。这份笔记面向三类人正在手写汇编器或反汇编器、需要把助记符和字节互相翻译的人备考体系结构、要手算指令编码的人以及读 8086 汇编时想搞清“这条指令凭什么这么长”的人。后面按骨架、表格、指令族、易错点、验证流程一路推下去。2. 指令骨架前缀、操作码、ModR/M 与两种“尾巴”2.1 八字节上限F0 3E 81 80 CD AB 34 12 逐段拆开把最长的那条指令摊开看骨架就一目了然F0 3E 81 80 CD AB 34 12。F0 是总线锁定前缀3E 是 DS 段超越前缀81 是 ADD 的操作码后面跟 16 位立即数那种80 是 ModR/M 字节CD AB 是 16 位位移常量34 12 是 16 位立即数。翻回助记符是lock add word [ds:bxsi-0x5433],0x1234——位移 0xABCD 在这里被当成 -0x5433 使用编码层面它只是一个 16 位补码常量正负是人的解读不是机器的。按出现顺序列清楚这五段段长度本例取值作用前缀02 字节F0、3E锁定总线、改默认段操作码12 字节81指令族 方向位 d 宽度位 wModR/M01 字节80mod / reg / r/m 三段位域位移量0/1/2 字节CD AB有效地址里的常量部分立即数0/1/2 字节34 12参与运算的常数长度上限就是这几个数相加2 1 1 2 2 8。工程上按“1 个 LOCK 1 个段超越”预留前缀空间就够了再多的前缀即使写得出来也不会有额外语义。有了这个预算写解码器时缓冲区开多大、读指针怎么推进都不用临时猜。注意8086 的 w 位只有 1 位只能区分字节和字。双字D32 位是后续架构才引入的宽度概念把 D 塞进 8086 的通用解码表里长度计算会全线崩掉。2.2 ModR/M 字节的三段位域整个解码流程里ModR/M 是最值得单独盯住的一字节。它把 8 个位切成 2 3 3高 2 位是 mod中间 3 位是 reg有时被征用成扩展操作码低 3 位是 r/m。切法极其机械但三个字段各自的含义会随指令族变化这是后面所有易错点的源头。b 0x80 # ModR/M 字节取自 mov word [bxsi0x1BCD],0x1234 mod (b 6) 0b11 # 10 - 内存寻址位移量 2 字节 reg (b 3) 0b111 # 000 - 对 C7 来说是 /0 扩展位不是寄存器编号 rm b 0b111 # 000 - [bxsi] print(mod, reg, rm) # 2 0 0这段代码只做一件事把一字节拆成三个整数。mod决定后面跟几个字节的位移11 表示不走内存直接是寄存器reg在 MOV、ADD 这类双操作数指令里是寄存器编号在 C6/C7、80/81/83、F6/F7、FE/FF、8F 里则退化成“扩展操作码”写作/0、/6这种形式rm和 mod 组合起来才决定是哪个寄存器或哪种内存寻址。三个字段一个都不能少读读漏一个后面所有字节的偏移全错。mod 只有四种取值语义却覆盖了整张寻址表00 表示无位移r/m110 例外见第 5 章01 表示 1 字节位移10 表示 2 字节位移11 表示操作数是寄存器、根本不去内存。reg 字段的三种身份——普通寄存器编号、段寄存器编号、扩展操作码——是解码器分支最多的一个判断点写代码时最好用显式的枚举区分别用布尔开关硬凑。2.3 位移量与立即数的长度由谁拍板新手最容易犯的错是拿常量的“数值大小”去推字节数。规则完全不是这样位移量占几个字节由 mod 决定和数值大小无关立即数占几个字节由 w 位决定同样和数值大小无关。mov word [bxsi0x00],0x1234里的 0x00 依然老老实实占 2 字节位移若 mod10不能因为它是 0 就省掉。唯一的例外是操作码 83 那一组83 /0 ib表示“立即数是 1 字节但参与 16 位运算时按符号扩展”。它存在的意义是压缩常见的小常数也让解码器多了一个必须特判的分支。除此之外长度与宽度的对应关系可以写成一张固定表判断依据长度典型例子mod00且 r/m≠110位移 0 字节8B 00 mov ax,[bxsi]mod01位移 1 字节8B 40 34 mov ax,[bxsi0x34]mod10位移 2 字节8B 80 34 12 mov ax,[bxsi0x1234]w0立即数 1 字节B0 34 mov al,0x34w1立即数 2 字节B8 34 12 mov ax,0x1234操作码 83立即数 1 字节 符号扩展83 C0 12 add ax,0x12还有一个必须刻进条件反射的细节位移和立即数都是小端存放低字节在前。34 12读出来是 0x1234不是 0x3412。写解码器时统一用lo | (hi 8)拼别在两处地方各写一套顺序否则调试时会怀疑人生。3. 寄存器编号与寻址组合解码器里最容易查错的两张表3.1 三张编号表16 位、8 位、段寄存器reg 字段和 r/m 字段在 mod11 时都表示寄存器但同一个 3 位编号在不同宽度下指向完全不同的寄存器。这张对照表必须整张背下来因为它是“字节看着对、助记符却错”的第一大来源。3 位编号16 位通用寄存器8 位通用寄存器段寄存器000AXALES001CXCLCS010DXDLSS011BXBLDS100SPAH非法101BPCH非法110SIDH非法111DIBH非法规律其实很好记16 位表的顺序是 AX、CX、DX、BX然后是 SP、BP、SI、DI8 位表的前四个是 AL、CL、DL、BL后四个不是 SP/BP/SI/DH 的八位版而是 AH、CH、DH、BH 这四个高位半寄存器。段寄存器只用到低 2 位所以mov es,ax是8E C0reg000mov ds,ax是8E D8reg011。reg100 往上的段寄存器编码是非法的严格的解码器应该报错而不是查表越界。把三张表写成数据代码会比一长串 if 干净得多也便于将来扩展到别的架构REG16 (ax, cx, dx, bx, sp, bp, si, di) REG8 (al, cl, dl, bl, ah, ch, dh, bh) SREG (es, cs, ss, ds) def reg_name(idx, w, is_segFalse): if is_seg: if idx 0b011: # 段寄存器只定义了 4 个编号 100 以上属非法编码 raise ValueError(bad segment register) return SREG[idx] return (REG16 if w else REG8)[idx] print(reg_name(0b001, 1), reg_name(0b001, 0), reg_name(0b011, 1, True)) # cx cl ds参数说明只有三个idx是取出来的 3 位编号w是操作码里的宽度位1 表示字0 表示字节is_seg标记当前是否在处理段寄存器类指令。把“非法编码要报错”写进函数里比事后靠肉眼检查字节流可靠得多。3.2 mod 与 r/m 的 24 种组合与位移联动mod 取 00、01、10 三种“非寄存器”值时r/m 的 8 种取值构成 24 种内存寻址形式。这张表是 8086 解码里信息密度最高的地方也是手算时最值得反复对照的一页。r/mmod00mod01mod10000[bxsi][bxsi]disp8[bxsi]disp16001[bxdi][bxdi]disp8[bxdi]disp16010[bpsi][bpsi]disp8[bpsi]disp16011[bpdi][bpdi]disp8[bpdi]disp16100[si][si]disp8[si]disp16101[di][di]disp8[di]disp16110直接地址 disp16[bp]disp8[bp]disp16111[bx][bx]disp8[bx]disp16看清楚 r/m110 那一行mod00 时它不是[bp]而是“直接地址后面跟 2 字节偏移”。这是整张表唯一的破格项也是所有手写解码器的经典翻车点。至于为什么 8086 要这么设计——[bp]这种最常见的栈访问形式用 mod01 加 disp80 就能表达把 mod00/rm110 省出来给直接地址编码空间利用率更高。举两个对照例子加深印象。mov ax,[bxsi0x1234]编码为8B 80 34 12mod10 说明位移 2 字节如果把同一个语义写成短位移形式mov ax,[bxsi0x34]汇编器通常会选 disp8变成8B 40 34整整省掉一个字节。手算时长度对不上八成就是 mod 选错了档。3.3 默认段与段超越前缀DS 不是永远的老大段超越前缀只有四个字节值很容易记混26是 ES2E是 CS36是 SS3E是 DS。前缀的语义是“只修饰紧跟其后的这一条指令”不改变后续指令的默认段所以解码器读到前缀时要先把它存成临时状态等操作码解析完再一并吐出而不是立刻切换全局状态。默认段的规则按寻址形式分跟寄存器出现在哪个位置无关只看有效地址里有没有 bp寻址形式默认段含 bx 的组合、[bx]、[si]、[di]DS含 bp 的组合、[bp]SS直接地址 disp16DS取指、堆栈操作CS / SS所以mov ax,[bpsi]默认走 SS而mov ax,[bxsi]默认走 DS。想强制换段就在操作码前加前缀比如3E加8B 80 34 12就是mov ax,ds:[bxsi0x1234]。反汇编输出里如果省略了段超越前缀的显式标注读者很容易误判这条指令访问的是哪一段——这也是阅读他人反汇编结果时最该盯住的一处。4. 指令族逐类拆解MOV 的九种形态与其余六类编码4.1 双操作数编码d 位与 w 位怎么分工双操作数指令的操作码字节末尾两位是固定套路次低位是 ddirection最低位是 wwidth。d1 表示 reg 字段给出的是目的寄存器源由 mod 与 r/m 组合确定d0 则反过来mod 与 r/m 组合是目的reg 是源。w1 表示字操作w0 表示字节操作。这两个位一确定剩下的工作就是查表。以 MOV 为例操作码是100010 d w展开正好是四个值d0/w0 得88mov r/m8, r8d0/w1 得89mov r/m16, r16d1/w0 得8Amov r8, r/m8d1/w1 得8Bmov r16, r/m16。ADD 是000000 d w同理得到00、01、02、03四个操作码。同一套位模板在几十条指令上复用这就是 8086 编码设计里最值得体会的一处。记住这个模板后8B C1和8A C1的区别不需要背前者 w1 是字后者 w0 是字节ModR/M 字节都是 C1mod11、reg000、r/m001所以一个读作mov ax,cx另一个读作mov al,cl。字节完全一样语义差了一个宽度位。4.2 MOV 的九类形态对照MOV 是 8086 里形态最多的指令把它的九类编码吃透本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询