序文
『アセンブリ言語』の本の転送命令の紹介を直接見てください。
この記事では、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 命令の翻訳は完了しました。他の命令も同様です。追加したい命令があれば、この考え方に従って追加してください。