8086アセンブリコンパイラの実装(3) - jmp命令の翻訳

序文

『アセンブリ言語』の本の転送命令の紹介を直接見てください。

ここに画像の説明を挿入します

この記事では、jmp コマンドの翻訳について説明します。

jmpアセンブリ命令フォーマット

jmp アセンブリ命令には次の形式があります。

  • jmpショートラベル。セグメント内のショートトランスファー。
  • jmp は ptr ラベルの近くにあります。転送に近いセグメント内。
  • jmp far ptr ラベル。セグメント間の転送をファー転送といいます。
  • jmp 16ビットレジスタ。
  • jmp word ptr メモリユニットアドレス。セグメント内転送。
  • jmp dword ptr メモリ ユニット アドレス。セグメント間の転送。
  • jmpラベル。私はこれをセグメント内の転送に近い形で実装しました。

jmp機械命令フォーマット

jmp 機械命令の形式は次のとおりです。

ここに画像の説明を挿入します

転送に近いセグメント内。アセンブリ「jmp Near ptr」ラベルと「jmp ラベル」の 2 つの形式に対応します。

セグメント内のショートトランスファー。アセンブリの形式「jmpショートラベル」に対応します。

セグメント内間接異動。 「jmp 16ビットレジスタ」と「jmp word ptrメモリユニットアドレス」の2つの形式のアセンブリに対応。

セグメント間の直接転送。アセンブリ「jmp far ptr label」の形式に対応します。

セグメント間の間接転送。アセンブリ「jmp dword ptr メモリユニットアドレス」の形式に対応します。

jmpコマンドの翻訳

jmp オペランドの型

jmp アセンブリ命令には多くの形式があるため、まずオペランドの型を区別する必要があります。

したがって、最初に jmp オペランドのタイプを定義します。

type JmpOperandType uint8

const (
	JmpLabelOperand JmpOperandType = iota
	JmpShortLabelOperand
	JmpNearLabelOperand
	JmpFarLabelOperand
	JmpRegOperand
	JmpWordMemOperand
	JmpDwordMemOperand
	JmpInvalidOperand
)

オペランドの解析

次に、文字列を解析し、それがどのオペランド タイプであるかを識別します。

func getJmpOperand(operand string) JmpOperand {
    
    
	fields := strings.Fields(operand)
	len := len(fields)

	switch len {
    
    
	// jmp s1 , jmp ax
	case 1:
		if t, v := isReg16Operand(fields[0]); t {
    
    
			return JmpOperand{
    
    JmpRegOperand, v}
		}

		return JmpOperand{
    
    JmpLabelOperand, fields[0]}
	// jmp short s1
	case 2:
		return JmpOperand{
    
    JmpShortLabelOperand, fields[1]}

	// jmp near ptr s1, jmp far ptr s1, jmp word ptr [bx+si+123], jmp dword ptr [bx+si+123]
	case 3:
		switch fields[0] {
    
    
		case "near":
			return JmpOperand{
    
    JmpNearLabelOperand, fields[2]}
		case "far":
			return JmpOperand{
    
    JmpFarLabelOperand, fields[2]}
		case "word":
			if t, v := isSimpleMemOperand(fields[2]); t {
    
    
				return JmpOperand{
    
    JmpWordMemOperand, v}
			}
			return JmpOperand{
    
    JmpInvalidOperand, nil}
		case "dword":
			if t, v := isSimpleMemOperand(fields[2]); t {
    
    
				return JmpOperand{
    
    JmpDwordMemOperand, v}
			}
			return JmpOperand{
    
    JmpInvalidOperand, nil}

		}
	}

	return JmpOperand{
    
    JmpInvalidOperand, nil}
}

checkJmpの実装

func checkJmp(stmt []string) (bool, context.Context) {
    
    
	fields := strings.Fields(stmt[1])
	len := len(fields)
	if len > 3 {
    
    
		log.Fatalf("0 invalid \"%s\" syntax", stmt[0])
	}

	if len == 2 {
    
    
		if fields[0] != "short" {
    
    
			log.Fatalf("1 invalid \"%s\" syntax", stmt[0])
		}
	} else if len == 3 {
    
    
		if fields[1] != "ptr" {
    
    
			log.Fatalf("2 invalid \"%s\" syntax", stmt[0])
		}

		if fields[0] != "near" &&
			fields[0] != "far" &&
			fields[0] != "word" &&
			fields[0] != "dword" {
    
    
			log.Fatalf("2 invalid \"%s\" syntax", stmt[0])
		}
	}

	operand := getJmpOperand(stmt[1])
	if operand.Type == JmpInvalidOperand {
    
    
		log.Fatalf("3 invalid \"%s\" syntax", stmt[0])
	}

	var k encodeCtxKey
	ctx := context.Background()
	k = encodeCtxKey("dst")
	ctx = context.WithValue(ctx, k, operand)
	table := map[string]uint8{
    
    
		"jmp":  InstructionJmp,
		"call": InstructionCall,
	}
	k = encodeCtxKey("type")
	ctx = context.WithValue(ctx, k, table[stmt[0]])
	return true, ctx
}

call命令とjmp命令は形式が似ているため、ここではまとめて実装します。そこで、ctxにはjmp命令かcall命令かを区別するために「type」キーが追加されています。

呼び出し命令の形式は次のとおりです。

ここに画像の説明を挿入します

encodeJmpの実装

func encodeJmp(ctx context.Context) []byte {
    
    
	var instruction []byte
	dstOperand := ctx.Value(encodeCtxKey("dst")).(JmpOperand)
	dstOperandType := dstOperand.Type
	instructionType := ctx.Value(encodeCtxKey("type")).(uint8)
	switch dstOperandType {
    
    
	// jmp short s1
	case JmpShortLabelOperand:
		dst := dstOperand.Value.(string)
		instruction = encodeJmpDirectWithinsegmentShort(dst)
	// jmp s1, jmp near ptr s1, call s
	case JmpLabelOperand, JmpNearLabelOperand:
		dst := dstOperand.Value.(string)
		instruction = encodeJmpDirectWithinsegment(instructionType, dst)
	// jmp far ptr s1, call far ptr s
	case JmpFarLabelOperand:
		log.Fatal("jmp not support far ptr")
		dst := dstOperand.Value.(string)
		instruction = encodeJmpDirectIntersegment(dst)
	case JmpRegOperand:
		dst := dstOperand.Value.(*RegOperand)
		instruction = encodeJmpRegIndirectWithinsegment(instructionType, dst)
	case JmpWordMemOperand:
		dst := dstOperand.Value.(*MemOperand)
		instruction = encodeJmpIndirectWithinsegment(instructionType, dst)
	case JmpDwordMemOperand:
		dst := dstOperand.Value.(*MemOperand)
		instruction = encodeJmpIndirectIntersegment(instructionType, dst)
	default:
		log.Fatal("error encodeing jmp")
	}
	return instruction
}

次の段落のエンコード関数 encodeJmpDirectWithinsegment の実装を見てください。

// jmp near ptr s1, jmp s1, call s
func encodeJmpDirectWithinsegment(instructionType uint8, label string) []byte {
    
    
	/*11101001,IP-INC-LO,IP-INC-HI*/
	var instruction []byte
	if instructionType == InstructionJmp {
    
    
		instruction = append(instruction, 0b11101001)
	} else if instructionType == InstructionCall {
    
    
		instruction = append(instruction, 0b11101000)
	}
	instruction = append(instruction, 0)
	instruction = append(instruction, 0)
	putJmpLabelEncodeInfo(label, 1, 16, 3)
	return instruction
}

jmp のオフセットが機械語命令に変換されるとき、その値は 0 になることに注意してください。

次に、putJmpLabelEncodeInfo(label, 1, 16, 3) を呼び出してラベル情報を記録します。

putJmpLabelEncodeInfo の実装は次のとおりです。

func putJmpLabelEncodeInfo(name string, offsetInInstruction uint8, width uint8, instructionLen uint8) {
    
    
	labelEncodeInfos = append(labelEncodeInfos,
		LabelEncodeInfo{
    
    
			Name:       name,
			Offset:     progOffset + uint32(offsetInInstruction),
			Width:      width,
			IsJmpLable: true,
			JmpInc:     codeOffset + uint16(instructionLen),
		})
}

ラベル情報構造の定義を見てみましょう。

var labelEncodeInfos []LabelEncodeInfo

type LabelEncodeInfo struct {
    
    
	Name          string // 标号名称
	Offset        uint32 // 标号在程序中的偏移量
	Width         uint8  // 标号值的宽度
	IsOffsetLabel bool   // 是否是 offset 标号
	IsJmpLable    bool   // 是否是 jmp 指令中的标号
	JmpInc        uint16 // jmp 指令下一条指令在代码段中的偏移量
}

putJmpLabelEncodeInfo(label, 1, 16, 3) の 4 つのパラメーターを理解しましょう。

最初のパラメータはラベル名です。

2 番目のパラメータは、機械語命令の jmp のオフセットです。

ここに画像の説明を挿入します
この形式から、jmp オフセットを表す IP-INC-LO フィールドと IP-INC-HI フィールドが命令の 2 バイト目から始まることがわかります。そのため、offsetInstruct パラメータは 1 です。 putJmpLabelEncodeInfo に追加されたラベル構造で、このパラメータ値を progOffset 変数の値に追加して、プログラム内でのこのラベルのオフセットを取得します。 【次の記事でその機能を紹介します】

3 番目のパラメータは jmp オフセットの幅、16 ビットです。

最後のパラメータは、エンコードされた機械命令の長さ (3 バイト) です。 putJmpLabelEncodeInfo に追加されたラベル構造で、このパラメータ値を codeOffset 変数の値に追加して、この命令の次の命令のオフセットを取得します。コードセグメント! [次の記事でその機能を紹介します]


mov 命令と jmp 命令の翻訳は完了しました。他の命令も同様です。追加したい命令があれば、この考え方に従って追加してください。

おすすめ

転載: blog.csdn.net/woay2008/article/details/126575335