实现8086汇编编译器(二)

您所在的位置:网站首页 翻译指令怎么理解 实现8086汇编编译器(二)

实现8086汇编编译器(二)

2024-07-10 03:53| 来源: 网络整理| 查看: 265

文章目录 前言机器指令通用格式mov 机器指令的格式mov 汇编指令的格式mov 指令的翻译识别操作数类型立即数寄存器操作数内存操作数解析操作数checkMov 的实现encodeMov 的实现关键点

前言

因为 CPU 只能识别和执行机器指令,所以需要将汇编指令翻译【转换,编码】成机器指令。

官方手册《Intel_8086_Family_Users_Manual》里面包含了所有汇编指令对应的机器指令的格式。

这篇文件介绍mov指令的翻译是如何实现的。

机器指令通用格式

8086 机器指令的长度从1字节到6字节不等。大部分指令的格式如下: 在这里插入图片描述 多字节指令的前 6 位通常包含一个操作码,用于标识基本指令类型:比如 ADD、XOR 等。

接下来的位称为 D 字段,一般指定操作的“方向”:1 = REG 字段标识目标操作数,0 = REG 字段标识源操作数。

W 字段区分字节和字操作之间:0 = 字节操作,1 = 字操作。

除了D和W外,其他的指令可能还出现以下几个字段:

在这里插入图片描述

指令的第 2 个字节通常标识指令的操作数。 MOD 字段指示两个操作数之一是否在内存中【也就是其中之一是否是内存操作数。因为 8086 的指令不可能同时有两个内存操作数】或者两个操作数是否都是寄存器:

在这里插入图片描述

REG 字段标识操作数使用哪个寄存器【寄存器操作数】:

在这里插入图片描述

在一些指令中,主要是立即数到内存类型的指令中, REG被用来扩展操作码来识别操作的类型。

R/M(寄存器/内存)字段的编码取决于MOD字段的设置方式。 如果 MOD = 11(寄存器到寄存器模式),那么 R/M 标识第二个寄存器操作数。 如果 MOD 是内存操作模式,那么 R/M表示内存操作数的有效地址是如何计算的:

在这里插入图片描述

指令的第 3 到 6 字节是可选的,通常包含内存操作数的偏移量【displacement 】和/或一个立即数的值。MOD 字段指明了偏移量的长度是 1 字节还是 2 字节【1个字】,第 2 字节是字的最高字节。

偏移量后的立即数也是可选的,第 2 字节是最高字节。

mov 机器指令的格式

mov 机器指令的格式如下:

在这里插入图片描述

可以看到指令共分为七大类:

寄存器/内存 到/从 寄存器立即数到寄存器/内存立即数到寄存器【这种编码格式比上面的更短】内存到累加器累加器到内存寄存器/内存 到 段寄存器段寄存器 到 寄存器/内存 mov 汇编指令的格式

根据 mov 机器指令格式,mov 汇编指令细分以下就有11种,【右边是源操作数,左边是目的操作数】:

寄存器到寄存器【mov ax,bx】内存到寄存器【mov cx,[bp+si+1]】寄存器到内存【mov [bx+si],ax】立即数到寄存器【mov ax,123】立即数到内存【mov [bx],123】内存到累加器【mov ax,[si+1]】累加器到内存【mov [si+1],al】寄存器到段寄存器【mov cs,ax】内存到段寄存器【mov cs,[1]】段寄存器到寄存器【mov cx,cs】段寄存器到内存【mov [di+1],ds】 mov 指令的翻译 识别操作数类型

要识别汇编程序中的 mov 指令是哪种格式,我们首先要识别操作数的类型。

比如 mov [bx],123,源操作数类型是立即数,目的操作数是内存操作数。

mov cx, cs,源操作数类型是段寄存器,目的操作数是【通用】寄存器。

于是我定义了如下的操作数类型:

type OperandType uint8 const ( Immediate8Operand OperandType = iota Immediate16Operand ImmediateLabelOperand ImmediateOffsetLabelOperand Reg8Operand Reg16Operand SegRegOperand Mem8Operand Mem16Operand MemUnknownSizeOperand InvalidOperand ) type Operand struct { Type OperandType // 操作数类型 Value interface{} // 操作数的值 } 立即数

立即数我把它分成4类:

8 位立即数16 位立即数内部标号带 offset 修饰的内部标号

看如下的示例程序:

assume cs:code,ds:data,ss:stack ;将cs,ds,ss分别和code,data,stack段相连 data segment dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h data ends stack segment dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 stack ends code segment start: mov ax,stack mov ss,ax mov sp,20h ; 将设置栈顶ss:sp指向stack:20 mov ax, data ; 将名称为"data"的段的段地址送入ax mov ds,ax ; ds指向data段 mov bx,0 ; ds:bx指向data段中的第一个单元 mov cx,8 s0: push cs:[bx] add bx,2 loop s0 ; 以上将代码段0~15单元总的8个字型数据依次入栈 mov bx,0 mov cx, 8 s1:pop cs:[bx] add bx,2 loop s1 ; 以上依次出栈8个字型数据到代码段0~15单元中 mov ax,4c00h int 21h code ends end start

mov ax, data ,这个 data 就是内部标号。它的值是程序加载时数据段的值,它是个立即数。

mov ax,stack 也是同理。

看如下示例程序:

assume cs:code code segment mov ax,4c00h int 21h start: mov ax,0 s: nop nop mov di,offset s mov si,offset s2 mov ax,cs:[si] mov cs:[di],ax s0: jmp short s s1: mov ax,0 int 21h mov ax,0 s2: jmp short s1 nop code ends end start

mov di,offset s,表示将外部标号 s 的【代码段】偏移量放到 di 寄存器。这里 s 就是带 offset 的标号。

【暂不支持mov di,s 这种格式,其中 s 是数据段定义的一些数据的起始地址。也就是说 s 是个变量】

mov si,offset s2 也是同理。

用如下的结构体表示一个立即数:

type ImmediateOperand struct { Value uint16 // 值 Width uint8 // 立即数宽度,8位或16位 IsLabel bool // 是否是内部标号 IsLabelOffset bool // 是否是内部带offset的标号 Label string // 标号的名字 }

使用 isImmediateOperand 函数判断是否是一个 8 位或 16 位立即数。

// 12345 // 0ffffh // 123h // 1000b // -123h //'a' func isSizedImmediateOperand(s string, bitSize int) (bool, *ImmediateOperand) { s = strings.TrimSpace(s) if len(s) == 0 { return false, nil } if bitSize == 8 { if len(s) == 3 && s[0] == '\'' && s[2] == '\'' { return true, &ImmediateOperand{ Value: uint16(s[1]), Width: 8, } } } isNegative := false if s[0] == '-' { isNegative = true } base := 10 if strings.HasSuffix(s, "h") { base = 16 s = s[:len(s)-1] } else if strings.HasSuffix(s, "b") { base = 2 s = s[:len(s)-1] } if isNegative { v, err := strconv.ParseInt(s, base, bitSize) if err != nil { return false, nil } return true, &ImmediateOperand{ Value: uint16(v), Width: uint8(bitSize), } } v, err := strconv.ParseUint(s, base, bitSize) if err != nil { return false, nil } return true, &ImmediateOperand{ Value: uint16(v), Width: uint8(bitSize), } } func isImmediateOperand(s string) (bool, *ImmediateOperand) { t, v := isSizedImmediateOperand(s, 8) if t { return t, v } t, v = isSizedImmediateOperand(s, 16) if t { return t, v } return false, nil }

是否是内部标号或带 offset 的内部标号的逻辑在下文给出。

寄存器操作数

寄存器分为 8 位寄存器【al,cl,dl,bl,ah,chdh,bh】,16 位寄存器【ax,cx,dx,bx,sp,bp,si,di】,段寄存器【es,cs,ss,ds】。

用如下的结构体表示:

type RegOperand struct { Name string // 寄存器名称 Width uint8 // 寄存器宽度 IsSegReg bool // 是否是段寄存器 }

实现了如下的函数判断寄存器类型:

var reg8BitMap = map[string]uint8{ "al": 0, //Byte Multiply, Byte Divide, Byte 1/0, Translate, Decimal Arithmetic "cl": 1, //Variable Shift and Rotate "dl": 2, "bl": 3, "ah": 4, //Byte Multiply, Byte Divide "ch": 5, "dh": 6, "bh": 7, } var reg16BitMap = map[string]uint8{ "ax": 0, //Word Multiply, Word Divide, Word 1/0 "cx": 1, //String Operations, Loops "dx": 2, //Word Multiply, Word Divide, Indirect 1/0 "bx": 3, //Translate "sp": 4, //Stack Operations "bp": 5, "si": 6, //String Operations "di": 7, //String Operations } var regSegMap = map[string]uint8{ "es": 0, "cs": 1, "ss": 2, "ds": 3, } func isReg8Operand(s string) (bool, *RegOperand) { if _, ok := reg8BitMap[s]; !ok { return false, nil } return true, &RegOperand{Name: s, Width: 8} } func isReg16Operand(s string) (bool, *RegOperand) { if _, ok := reg16BitMap[s]; !ok { return false, nil } return true, &RegOperand{Name: s, Width: 16} } func isSegRegOperand(s string) (bool, *RegOperand) { if _, ok := regSegMap[s]; !ok { return false, nil } return true, &RegOperand{Name: s, Width: 16, IsSegReg: true} }

注意:上面定义的各种 map 对应的值不是随意定的,而是根据机器指令中 REG 字段的值定义的。

内存操作数

内存操作数是最复杂的。它一共有如下16种形式:

在这里插入图片描述

算上偏移量是 8 位还是 16 位,一共有 24 种形式。而且立即数还可以放 [] 外,比如

[bx+di+123] 和 123[bx+di] 和 [bx+di]123 这三种形式是一样的。编译器必须都得支持。

内存操作数用如下的结构体表示:

type MemOperand struct { IsSingleIndex bool // 是否使用一个索引寄存器,比如[bx],[bx+123]等 SingleIndexReg string // 索引寄存器,比如"bx" IsDoubleIndex bool // 是否使用两个索引寄存器,比如[bx+si],[bx+di]123等 DoubleIndexFirstReg string // 第一个索引寄存器,比如"bx",“bp”等 DoubleIndexSecondReg string // 第二个索引寄存器 ,比如"si","di"等 HasDisplacement bool // 是否有偏移量 DisplacementValue uint16 // 偏移量的值 DisplacementWidth uint8 // 偏移量的宽度 OperandWidth uint8 // 操作数的宽度,比如 byte ptr [bx],操作数宽度就是8 HasSegmentPrefix bool // 是否有段前缀 SegmentPrefix string // 段前缀名称 }

于是在某个深夜,我写下了项目代码中最长的字符串处理函数 isSimpleMemOperand,来判断是否是一个不带段前缀和不带 word ptr 或byte ptr修饰的内存操作数。

func isSimpleMemOperand(s string) (bool, *MemOperand) { s = strings.TrimSpace(s) if len(s) == 0 { return false, nil } // 内存操作数必须带有 [ ] idxL := strings.IndexByte(s, '[') idxR := strings.IndexByte(s, ']') if idxL 3 { return false, nil } if len(fields) == 1 { //外面有偏移量 // [SI]d8 // [DI]d8 // [BP]d8 // [BX]d8 // [SI]d16 // [DI]d16 // [BP]d16 // [BX]d16 if Immediate != nil { if fields[0] != "si" && fields[0] != "di" && fields[0] != "bx" && fields[0] != "bp" { return false, nil } return true, &MemOperand{ IsSingleIndex: true, SingleIndexReg: fields[0], HasDisplacement: true, DisplacementValue: Immediate.Value, DisplacementWidth: Immediate.Width, } } //外面没有偏移量 //[SI] //[DI] //[d16] //[BX] if fields[0] != "si" && fields[0] != "di" && fields[0] != "bx" { t, Immediate = isImmediateOperand(fields[0]) if !t { return false, nil } return true, &MemOperand{ HasDisplacement: true, DisplacementValue: Immediate.Value, DisplacementWidth: Immediate.Width, } } return true, &MemOperand{ IsSingleIndex: true, SingleIndexReg: fields[0], } } if len(fields) == 2 { //外面有偏移量 // [BX + SI]d8 // [BX + DI]d8 // [BP + SI]d8 // [BP + DI]d8 // [BX + SI]d16 // [BX + DI]d16 // [BP + SI]d16 // [BP + DI]d16 if Immediate != nil { switch fields[0] { case "bx", "bp": if fields[1] != "si" && fields[1] != "di" { return false, nil } case "si", "di": if fields[1] != "bx" && fields[1] != "bp" { return false, nil } default: return false, nil } if fields[0] == "si" || fields[0] == "di" { fields[0], fields[1] = fields[1], fields[0] } return true, &MemOperand{ IsDoubleIndex: true, DoubleIndexFirstReg: fields[0], DoubleIndexSecondReg: fields[1], HasDisplacement: true, DisplacementValue: Immediate.Value, DisplacementWidth: Immediate.Width, } } // 外面没有偏移量 t, Immediate = isImmediateOperand(fields[0]) if t { fields[0], fields[1] = fields[1], fields[0] } else { _, Immediate = isImmediateOperand(fields[1]) } // [BX + SI] // [BX + DI] // [BP + SI] // [BP + DI] // [SI + d8] // [DI + d8] // [BP + d8] // [BX + d8] // [SI + d16] // [DI + d16] // [BP + d16] // [BX + d16] switch fields[0] { case "bx", "bp": if fields[1] != "si" && fields[1] != "di" && Immediate == nil { return false, nil } case "si", "di": if fields[1] != "bx" && fields[1] != "bp" && Immediate == nil { return false, nil } default: return false, nil } if Immediate != nil { return true, &MemOperand{ IsSingleIndex: true, SingleIndexReg: fields[0], HasDisplacement: true, DisplacementValue: Immediate.Value, DisplacementWidth: Immediate.Width, } } return true, &MemOperand{ IsDoubleIndex: true, DoubleIndexFirstReg: fields[0], DoubleIndexSecondReg: fields[1], } } if len(fields) == 3 { if Immediate != nil { return false, nil } t, Immediate = isImmediateOperand(fields[0]) if t { fields[0], fields[2] = fields[2], fields[0] } else { t, Immediate = isImmediateOperand(fields[1]) if t { fields[1], fields[2] = fields[2], fields[1] } else { t, Immediate = isImmediateOperand(fields[2]) if !t { return false, nil } } } if fields[0] != "bx" && fields[0] != "bp" { fields[0], fields[1] = fields[1], fields[0] } // [BX + SI + d8] // [BX + DI + d8] // [BP + SI + d8] // [BP + DI + d8] // [BX + SI + d16] // [BX + DI + d16] // [BP + SI + d16] // [BP + DI + d16] switch fields[0] { case "bx", "bp": if fields[1] != "si" && fields[1] != "di" { return false, nil } case "si", "di": if fields[1] != "bx" && fields[1] != "bp" { return false, nil } default: return false, nil } } return true, &MemOperand{ IsDoubleIndex: true, DoubleIndexFirstReg: fields[0], DoubleIndexSecondReg: fields[1], HasDisplacement: true, DisplacementValue: Immediate.Value, DisplacementWidth: Immediate.Width, } }

然后实现了 isMemOperand 来完整判断是否是一个内存操作数:

func isMemOperand(s string) (bool, *MemOperand) { s = strings.TrimSpace(s) idxCol := strings.IndexByte(s, ':') if idxCol == 0 { return false, nil } var t bool var memOperand *MemOperand var operandWidth uint8 var segPrefix string // word ptr ds:[0] // word ptr ds:[bx+2] // ds:[bx+si] if idxCol > 0 { // have segment override prefix idxSpace := strings.LastIndexByte(s[:idxCol], ' ') if idxSpace == 0 { return false, nil } if idxSpace > 0 { // have word ptr or byte ptr fields := strings.Fields(s[:idxSpace]) if fields[1] != "ptr" { return false, nil } if fields[0] == "word" { operandWidth = 16 } else if fields[0] == "byte" { operandWidth = 8 } else { return false, nil } } segPrefix = s[idxSpace+1 : idxCol] if !isSegReg(segPrefix) { return false, nil } t, memOperand = isSimpleMemOperand(s[idxCol+1:]) if !t { return false, nil } fmt.Printf("has seg prefix :%s\n", segPrefix) memOperand.OperandWidth = operandWidth memOperand.HasSegmentPrefix = true memOperand.SegmentPrefix = segPrefix return true, memOperand } //no segment override prefix // word ptr [bx+2] //[bx+2] fields := strings.Fields(s) if fields[0] == "word" || fields[0] == "byte" { if fields[1] != "ptr" { return false, nil } idxP := strings.IndexByte(s, 'p') if idxP 8)&0xff)) } return instruction }

这个函数就是按照手册将指令编码成特定的格式:

在这里插入图片描述

其中调用 encodeMemoryOperand 函数编码内存操作数:

func getMODAndRM(operand *MemOperand) (MOD uint8, RM uint8) { if operand.IsDoubleIndex { MOD = 0b00 if operand.DoubleIndexFirstReg == "bx" && operand.DoubleIndexSecondReg == "si" { RM = 0b000 } else if operand.DoubleIndexFirstReg == "bx" && operand.DoubleIndexSecondReg == "di" { RM = 0b001 } else if operand.DoubleIndexFirstReg == "bp" && operand.DoubleIndexSecondReg == "si" { RM = 0b010 } else if operand.DoubleIndexFirstReg == "bp" && operand.DoubleIndexSecondReg == "di" { RM = 0b011 } if operand.HasDisplacement { if operand.DisplacementWidth == 8 { MOD = 0b01 } else { MOD = 0b10 } } return } if operand.IsSingleIndex { MOD = 0b00 if operand.SingleIndexReg == "si" { RM = 0b100 } else if operand.SingleIndexReg == "di" { RM = 0b101 } else if operand.SingleIndexReg == "bx" { RM = 0b111 } else if operand.SingleIndexReg == "bp" { RM = 0b110 } if operand.HasDisplacement { if operand.DisplacementWidth == 8 { MOD = 0b01 } else { MOD = 0b10 } } else { if RM == 0b110 { log.Fatal("getMODAndRM error 1!") } } return } if operand.HasDisplacement { RM = 0b110 MOD = 0b00 } else { log.Fatal("getMODAndRM error 2!") } return } func encodeMemoryOperand(operand *MemOperand) []byte { /* mod 000 r/m, (DISP-LO), (DISP-HI) */ var instruction []byte mod, rm := getMODAndRM(operand) instruction = append(instruction, rm|uint8(mod8)&0xff)) } else { /* mov bx, [1],DISP也编码成2字节,否则解码时不知道DISP长度*/ if mod == 0b00 && rm == 0b110 { instruction = append(instruction, 0) } } } return instruction } 关键点

值得注意的还有两点:

内存操作数可能带有段前缀,比如 “es: [bx]”。encodeMovImmediateToMemory 对带段前缀的内存操作数调用 encodeSegPrefix 将段前缀编码。

func encodeSegPrefix(segPrefix string) []byte { var instruction []byte var b byte switch segPrefix { case "es": b = 0b00100110 case "cs": b = 0b00101110 case "ss": b = 0b00110110 case "ds": b = 0b00111110 default: log.Fatalf("invalid seg prefix \"%s\"\n", segPrefix) } instruction = append(instruction, b) return instruction }

对内部标号和带 offset 的标号进行了处理。

//考虑立即数是个label,或者是 offset label if src.IsLabel || src.IsLabelOffset { putLabelEncodeInfo(src.Label, uint8(len(instruction)), dst.OperandWidth, src.IsLabelOffset) }

其中 putLabelEncodeInfo 的实现如下:

var labelEncodeInfos []LabelEncodeInfo type LabelEncodeInfo struct { Name string // 标号名称 Offset uint32 // 标号在程序中的偏移量 Width uint8 // 标号值的宽度 IsOffsetLabel bool // 是否是 offset 标号 IsJmpLable bool // 是否是 jmp 指令中的标号 JmpInc uint16 // jmp 指令下一条指令在代码段中的偏移量 } func putLabelEncodeInfo(name string, offsetInInstruction uint8, width uint8, isOffsetLabel bool) { labelEncodeInfos = append(labelEncodeInfos, LabelEncodeInfo{ Name: name, Offset: progOffset + uint32(offsetInInstruction), Width: width, IsOffsetLabel: isOffsetLabel, }) }

它会将内部标号的信息记录下来,后续处理【见后续文章】。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3