whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
tables.go: ../armmap/map.go ../arm.csv
|
||||
go run ../armmap/map.go -fmt=decoder ../arm.csv >_tables.go && gofmt _tables.go >tables.go && rm _tables.go
|
||||
@@ -0,0 +1,569 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An instFormat describes the format of an instruction encoding.
|
||||
// An instruction with 32-bit value x matches the format if x&mask == value
|
||||
// and the condition matches.
|
||||
// The condition matches if x>>28 == 0xF && value>>28==0xF
|
||||
// or if x>>28 != 0xF and value>>28 == 0.
|
||||
// If x matches the format, then the rest of the fields describe how to interpret x.
|
||||
// The opBits describe bits that should be extracted from x and added to the opcode.
|
||||
// For example opBits = 0x1234 means that the value
|
||||
//
|
||||
// (2 bits at offset 1) followed by (4 bits at offset 3)
|
||||
//
|
||||
// should be added to op.
|
||||
// Finally the args describe how to decode the instruction arguments.
|
||||
// args is stored as a fixed-size array; if there are fewer than len(args) arguments,
|
||||
// args[i] == 0 marks the end of the argument list.
|
||||
type instFormat struct {
|
||||
mask uint32
|
||||
value uint32
|
||||
priority int8
|
||||
op Op
|
||||
opBits uint64
|
||||
args instArgs
|
||||
}
|
||||
|
||||
type instArgs [4]instArg
|
||||
|
||||
var (
|
||||
errMode = fmt.Errorf("unsupported execution mode")
|
||||
errShort = fmt.Errorf("truncated instruction")
|
||||
errUnknown = fmt.Errorf("unknown instruction")
|
||||
)
|
||||
|
||||
var decoderCover []bool
|
||||
|
||||
// Decode decodes the leading bytes in src as a single instruction.
|
||||
func Decode(src []byte, mode Mode) (inst Inst, err error) {
|
||||
if mode != ModeARM {
|
||||
return Inst{}, errMode
|
||||
}
|
||||
if len(src) < 4 {
|
||||
return Inst{}, errShort
|
||||
}
|
||||
|
||||
if decoderCover == nil {
|
||||
decoderCover = make([]bool, len(instFormats))
|
||||
}
|
||||
|
||||
x := binary.LittleEndian.Uint32(src)
|
||||
|
||||
// The instFormat table contains both conditional and unconditional instructions.
|
||||
// Considering only the top 4 bits, the conditional instructions use mask=0, value=0,
|
||||
// while the unconditional instructions use mask=f, value=f.
|
||||
// Prepare a version of x with the condition cleared to 0 in conditional instructions
|
||||
// and then assume mask=f during matching.
|
||||
const condMask = 0xf0000000
|
||||
xNoCond := x
|
||||
if x&condMask != condMask {
|
||||
xNoCond &^= condMask
|
||||
}
|
||||
var priority int8
|
||||
Search:
|
||||
for i := range instFormats {
|
||||
f := &instFormats[i]
|
||||
if xNoCond&(f.mask|condMask) != f.value || f.priority <= priority {
|
||||
continue
|
||||
}
|
||||
delta := uint32(0)
|
||||
deltaShift := uint(0)
|
||||
for opBits := f.opBits; opBits != 0; opBits >>= 16 {
|
||||
n := uint(opBits & 0xFF)
|
||||
off := uint((opBits >> 8) & 0xFF)
|
||||
delta |= (x >> off) & (1<<n - 1) << deltaShift
|
||||
deltaShift += n
|
||||
}
|
||||
op := f.op + Op(delta)
|
||||
|
||||
// Special case: BKPT encodes with condition but cannot have one.
|
||||
if op&^15 == BKPT_EQ && op != BKPT {
|
||||
continue Search
|
||||
}
|
||||
|
||||
var args Args
|
||||
for j, aop := range f.args {
|
||||
if aop == 0 {
|
||||
break
|
||||
}
|
||||
arg := decodeArg(aop, x)
|
||||
if arg == nil { // cannot decode argument
|
||||
continue Search
|
||||
}
|
||||
args[j] = arg
|
||||
}
|
||||
|
||||
decoderCover[i] = true
|
||||
|
||||
inst = Inst{
|
||||
Op: op,
|
||||
Args: args,
|
||||
Enc: x,
|
||||
Len: 4,
|
||||
}
|
||||
priority = f.priority
|
||||
continue Search
|
||||
}
|
||||
if inst.Op != 0 {
|
||||
return inst, nil
|
||||
}
|
||||
return Inst{}, errUnknown
|
||||
}
|
||||
|
||||
// An instArg describes the encoding of a single argument.
|
||||
// In the names used for arguments, _p_ means +, _m_ means -,
|
||||
// _pm_ means ± (usually keyed by the U bit).
|
||||
// The _W suffix indicates a general addressing mode based on the P and W bits.
|
||||
// The _offset and _postindex suffixes force the given addressing mode.
|
||||
// The rest should be somewhat self-explanatory, at least given
|
||||
// the decodeArg function.
|
||||
type instArg uint8
|
||||
|
||||
const (
|
||||
_ instArg = iota
|
||||
arg_APSR
|
||||
arg_FPSCR
|
||||
arg_Dn_half
|
||||
arg_R1_0
|
||||
arg_R1_12
|
||||
arg_R2_0
|
||||
arg_R2_12
|
||||
arg_R_0
|
||||
arg_R_12
|
||||
arg_R_12_nzcv
|
||||
arg_R_16
|
||||
arg_R_16_WB
|
||||
arg_R_8
|
||||
arg_R_rotate
|
||||
arg_R_shift_R
|
||||
arg_R_shift_imm
|
||||
arg_SP
|
||||
arg_Sd
|
||||
arg_Sd_Dd
|
||||
arg_Dd_Sd
|
||||
arg_Sm
|
||||
arg_Sm_Dm
|
||||
arg_Sn
|
||||
arg_Sn_Dn
|
||||
arg_const
|
||||
arg_endian
|
||||
arg_fbits
|
||||
arg_fp_0
|
||||
arg_imm24
|
||||
arg_imm5
|
||||
arg_imm5_32
|
||||
arg_imm5_nz
|
||||
arg_imm_12at8_4at0
|
||||
arg_imm_4at16_12at0
|
||||
arg_imm_vfp
|
||||
arg_label24
|
||||
arg_label24H
|
||||
arg_label_m_12
|
||||
arg_label_p_12
|
||||
arg_label_pm_12
|
||||
arg_label_pm_4_4
|
||||
arg_lsb_width
|
||||
arg_mem_R
|
||||
arg_mem_R_pm_R_W
|
||||
arg_mem_R_pm_R_postindex
|
||||
arg_mem_R_pm_R_shift_imm_W
|
||||
arg_mem_R_pm_R_shift_imm_offset
|
||||
arg_mem_R_pm_R_shift_imm_postindex
|
||||
arg_mem_R_pm_imm12_W
|
||||
arg_mem_R_pm_imm12_offset
|
||||
arg_mem_R_pm_imm12_postindex
|
||||
arg_mem_R_pm_imm8_W
|
||||
arg_mem_R_pm_imm8_postindex
|
||||
arg_mem_R_pm_imm8at0_offset
|
||||
arg_option
|
||||
arg_registers
|
||||
arg_registers1
|
||||
arg_registers2
|
||||
arg_satimm4
|
||||
arg_satimm5
|
||||
arg_satimm4m1
|
||||
arg_satimm5m1
|
||||
arg_widthm1
|
||||
)
|
||||
|
||||
// decodeArg decodes the arg described by aop from the instruction bits x.
|
||||
// It returns nil if x cannot be decoded according to aop.
|
||||
func decodeArg(aop instArg, x uint32) Arg {
|
||||
switch aop {
|
||||
default:
|
||||
return nil
|
||||
|
||||
case arg_APSR:
|
||||
return APSR
|
||||
case arg_FPSCR:
|
||||
return FPSCR
|
||||
|
||||
case arg_R_0:
|
||||
return Reg(x & (1<<4 - 1))
|
||||
case arg_R_8:
|
||||
return Reg((x >> 8) & (1<<4 - 1))
|
||||
case arg_R_12:
|
||||
return Reg((x >> 12) & (1<<4 - 1))
|
||||
case arg_R_16:
|
||||
return Reg((x >> 16) & (1<<4 - 1))
|
||||
|
||||
case arg_R_12_nzcv:
|
||||
r := Reg((x >> 12) & (1<<4 - 1))
|
||||
if r == R15 {
|
||||
return APSR_nzcv
|
||||
}
|
||||
return r
|
||||
|
||||
case arg_R_16_WB:
|
||||
mode := AddrLDM
|
||||
if (x>>21)&1 != 0 {
|
||||
mode = AddrLDM_WB
|
||||
}
|
||||
return Mem{Base: Reg((x >> 16) & (1<<4 - 1)), Mode: mode}
|
||||
|
||||
case arg_R_rotate:
|
||||
Rm := Reg(x & (1<<4 - 1))
|
||||
typ, count := decodeShift(x)
|
||||
// ROR #0 here means ROR #0, but decodeShift rewrites to RRX #1.
|
||||
if typ == RotateRightExt {
|
||||
return Rm
|
||||
}
|
||||
return RegShift{Rm, typ, count}
|
||||
|
||||
case arg_R_shift_R:
|
||||
Rm := Reg(x & (1<<4 - 1))
|
||||
Rs := Reg((x >> 8) & (1<<4 - 1))
|
||||
typ := Shift((x >> 5) & (1<<2 - 1))
|
||||
return RegShiftReg{Rm, typ, Rs}
|
||||
|
||||
case arg_R_shift_imm:
|
||||
Rm := Reg(x & (1<<4 - 1))
|
||||
typ, count := decodeShift(x)
|
||||
if typ == ShiftLeft && count == 0 {
|
||||
return Reg(Rm)
|
||||
}
|
||||
return RegShift{Rm, typ, count}
|
||||
|
||||
case arg_R1_0:
|
||||
return Reg((x & (1<<4 - 1)))
|
||||
case arg_R1_12:
|
||||
return Reg(((x >> 12) & (1<<4 - 1)))
|
||||
case arg_R2_0:
|
||||
return Reg((x & (1<<4 - 1)) | 1)
|
||||
case arg_R2_12:
|
||||
return Reg(((x >> 12) & (1<<4 - 1)) | 1)
|
||||
|
||||
case arg_SP:
|
||||
return SP
|
||||
|
||||
case arg_Sd_Dd:
|
||||
v := (x >> 12) & (1<<4 - 1)
|
||||
vx := (x >> 22) & 1
|
||||
sz := (x >> 8) & 1
|
||||
if sz != 0 {
|
||||
return D0 + Reg(vx<<4+v)
|
||||
} else {
|
||||
return S0 + Reg(v<<1+vx)
|
||||
}
|
||||
|
||||
case arg_Dd_Sd:
|
||||
return decodeArg(arg_Sd_Dd, x^(1<<8))
|
||||
|
||||
case arg_Sd:
|
||||
v := (x >> 12) & (1<<4 - 1)
|
||||
vx := (x >> 22) & 1
|
||||
return S0 + Reg(v<<1+vx)
|
||||
|
||||
case arg_Sm_Dm:
|
||||
v := (x >> 0) & (1<<4 - 1)
|
||||
vx := (x >> 5) & 1
|
||||
sz := (x >> 8) & 1
|
||||
if sz != 0 {
|
||||
return D0 + Reg(vx<<4+v)
|
||||
} else {
|
||||
return S0 + Reg(v<<1+vx)
|
||||
}
|
||||
|
||||
case arg_Sm:
|
||||
v := (x >> 0) & (1<<4 - 1)
|
||||
vx := (x >> 5) & 1
|
||||
return S0 + Reg(v<<1+vx)
|
||||
|
||||
case arg_Dn_half:
|
||||
v := (x >> 16) & (1<<4 - 1)
|
||||
vx := (x >> 7) & 1
|
||||
return RegX{D0 + Reg(vx<<4+v), int((x >> 21) & 1)}
|
||||
|
||||
case arg_Sn_Dn:
|
||||
v := (x >> 16) & (1<<4 - 1)
|
||||
vx := (x >> 7) & 1
|
||||
sz := (x >> 8) & 1
|
||||
if sz != 0 {
|
||||
return D0 + Reg(vx<<4+v)
|
||||
} else {
|
||||
return S0 + Reg(v<<1+vx)
|
||||
}
|
||||
|
||||
case arg_Sn:
|
||||
v := (x >> 16) & (1<<4 - 1)
|
||||
vx := (x >> 7) & 1
|
||||
return S0 + Reg(v<<1+vx)
|
||||
|
||||
case arg_const:
|
||||
v := x & (1<<8 - 1)
|
||||
rot := (x >> 8) & (1<<4 - 1) * 2
|
||||
if rot > 0 && v&3 == 0 {
|
||||
// could rotate less
|
||||
return ImmAlt{uint8(v), uint8(rot)}
|
||||
}
|
||||
if rot >= 24 && ((v<<(32-rot))&0xFF)>>(32-rot) == v {
|
||||
// could wrap around to rot==0.
|
||||
return ImmAlt{uint8(v), uint8(rot)}
|
||||
}
|
||||
return Imm(v>>rot | v<<(32-rot))
|
||||
|
||||
case arg_endian:
|
||||
return Endian((x >> 9) & 1)
|
||||
|
||||
case arg_fbits:
|
||||
return Imm((16 << ((x >> 7) & 1)) - ((x&(1<<4-1))<<1 | (x>>5)&1))
|
||||
|
||||
case arg_fp_0:
|
||||
return Imm(0)
|
||||
|
||||
case arg_imm24:
|
||||
return Imm(x & (1<<24 - 1))
|
||||
|
||||
case arg_imm5:
|
||||
return Imm((x >> 7) & (1<<5 - 1))
|
||||
|
||||
case arg_imm5_32:
|
||||
x = (x >> 7) & (1<<5 - 1)
|
||||
if x == 0 {
|
||||
x = 32
|
||||
}
|
||||
return Imm(x)
|
||||
|
||||
case arg_imm5_nz:
|
||||
x = (x >> 7) & (1<<5 - 1)
|
||||
if x == 0 {
|
||||
return nil
|
||||
}
|
||||
return Imm(x)
|
||||
|
||||
case arg_imm_4at16_12at0:
|
||||
return Imm((x>>16)&(1<<4-1)<<12 | x&(1<<12-1))
|
||||
|
||||
case arg_imm_12at8_4at0:
|
||||
return Imm((x>>8)&(1<<12-1)<<4 | x&(1<<4-1))
|
||||
|
||||
case arg_imm_vfp:
|
||||
x = (x>>16)&(1<<4-1)<<4 | x&(1<<4-1)
|
||||
return Imm(x)
|
||||
|
||||
case arg_label24:
|
||||
imm := (x & (1<<24 - 1)) << 2
|
||||
return PCRel(int32(imm<<6) >> 6)
|
||||
|
||||
case arg_label24H:
|
||||
h := (x >> 24) & 1
|
||||
imm := (x&(1<<24-1))<<2 | h<<1
|
||||
return PCRel(int32(imm<<6) >> 6)
|
||||
|
||||
case arg_label_m_12:
|
||||
d := int32(x & (1<<12 - 1))
|
||||
return Mem{Base: PC, Mode: AddrOffset, Offset: int16(-d)}
|
||||
|
||||
case arg_label_p_12:
|
||||
d := int32(x & (1<<12 - 1))
|
||||
return Mem{Base: PC, Mode: AddrOffset, Offset: int16(d)}
|
||||
|
||||
case arg_label_pm_12:
|
||||
d := int32(x & (1<<12 - 1))
|
||||
u := (x >> 23) & 1
|
||||
if u == 0 {
|
||||
d = -d
|
||||
}
|
||||
return Mem{Base: PC, Mode: AddrOffset, Offset: int16(d)}
|
||||
|
||||
case arg_label_pm_4_4:
|
||||
d := int32((x>>8)&(1<<4-1)<<4 | x&(1<<4-1))
|
||||
u := (x >> 23) & 1
|
||||
if u == 0 {
|
||||
d = -d
|
||||
}
|
||||
return PCRel(d)
|
||||
|
||||
case arg_lsb_width:
|
||||
lsb := (x >> 7) & (1<<5 - 1)
|
||||
msb := (x >> 16) & (1<<5 - 1)
|
||||
if msb < lsb || msb >= 32 {
|
||||
return nil
|
||||
}
|
||||
return Imm(msb + 1 - lsb)
|
||||
|
||||
case arg_mem_R:
|
||||
Rn := Reg((x >> 16) & (1<<4 - 1))
|
||||
return Mem{Base: Rn, Mode: AddrOffset}
|
||||
|
||||
case arg_mem_R_pm_R_postindex:
|
||||
// Treat [<Rn>],+/-<Rm> like [<Rn>,+/-<Rm>{,<shift>}]{!}
|
||||
// by forcing shift bits to <<0 and P=0, W=0 (postindex=true).
|
||||
return decodeArg(arg_mem_R_pm_R_shift_imm_W, x&^((1<<7-1)<<5|1<<24|1<<21))
|
||||
|
||||
case arg_mem_R_pm_R_W:
|
||||
// Treat [<Rn>,+/-<Rm>]{!} like [<Rn>,+/-<Rm>{,<shift>}]{!}
|
||||
// by forcing shift bits to <<0.
|
||||
return decodeArg(arg_mem_R_pm_R_shift_imm_W, x&^((1<<7-1)<<5))
|
||||
|
||||
case arg_mem_R_pm_R_shift_imm_offset:
|
||||
// Treat [<Rn>],+/-<Rm>{,<shift>} like [<Rn>,+/-<Rm>{,<shift>}]{!}
|
||||
// by forcing P=1, W=0 (index=false, wback=false).
|
||||
return decodeArg(arg_mem_R_pm_R_shift_imm_W, x&^(1<<21)|1<<24)
|
||||
|
||||
case arg_mem_R_pm_R_shift_imm_postindex:
|
||||
// Treat [<Rn>],+/-<Rm>{,<shift>} like [<Rn>,+/-<Rm>{,<shift>}]{!}
|
||||
// by forcing P=0, W=0 (postindex=true).
|
||||
return decodeArg(arg_mem_R_pm_R_shift_imm_W, x&^(1<<24|1<<21))
|
||||
|
||||
case arg_mem_R_pm_R_shift_imm_W:
|
||||
Rn := Reg((x >> 16) & (1<<4 - 1))
|
||||
Rm := Reg(x & (1<<4 - 1))
|
||||
typ, count := decodeShift(x)
|
||||
u := (x >> 23) & 1
|
||||
w := (x >> 21) & 1
|
||||
p := (x >> 24) & 1
|
||||
if p == 0 && w == 1 {
|
||||
return nil
|
||||
}
|
||||
sign := int8(+1)
|
||||
if u == 0 {
|
||||
sign = -1
|
||||
}
|
||||
mode := AddrMode(uint8(p<<1) | uint8(w^1))
|
||||
return Mem{Base: Rn, Mode: mode, Sign: sign, Index: Rm, Shift: typ, Count: count}
|
||||
|
||||
case arg_mem_R_pm_imm12_offset:
|
||||
// Treat [<Rn>,#+/-<imm12>] like [<Rn>{,#+/-<imm12>}]{!}
|
||||
// by forcing P=1, W=0 (index=false, wback=false).
|
||||
return decodeArg(arg_mem_R_pm_imm12_W, x&^(1<<21)|1<<24)
|
||||
|
||||
case arg_mem_R_pm_imm12_postindex:
|
||||
// Treat [<Rn>],#+/-<imm12> like [<Rn>{,#+/-<imm12>}]{!}
|
||||
// by forcing P=0, W=0 (postindex=true).
|
||||
return decodeArg(arg_mem_R_pm_imm12_W, x&^(1<<24|1<<21))
|
||||
|
||||
case arg_mem_R_pm_imm12_W:
|
||||
Rn := Reg((x >> 16) & (1<<4 - 1))
|
||||
u := (x >> 23) & 1
|
||||
w := (x >> 21) & 1
|
||||
p := (x >> 24) & 1
|
||||
if p == 0 && w == 1 {
|
||||
return nil
|
||||
}
|
||||
sign := int8(+1)
|
||||
if u == 0 {
|
||||
sign = -1
|
||||
}
|
||||
imm := int16(x & (1<<12 - 1))
|
||||
mode := AddrMode(uint8(p<<1) | uint8(w^1))
|
||||
return Mem{Base: Rn, Mode: mode, Offset: int16(sign) * imm}
|
||||
|
||||
case arg_mem_R_pm_imm8_postindex:
|
||||
// Treat [<Rn>],#+/-<imm8> like [<Rn>{,#+/-<imm8>}]{!}
|
||||
// by forcing P=0, W=0 (postindex=true).
|
||||
return decodeArg(arg_mem_R_pm_imm8_W, x&^(1<<24|1<<21))
|
||||
|
||||
case arg_mem_R_pm_imm8_W:
|
||||
Rn := Reg((x >> 16) & (1<<4 - 1))
|
||||
u := (x >> 23) & 1
|
||||
w := (x >> 21) & 1
|
||||
p := (x >> 24) & 1
|
||||
if p == 0 && w == 1 {
|
||||
return nil
|
||||
}
|
||||
sign := int8(+1)
|
||||
if u == 0 {
|
||||
sign = -1
|
||||
}
|
||||
imm := int16((x>>8)&(1<<4-1)<<4 | x&(1<<4-1))
|
||||
mode := AddrMode(uint8(p<<1) | uint8(w^1))
|
||||
return Mem{Base: Rn, Mode: mode, Offset: int16(sign) * imm}
|
||||
|
||||
case arg_mem_R_pm_imm8at0_offset:
|
||||
Rn := Reg((x >> 16) & (1<<4 - 1))
|
||||
u := (x >> 23) & 1
|
||||
sign := int8(+1)
|
||||
if u == 0 {
|
||||
sign = -1
|
||||
}
|
||||
imm := int16(x&(1<<8-1)) << 2
|
||||
return Mem{Base: Rn, Mode: AddrOffset, Offset: int16(sign) * imm}
|
||||
|
||||
case arg_option:
|
||||
return Imm(x & (1<<4 - 1))
|
||||
|
||||
case arg_registers:
|
||||
return RegList(x & (1<<16 - 1))
|
||||
|
||||
case arg_registers2:
|
||||
x &= 1<<16 - 1
|
||||
n := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
if x>>uint(i)&1 != 0 {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n < 2 {
|
||||
return nil
|
||||
}
|
||||
return RegList(x)
|
||||
|
||||
case arg_registers1:
|
||||
Rt := (x >> 12) & (1<<4 - 1)
|
||||
return RegList(1 << Rt)
|
||||
|
||||
case arg_satimm4:
|
||||
return Imm((x >> 16) & (1<<4 - 1))
|
||||
|
||||
case arg_satimm5:
|
||||
return Imm((x >> 16) & (1<<5 - 1))
|
||||
|
||||
case arg_satimm4m1:
|
||||
return Imm((x>>16)&(1<<4-1) + 1)
|
||||
|
||||
case arg_satimm5m1:
|
||||
return Imm((x>>16)&(1<<5-1) + 1)
|
||||
|
||||
case arg_widthm1:
|
||||
return Imm((x>>16)&(1<<5-1) + 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// decodeShift decodes the shift-by-immediate encoded in x.
|
||||
func decodeShift(x uint32) (Shift, uint8) {
|
||||
count := (x >> 7) & (1<<5 - 1)
|
||||
typ := Shift((x >> 5) & (1<<2 - 1))
|
||||
switch typ {
|
||||
case ShiftRight, ShiftRightSigned:
|
||||
if count == 0 {
|
||||
count = 32
|
||||
}
|
||||
case RotateRight:
|
||||
if count == 0 {
|
||||
typ = RotateRightExt
|
||||
count = 1
|
||||
}
|
||||
}
|
||||
return typ, uint8(count)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/decode.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
all := string(data)
|
||||
for strings.Contains(all, "\t\t") {
|
||||
all = strings.Replace(all, "\t\t", "\t", -1)
|
||||
}
|
||||
for _, line := range strings.Split(all, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
f := strings.SplitN(line, "\t", 4)
|
||||
i := strings.Index(f[0], "|")
|
||||
if i < 0 {
|
||||
t.Errorf("parsing %q: missing | separator", f[0])
|
||||
continue
|
||||
}
|
||||
if i%2 != 0 {
|
||||
t.Errorf("parsing %q: misaligned | separator", f[0])
|
||||
}
|
||||
size := i / 2
|
||||
code, err := hex.DecodeString(f[0][:i] + f[0][i+1:])
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", f[0], err)
|
||||
continue
|
||||
}
|
||||
mode, err := strconv.Atoi(f[1])
|
||||
if err != nil {
|
||||
t.Errorf("invalid mode %q in: %s", f[1], line)
|
||||
continue
|
||||
}
|
||||
syntax, asm := f[2], f[3]
|
||||
inst, err := Decode(code, Mode(mode))
|
||||
var out string
|
||||
if err != nil {
|
||||
out = "error: " + err.Error()
|
||||
} else {
|
||||
switch syntax {
|
||||
case "gnu":
|
||||
out = GNUSyntax(inst)
|
||||
case "plan9": // [sic]
|
||||
out = GoSyntax(inst, 0, nil, nil)
|
||||
default:
|
||||
t.Errorf("unknown syntax %q", syntax)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if out != asm || inst.Len != size {
|
||||
t.Errorf("Decode(%s) [%s] = %s, %d, want %s, %d", f[0], syntax, out, inst.Len, asm, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Support for testing against external disassembler program.
|
||||
// Copied and simplified from ../../x86/x86asm/ext_test.go.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths")
|
||||
dumpTest = flag.Bool("dump", false, "dump all encodings")
|
||||
mismatch = flag.Bool("mismatch", false, "log allowed mismatches")
|
||||
longTest = flag.Bool("long", false, "long test")
|
||||
keep = flag.Bool("keep", false, "keep object files around")
|
||||
debug = false
|
||||
)
|
||||
|
||||
// An ExtInst represents a single decoded instruction parsed
|
||||
// from an external disassembler's output.
|
||||
type ExtInst struct {
|
||||
addr uint32
|
||||
enc [4]byte
|
||||
nenc int
|
||||
text string
|
||||
}
|
||||
|
||||
func (r ExtInst) String() string {
|
||||
return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text)
|
||||
}
|
||||
|
||||
// An ExtDis is a connection between an external disassembler and a test.
|
||||
type ExtDis struct {
|
||||
Arch Mode
|
||||
Dec chan ExtInst
|
||||
File *os.File
|
||||
Size int
|
||||
KeepFile bool
|
||||
Cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// Run runs the given command - the external disassembler - and returns
|
||||
// a buffered reader of its standard output.
|
||||
func (ext *ExtDis) Run(cmd ...string) (*bufio.Reader, error) {
|
||||
if *keep {
|
||||
log.Printf("%s\n", strings.Join(cmd, " "))
|
||||
}
|
||||
ext.Cmd = exec.Command(cmd[0], cmd[1:]...)
|
||||
out, err := ext.Cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stdoutpipe: %v", err)
|
||||
}
|
||||
if err := ext.Cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
b := bufio.NewReaderSize(out, 1<<20)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Wait waits for the command started with Run to exit.
|
||||
func (ext *ExtDis) Wait() error {
|
||||
return ext.Cmd.Wait()
|
||||
}
|
||||
|
||||
// testExtDis tests a set of byte sequences against an external disassembler.
|
||||
// The disassembler is expected to produce the given syntax and be run
|
||||
// in the given architecture mode (16, 32, or 64-bit).
|
||||
// The extdis function must start the external disassembler
|
||||
// and then parse its output, sending the parsed instructions on ext.Dec.
|
||||
// The generate function calls its argument f once for each byte sequence
|
||||
// to be tested. The generate function itself will be called twice, and it must
|
||||
// make the same sequence of calls to f each time.
|
||||
// When a disassembly does not match the internal decoding,
|
||||
// allowedMismatch determines whether this mismatch should be
|
||||
// allowed, or else considered an error.
|
||||
func testExtDis(
|
||||
t *testing.T,
|
||||
syntax string,
|
||||
arch Mode,
|
||||
extdis func(ext *ExtDis) error,
|
||||
generate func(f func([]byte)),
|
||||
allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool,
|
||||
) {
|
||||
start := time.Now()
|
||||
ext := &ExtDis{
|
||||
Dec: make(chan ExtInst),
|
||||
Arch: arch,
|
||||
}
|
||||
errc := make(chan error)
|
||||
|
||||
// First pass: write instructions to input file for external disassembler.
|
||||
file, f, size, err := writeInst(generate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ext.Size = size
|
||||
ext.File = f
|
||||
defer func() {
|
||||
f.Close()
|
||||
if !*keep {
|
||||
os.Remove(file)
|
||||
}
|
||||
}()
|
||||
|
||||
// Second pass: compare disassembly against our decodings.
|
||||
var (
|
||||
totalTests = 0
|
||||
totalSkips = 0
|
||||
totalErrors = 0
|
||||
|
||||
errors = make([]string, 0, 100) // sampled errors, at most cap
|
||||
)
|
||||
go func() {
|
||||
errc <- extdis(ext)
|
||||
}()
|
||||
generate(func(enc []byte) {
|
||||
dec, ok := <-ext.Dec
|
||||
if !ok {
|
||||
t.Errorf("decoding stream ended early")
|
||||
return
|
||||
}
|
||||
inst, text := disasm(syntax, arch, pad(enc))
|
||||
totalTests++
|
||||
if *dumpTest {
|
||||
fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc)
|
||||
}
|
||||
if text != dec.text || inst.Len != dec.nenc {
|
||||
suffix := ""
|
||||
if allowedMismatch(text, size, &inst, dec) {
|
||||
totalSkips++
|
||||
if !*mismatch {
|
||||
return
|
||||
}
|
||||
suffix += " (allowed mismatch)"
|
||||
}
|
||||
totalErrors++
|
||||
if len(errors) >= cap(errors) {
|
||||
j := rand.Intn(totalErrors)
|
||||
if j >= cap(errors) {
|
||||
return
|
||||
}
|
||||
errors = append(errors[:j], errors[j+1:]...)
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix))
|
||||
}
|
||||
})
|
||||
|
||||
if *mismatch {
|
||||
totalErrors -= totalSkips
|
||||
}
|
||||
|
||||
for _, b := range errors {
|
||||
t.Log(b)
|
||||
}
|
||||
|
||||
if totalErrors > 0 {
|
||||
t.Fail()
|
||||
}
|
||||
t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds())
|
||||
|
||||
if err := <-errc; err != nil {
|
||||
t.Fatalf("external disassembler: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const start = 0x8000 // start address of text
|
||||
|
||||
// writeInst writes the generated byte sequences to a new file
|
||||
// starting at offset start. That file is intended to be the input to
|
||||
// the external disassembler.
|
||||
func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) {
|
||||
f, err = ioutil.TempFile("", "armasm")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file = f.Name()
|
||||
|
||||
f.Seek(start, io.SeekStart)
|
||||
w := bufio.NewWriter(f)
|
||||
defer w.Flush()
|
||||
size = 0
|
||||
generate(func(x []byte) {
|
||||
if len(x) > 4 {
|
||||
x = x[:4]
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("%#x: %x%x\n", start+size, x, zeros[len(x):])
|
||||
}
|
||||
w.Write(x)
|
||||
w.Write(zeros[len(x):])
|
||||
size += len(zeros)
|
||||
})
|
||||
return file, f, size, nil
|
||||
}
|
||||
|
||||
var zeros = []byte{0, 0, 0, 0}
|
||||
|
||||
// pad pads the code sequence with pops.
|
||||
func pad(enc []byte) []byte {
|
||||
if len(enc) < 4 {
|
||||
enc = append(enc[:len(enc):len(enc)], zeros[:4-len(enc)]...)
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// disasm returns the decoded instruction and text
|
||||
// for the given source bytes, using the given syntax and mode.
|
||||
func disasm(syntax string, mode Mode, src []byte) (inst Inst, text string) {
|
||||
// If printTests is set, we record the coverage value
|
||||
// before and after, and we write out the inputs for which
|
||||
// coverage went up, in the format expected in testdata/decode.text.
|
||||
// This produces a fairly small set of test cases that exercise nearly
|
||||
// all the code.
|
||||
var cover float64
|
||||
if *printTests {
|
||||
cover -= coverage()
|
||||
}
|
||||
|
||||
inst, err := Decode(src, mode)
|
||||
if err != nil {
|
||||
text = "error: " + err.Error()
|
||||
} else {
|
||||
text = inst.String()
|
||||
switch syntax {
|
||||
//case "arm":
|
||||
// text = ARMSyntax(inst)
|
||||
case "gnu":
|
||||
text = GNUSyntax(inst)
|
||||
//case "plan9": // [sic]
|
||||
// text = GoSyntax(inst, 0, nil)
|
||||
default:
|
||||
text = "error: unknown syntax " + syntax
|
||||
}
|
||||
}
|
||||
|
||||
if *printTests {
|
||||
cover += coverage()
|
||||
if cover > 0 {
|
||||
max := len(src)
|
||||
if max > 4 && inst.Len <= 4 {
|
||||
max = 4
|
||||
}
|
||||
fmt.Printf("%x|%x\t%d\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], mode, syntax, text)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// coverage returns a floating point number denoting the
|
||||
// test coverage until now. The number increases when new code paths are exercised,
|
||||
// both in the Go program and in the decoder byte code.
|
||||
func coverage() float64 {
|
||||
/*
|
||||
testing.Coverage is not in the main distribution.
|
||||
The implementation, which must go in package testing, is:
|
||||
|
||||
// Coverage reports the current code coverage as a fraction in the range [0, 1].
|
||||
func Coverage() float64 {
|
||||
var n, d int64
|
||||
for _, counters := range cover.Counters {
|
||||
for _, c := range counters {
|
||||
if c > 0 {
|
||||
n++
|
||||
}
|
||||
d++
|
||||
}
|
||||
}
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(n) / float64(d)
|
||||
}
|
||||
*/
|
||||
|
||||
var f float64
|
||||
f += testing.Coverage()
|
||||
f += decodeCoverage()
|
||||
return f
|
||||
}
|
||||
|
||||
func decodeCoverage() float64 {
|
||||
n := 0
|
||||
for _, t := range decoderCover {
|
||||
if t {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return float64(1+n) / float64(1+len(decoderCover))
|
||||
}
|
||||
|
||||
// Helpers for writing disassembler output parsers.
|
||||
|
||||
// hasPrefix reports whether any of the space-separated words in the text s
|
||||
// begins with any of the given prefixes.
|
||||
func hasPrefix(s string, prefixes ...string) bool {
|
||||
for _, prefix := range prefixes {
|
||||
for s := s; s != ""; {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return true
|
||||
}
|
||||
i := strings.Index(s, " ")
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
s = s[i+1:]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// contains reports whether the text s contains any of the given substrings.
|
||||
func contains(s string, substrings ...string) bool {
|
||||
for _, sub := range substrings {
|
||||
if strings.Contains(s, sub) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isHex reports whether b is a hexadecimal character (0-9A-Fa-f).
|
||||
func isHex(b byte) bool { return b == '0' || unhex[b] > 0 }
|
||||
|
||||
// parseHex parses the hexadecimal byte dump in hex,
|
||||
// appending the parsed bytes to raw and returning the updated slice.
|
||||
// The returned bool signals whether any invalid hex was found.
|
||||
// Spaces and tabs between bytes are okay but any other non-hex is not.
|
||||
func parseHex(hex []byte, raw []byte) ([]byte, bool) {
|
||||
hex = trimSpace(hex)
|
||||
for j := 0; j < len(hex); {
|
||||
for hex[j] == ' ' || hex[j] == '\t' {
|
||||
j++
|
||||
}
|
||||
if j >= len(hex) {
|
||||
break
|
||||
}
|
||||
if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) {
|
||||
return nil, false
|
||||
}
|
||||
raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]])
|
||||
j += 2
|
||||
}
|
||||
return raw, true
|
||||
}
|
||||
|
||||
var unhex = [256]byte{
|
||||
'0': 0,
|
||||
'1': 1,
|
||||
'2': 2,
|
||||
'3': 3,
|
||||
'4': 4,
|
||||
'5': 5,
|
||||
'6': 6,
|
||||
'7': 7,
|
||||
'8': 8,
|
||||
'9': 9,
|
||||
'A': 10,
|
||||
'B': 11,
|
||||
'C': 12,
|
||||
'D': 13,
|
||||
'E': 14,
|
||||
'F': 15,
|
||||
'a': 10,
|
||||
'b': 11,
|
||||
'c': 12,
|
||||
'd': 13,
|
||||
'e': 14,
|
||||
'f': 15,
|
||||
}
|
||||
|
||||
// index is like bytes.Index(s, []byte(t)) but avoids the allocation.
|
||||
func index(s []byte, t string) int {
|
||||
i := 0
|
||||
for {
|
||||
j := bytes.IndexByte(s[i:], t[0])
|
||||
if j < 0 {
|
||||
return -1
|
||||
}
|
||||
i = i + j
|
||||
if i+len(t) > len(s) {
|
||||
return -1
|
||||
}
|
||||
for k := 1; k < len(t); k++ {
|
||||
if s[i+k] != t[k] {
|
||||
goto nomatch
|
||||
}
|
||||
}
|
||||
return i
|
||||
nomatch:
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s.
|
||||
// If s must be rewritten, it is rewritten in place.
|
||||
func fixSpace(s []byte) []byte {
|
||||
s = trimSpace(s)
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' {
|
||||
goto Fix
|
||||
}
|
||||
}
|
||||
return s
|
||||
|
||||
Fix:
|
||||
b := s
|
||||
w := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '\t' || c == '\n' {
|
||||
c = ' '
|
||||
}
|
||||
if c == ' ' && w > 0 && b[w-1] == ' ' {
|
||||
continue
|
||||
}
|
||||
b[w] = c
|
||||
w++
|
||||
}
|
||||
if w > 0 && b[w-1] == ' ' {
|
||||
w--
|
||||
}
|
||||
return b[:w]
|
||||
}
|
||||
|
||||
// trimSpace trims leading and trailing space from s, returning a subslice of s.
|
||||
func trimSpace(s []byte) []byte {
|
||||
j := len(s)
|
||||
for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') {
|
||||
j--
|
||||
}
|
||||
i := 0
|
||||
for i < j && (s[i] == ' ' || s[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
return s[i:j]
|
||||
}
|
||||
|
||||
// pcrel matches instructions using relative addressing mode.
|
||||
var (
|
||||
pcrel = regexp.MustCompile(`^((?:.* )?(?:b|bl)x?(?:eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le)?) 0x([0-9a-f]+)$`)
|
||||
)
|
||||
|
||||
// Generators.
|
||||
//
|
||||
// The test cases are described as functions that invoke a callback repeatedly,
|
||||
// with a new input sequence each time. These helpers make writing those
|
||||
// a little easier.
|
||||
|
||||
// condCases generates conditional instructions.
|
||||
func condCases(t *testing.T) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
// All the strides are relatively prime to 2 and therefore to 2²⁸,
|
||||
// so we will not repeat any instructions until we have tried all 2²⁸.
|
||||
// Using a stride other than 1 is meant to visit the instructions in a
|
||||
// pseudorandom order, which gives better variety in the set of
|
||||
// test cases chosen by -printtests.
|
||||
stride := uint32(10007)
|
||||
n := 1 << 28 / 7
|
||||
if testing.Short() {
|
||||
stride = 100003
|
||||
n = 1 << 28 / 1001
|
||||
} else if *longTest {
|
||||
stride = 200000033
|
||||
n = 1 << 28
|
||||
}
|
||||
x := uint32(0)
|
||||
for i := 0; i < n; i++ {
|
||||
enc := (x%15)<<28 | x&(1<<28-1)
|
||||
try([]byte{byte(enc), byte(enc >> 8), byte(enc >> 16), byte(enc >> 24)})
|
||||
x += stride
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uncondCases generates unconditional instructions.
|
||||
func uncondCases(t *testing.T) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
condCases(t)(func(enc []byte) {
|
||||
enc[3] |= 0xF0
|
||||
try(enc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func countBits(x uint32) int {
|
||||
n := 0
|
||||
for ; x != 0; x >>= 1 {
|
||||
n += int(x & 1)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func expandBits(x, m uint32) uint32 {
|
||||
var out uint32
|
||||
for i := uint(0); i < 32; i++ {
|
||||
out >>= 1
|
||||
if m&1 != 0 {
|
||||
out |= (x & 1) << 31
|
||||
x >>= 1
|
||||
}
|
||||
m >>= 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func tryCondMask(mask, val uint32, try func([]byte)) {
|
||||
n := countBits(^mask)
|
||||
bits := uint32(0)
|
||||
for i := 0; i < 1<<uint(n); i++ {
|
||||
bits += 848251 // arbitrary prime
|
||||
x := val | expandBits(bits, ^mask) | uint32(i)%15<<28
|
||||
try([]byte{byte(x), byte(x >> 8), byte(x >> 16), byte(x >> 24)})
|
||||
}
|
||||
}
|
||||
|
||||
// vfpCases generates VFP instructions.
|
||||
func vfpCases(t *testing.T) func(func([]byte)) {
|
||||
const (
|
||||
vfpmask uint32 = 0xFF00FE10
|
||||
vfp uint32 = 0x0E009A00
|
||||
)
|
||||
return func(try func([]byte)) {
|
||||
tryCondMask(0xff00fe10, 0x0e009a00, try) // standard VFP instruction space
|
||||
tryCondMask(0xffc00f7f, 0x0e000b10, try) // VFP MOV core reg to/from float64 half
|
||||
tryCondMask(0xffe00f7f, 0x0e000a10, try) // VFP MOV core reg to/from float32
|
||||
tryCondMask(0xffef0fff, 0x0ee10a10, try) // VFP MOV core reg to/from cond codes
|
||||
}
|
||||
}
|
||||
|
||||
// hexCases generates the cases written in hexadecimal in the encoded string.
|
||||
// Spaces in 'encoded' separate entire test cases, not individual bytes.
|
||||
func hexCases(t *testing.T, encoded string) func(func([]byte)) {
|
||||
return func(try func([]byte)) {
|
||||
for _, x := range strings.Fields(encoded) {
|
||||
src, err := hex.DecodeString(x)
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", x, err)
|
||||
}
|
||||
try(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testdataCases generates the test cases recorded in testdata/decode.txt.
|
||||
// It only uses the inputs; it ignores the answers recorded in that file.
|
||||
func testdataCases(t *testing.T) func(func([]byte)) {
|
||||
var codes [][]byte
|
||||
data, err := ioutil.ReadFile("testdata/decode.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
f := strings.Fields(line)[0]
|
||||
i := strings.Index(f, "|")
|
||||
if i < 0 {
|
||||
t.Errorf("parsing %q: missing | separator", f)
|
||||
continue
|
||||
}
|
||||
if i%2 != 0 {
|
||||
t.Errorf("parsing %q: misaligned | separator", f)
|
||||
}
|
||||
code, err := hex.DecodeString(f[:i] + f[i+1:])
|
||||
if err != nil {
|
||||
t.Errorf("parsing %q: %v", f, err)
|
||||
continue
|
||||
}
|
||||
codes = append(codes, code)
|
||||
}
|
||||
|
||||
return func(try func([]byte)) {
|
||||
for _, code := range codes {
|
||||
try(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func caller(skip int) string {
|
||||
pc, _, _, _ := runtime.Caller(skip)
|
||||
f := runtime.FuncForPC(pc)
|
||||
name := "?"
|
||||
if f != nil {
|
||||
name = f.Name()
|
||||
if i := strings.LastIndex(name, "."); i >= 0 {
|
||||
name = name[i+1:]
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var saveDot = strings.NewReplacer(
|
||||
".F16", "_dot_F16",
|
||||
".F32", "_dot_F32",
|
||||
".F64", "_dot_F64",
|
||||
".S32", "_dot_S32",
|
||||
".U32", "_dot_U32",
|
||||
".FXS", "_dot_S",
|
||||
".FXU", "_dot_U",
|
||||
".32", "_dot_32",
|
||||
)
|
||||
|
||||
// GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils.
|
||||
// This form typically matches the syntax defined in the ARM Reference Manual.
|
||||
func GNUSyntax(inst Inst) string {
|
||||
var buf bytes.Buffer
|
||||
op := inst.Op.String()
|
||||
op = saveDot.Replace(op)
|
||||
op = strings.Replace(op, ".", "", -1)
|
||||
op = strings.Replace(op, "_dot_", ".", -1)
|
||||
op = strings.ToLower(op)
|
||||
buf.WriteString(op)
|
||||
sep := " "
|
||||
for i, arg := range inst.Args {
|
||||
if arg == nil {
|
||||
break
|
||||
}
|
||||
text := gnuArg(&inst, i, arg)
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(sep)
|
||||
sep = ", "
|
||||
buf.WriteString(text)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func gnuArg(inst *Inst, argIndex int, arg Arg) string {
|
||||
switch inst.Op &^ 15 {
|
||||
case LDRD_EQ, LDREXD_EQ, STRD_EQ:
|
||||
if argIndex == 1 {
|
||||
// second argument in consecutive pair not printed
|
||||
return ""
|
||||
}
|
||||
case STREXD_EQ:
|
||||
if argIndex == 2 {
|
||||
// second argument in consecutive pair not printed
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
switch arg := arg.(type) {
|
||||
case Imm:
|
||||
switch inst.Op &^ 15 {
|
||||
case BKPT_EQ:
|
||||
return fmt.Sprintf("%#04x", uint32(arg))
|
||||
case SVC_EQ:
|
||||
return fmt.Sprintf("%#08x", uint32(arg))
|
||||
}
|
||||
return fmt.Sprintf("#%d", int32(arg))
|
||||
|
||||
case ImmAlt:
|
||||
return fmt.Sprintf("#%d, %d", arg.Val, arg.Rot)
|
||||
|
||||
case Mem:
|
||||
R := gnuArg(inst, -1, arg.Base)
|
||||
X := ""
|
||||
if arg.Sign != 0 {
|
||||
X = ""
|
||||
if arg.Sign < 0 {
|
||||
X = "-"
|
||||
}
|
||||
X += gnuArg(inst, -1, arg.Index)
|
||||
if arg.Shift == ShiftLeft && arg.Count == 0 {
|
||||
// nothing
|
||||
} else if arg.Shift == RotateRightExt {
|
||||
X += ", rrx"
|
||||
} else {
|
||||
X += fmt.Sprintf(", %s #%d", strings.ToLower(arg.Shift.String()), arg.Count)
|
||||
}
|
||||
} else {
|
||||
X = fmt.Sprintf("#%d", arg.Offset)
|
||||
}
|
||||
|
||||
switch arg.Mode {
|
||||
case AddrOffset:
|
||||
if X == "#0" {
|
||||
return fmt.Sprintf("[%s]", R)
|
||||
}
|
||||
return fmt.Sprintf("[%s, %s]", R, X)
|
||||
case AddrPreIndex:
|
||||
return fmt.Sprintf("[%s, %s]!", R, X)
|
||||
case AddrPostIndex:
|
||||
return fmt.Sprintf("[%s], %s", R, X)
|
||||
case AddrLDM:
|
||||
if X == "#0" {
|
||||
return R
|
||||
}
|
||||
case AddrLDM_WB:
|
||||
if X == "#0" {
|
||||
return R + "!"
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("[%s Mode(%d) %s]", R, int(arg.Mode), X)
|
||||
|
||||
case PCRel:
|
||||
return fmt.Sprintf(".%+#x", int32(arg)+4)
|
||||
|
||||
case Reg:
|
||||
switch inst.Op &^ 15 {
|
||||
case LDREX_EQ:
|
||||
if argIndex == 0 {
|
||||
return fmt.Sprintf("r%d", int32(arg))
|
||||
}
|
||||
}
|
||||
switch arg {
|
||||
case R10:
|
||||
return "sl"
|
||||
case R11:
|
||||
return "fp"
|
||||
case R12:
|
||||
return "ip"
|
||||
}
|
||||
|
||||
case RegList:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "{")
|
||||
sep := ""
|
||||
for i := 0; i < 16; i++ {
|
||||
if arg&(1<<uint(i)) != 0 {
|
||||
fmt.Fprintf(&buf, "%s%s", sep, gnuArg(inst, -1, Reg(i)))
|
||||
sep = ", "
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
|
||||
case RegShift:
|
||||
if arg.Shift == ShiftLeft && arg.Count == 0 {
|
||||
return gnuArg(inst, -1, arg.Reg)
|
||||
}
|
||||
if arg.Shift == RotateRightExt {
|
||||
return gnuArg(inst, -1, arg.Reg) + ", rrx"
|
||||
}
|
||||
return fmt.Sprintf("%s, %s #%d", gnuArg(inst, -1, arg.Reg), strings.ToLower(arg.Shift.String()), arg.Count)
|
||||
|
||||
case RegShiftReg:
|
||||
return fmt.Sprintf("%s, %s %s", gnuArg(inst, -1, arg.Reg), strings.ToLower(arg.Shift.String()), gnuArg(inst, -1, arg.RegCount))
|
||||
|
||||
}
|
||||
return strings.ToLower(arg.String())
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A Mode is an instruction execution mode.
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
_ Mode = iota
|
||||
ModeARM
|
||||
ModeThumb
|
||||
)
|
||||
|
||||
func (m Mode) String() string {
|
||||
switch m {
|
||||
case ModeARM:
|
||||
return "ARM"
|
||||
case ModeThumb:
|
||||
return "Thumb"
|
||||
}
|
||||
return fmt.Sprintf("Mode(%d)", int(m))
|
||||
}
|
||||
|
||||
// An Op is an ARM opcode.
|
||||
type Op uint16
|
||||
|
||||
// NOTE: The actual Op values are defined in tables.go.
|
||||
// They are chosen to simplify instruction decoding and
|
||||
// are not a dense packing from 0 to N, although the
|
||||
// density is high, probably at least 90%.
|
||||
|
||||
func (op Op) String() string {
|
||||
if op >= Op(len(opstr)) || opstr[op] == "" {
|
||||
return fmt.Sprintf("Op(%d)", int(op))
|
||||
}
|
||||
return opstr[op]
|
||||
}
|
||||
|
||||
// An Inst is a single instruction.
|
||||
type Inst struct {
|
||||
Op Op // Opcode mnemonic
|
||||
Enc uint32 // Raw encoding bits.
|
||||
Len int // Length of encoding in bytes.
|
||||
Args Args // Instruction arguments, in ARM manual order.
|
||||
}
|
||||
|
||||
func (i Inst) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(i.Op.String())
|
||||
for j, arg := range i.Args {
|
||||
if arg == nil {
|
||||
break
|
||||
}
|
||||
if j == 0 {
|
||||
buf.WriteString(" ")
|
||||
} else {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(arg.String())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// An Args holds the instruction arguments.
|
||||
// If an instruction has fewer than 4 arguments,
|
||||
// the final elements in the array are nil.
|
||||
type Args [4]Arg
|
||||
|
||||
// An Arg is a single instruction argument, one of these types:
|
||||
// Endian, Imm, Mem, PCRel, Reg, RegList, RegShift, RegShiftReg.
|
||||
type Arg interface {
|
||||
IsArg()
|
||||
String() string
|
||||
}
|
||||
|
||||
type Float32Imm float32
|
||||
|
||||
func (Float32Imm) IsArg() {}
|
||||
|
||||
func (f Float32Imm) String() string {
|
||||
return fmt.Sprintf("#%v", float32(f))
|
||||
}
|
||||
|
||||
type Float64Imm float32
|
||||
|
||||
func (Float64Imm) IsArg() {}
|
||||
|
||||
func (f Float64Imm) String() string {
|
||||
return fmt.Sprintf("#%v", float64(f))
|
||||
}
|
||||
|
||||
// An Imm is an integer constant.
|
||||
type Imm uint32
|
||||
|
||||
func (Imm) IsArg() {}
|
||||
|
||||
func (i Imm) String() string {
|
||||
return fmt.Sprintf("#%#x", uint32(i))
|
||||
}
|
||||
|
||||
// An ImmAlt is an alternate encoding of an integer constant.
|
||||
type ImmAlt struct {
|
||||
Val uint8
|
||||
Rot uint8
|
||||
}
|
||||
|
||||
func (ImmAlt) IsArg() {}
|
||||
|
||||
func (i ImmAlt) Imm() Imm {
|
||||
v := uint32(i.Val)
|
||||
r := uint(i.Rot)
|
||||
return Imm(v>>r | v<<(32-r))
|
||||
}
|
||||
|
||||
func (i ImmAlt) String() string {
|
||||
return fmt.Sprintf("#%#x, %d", i.Val, i.Rot)
|
||||
}
|
||||
|
||||
// A Label is a text (code) address.
|
||||
type Label uint32
|
||||
|
||||
func (Label) IsArg() {}
|
||||
|
||||
func (i Label) String() string {
|
||||
return fmt.Sprintf("%#x", uint32(i))
|
||||
}
|
||||
|
||||
// A Reg is a single register.
|
||||
// The zero value denotes R0, not the absence of a register.
|
||||
type Reg uint8
|
||||
|
||||
const (
|
||||
R0 Reg = iota
|
||||
R1
|
||||
R2
|
||||
R3
|
||||
R4
|
||||
R5
|
||||
R6
|
||||
R7
|
||||
R8
|
||||
R9
|
||||
R10
|
||||
R11
|
||||
R12
|
||||
R13
|
||||
R14
|
||||
R15
|
||||
|
||||
S0
|
||||
S1
|
||||
S2
|
||||
S3
|
||||
S4
|
||||
S5
|
||||
S6
|
||||
S7
|
||||
S8
|
||||
S9
|
||||
S10
|
||||
S11
|
||||
S12
|
||||
S13
|
||||
S14
|
||||
S15
|
||||
S16
|
||||
S17
|
||||
S18
|
||||
S19
|
||||
S20
|
||||
S21
|
||||
S22
|
||||
S23
|
||||
S24
|
||||
S25
|
||||
S26
|
||||
S27
|
||||
S28
|
||||
S29
|
||||
S30
|
||||
S31
|
||||
|
||||
D0
|
||||
D1
|
||||
D2
|
||||
D3
|
||||
D4
|
||||
D5
|
||||
D6
|
||||
D7
|
||||
D8
|
||||
D9
|
||||
D10
|
||||
D11
|
||||
D12
|
||||
D13
|
||||
D14
|
||||
D15
|
||||
D16
|
||||
D17
|
||||
D18
|
||||
D19
|
||||
D20
|
||||
D21
|
||||
D22
|
||||
D23
|
||||
D24
|
||||
D25
|
||||
D26
|
||||
D27
|
||||
D28
|
||||
D29
|
||||
D30
|
||||
D31
|
||||
|
||||
APSR
|
||||
APSR_nzcv
|
||||
FPSCR
|
||||
|
||||
SP = R13
|
||||
LR = R14
|
||||
PC = R15
|
||||
)
|
||||
|
||||
func (Reg) IsArg() {}
|
||||
|
||||
func (r Reg) String() string {
|
||||
switch r {
|
||||
case APSR:
|
||||
return "APSR"
|
||||
case APSR_nzcv:
|
||||
return "APSR_nzcv"
|
||||
case FPSCR:
|
||||
return "FPSCR"
|
||||
case SP:
|
||||
return "SP"
|
||||
case PC:
|
||||
return "PC"
|
||||
case LR:
|
||||
return "LR"
|
||||
}
|
||||
if R0 <= r && r <= R15 {
|
||||
return fmt.Sprintf("R%d", int(r-R0))
|
||||
}
|
||||
if S0 <= r && r <= S31 {
|
||||
return fmt.Sprintf("S%d", int(r-S0))
|
||||
}
|
||||
if D0 <= r && r <= D31 {
|
||||
return fmt.Sprintf("D%d", int(r-D0))
|
||||
}
|
||||
return fmt.Sprintf("Reg(%d)", int(r))
|
||||
}
|
||||
|
||||
// A RegX represents a fraction of a multi-value register.
|
||||
// The Index field specifies the index number,
|
||||
// but the size of the fraction is not specified.
|
||||
// It must be inferred from the instruction and the register type.
|
||||
// For example, in a VMOV instruction, RegX{D5, 1} represents
|
||||
// the top 32 bits of the 64-bit D5 register.
|
||||
type RegX struct {
|
||||
Reg Reg
|
||||
Index int
|
||||
}
|
||||
|
||||
func (RegX) IsArg() {}
|
||||
|
||||
func (r RegX) String() string {
|
||||
return fmt.Sprintf("%s[%d]", r.Reg, r.Index)
|
||||
}
|
||||
|
||||
// A RegList is a register list.
|
||||
// Bits at indexes x = 0 through 15 indicate whether the corresponding Rx register is in the list.
|
||||
type RegList uint16
|
||||
|
||||
func (RegList) IsArg() {}
|
||||
|
||||
func (r RegList) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "{")
|
||||
sep := ""
|
||||
for i := 0; i < 16; i++ {
|
||||
if r&(1<<uint(i)) != 0 {
|
||||
fmt.Fprintf(&buf, "%s%s", sep, Reg(i).String())
|
||||
sep = ","
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// An Endian is the argument to the SETEND instruction.
|
||||
type Endian uint8
|
||||
|
||||
const (
|
||||
LittleEndian Endian = 0
|
||||
BigEndian Endian = 1
|
||||
)
|
||||
|
||||
func (Endian) IsArg() {}
|
||||
|
||||
func (e Endian) String() string {
|
||||
if e != 0 {
|
||||
return "BE"
|
||||
}
|
||||
return "LE"
|
||||
}
|
||||
|
||||
// A Shift describes an ARM shift operation.
|
||||
type Shift uint8
|
||||
|
||||
const (
|
||||
ShiftLeft Shift = 0 // left shift
|
||||
ShiftRight Shift = 1 // logical (unsigned) right shift
|
||||
ShiftRightSigned Shift = 2 // arithmetic (signed) right shift
|
||||
RotateRight Shift = 3 // right rotate
|
||||
RotateRightExt Shift = 4 // right rotate through carry (Count will always be 1)
|
||||
)
|
||||
|
||||
var shiftName = [...]string{
|
||||
"LSL", "LSR", "ASR", "ROR", "RRX",
|
||||
}
|
||||
|
||||
func (s Shift) String() string {
|
||||
if s < 5 {
|
||||
return shiftName[s]
|
||||
}
|
||||
return fmt.Sprintf("Shift(%d)", int(s))
|
||||
}
|
||||
|
||||
// A RegShift is a register shifted by a constant.
|
||||
type RegShift struct {
|
||||
Reg Reg
|
||||
Shift Shift
|
||||
Count uint8
|
||||
}
|
||||
|
||||
func (RegShift) IsArg() {}
|
||||
|
||||
func (r RegShift) String() string {
|
||||
return fmt.Sprintf("%s %s #%d", r.Reg, r.Shift, r.Count)
|
||||
}
|
||||
|
||||
// A RegShiftReg is a register shifted by a register.
|
||||
type RegShiftReg struct {
|
||||
Reg Reg
|
||||
Shift Shift
|
||||
RegCount Reg
|
||||
}
|
||||
|
||||
func (RegShiftReg) IsArg() {}
|
||||
|
||||
func (r RegShiftReg) String() string {
|
||||
return fmt.Sprintf("%s %s %s", r.Reg, r.Shift, r.RegCount)
|
||||
}
|
||||
|
||||
// A PCRel describes a memory address (usually a code label)
|
||||
// as a distance relative to the program counter.
|
||||
// TODO(rsc): Define which program counter (PC+4? PC+8? PC?).
|
||||
type PCRel int32
|
||||
|
||||
func (PCRel) IsArg() {}
|
||||
|
||||
func (r PCRel) String() string {
|
||||
return fmt.Sprintf("PC%+#x", int32(r))
|
||||
}
|
||||
|
||||
// An AddrMode is an ARM addressing mode.
|
||||
type AddrMode uint8
|
||||
|
||||
const (
|
||||
_ AddrMode = iota
|
||||
AddrPostIndex // [R], X – use address R, set R = R + X
|
||||
AddrPreIndex // [R, X]! – use address R + X, set R = R + X
|
||||
AddrOffset // [R, X] – use address R + X
|
||||
AddrLDM // R – [R] but formats as R, for LDM/STM only
|
||||
AddrLDM_WB // R! - [R], X where X is instruction-specific amount, for LDM/STM only
|
||||
)
|
||||
|
||||
// A Mem is a memory reference made up of a base R and index expression X.
|
||||
// The effective memory address is R or R+X depending on AddrMode.
|
||||
// The index expression is X = Sign*(Index Shift Count) + Offset,
|
||||
// but in any instruction either Sign = 0 or Offset = 0.
|
||||
type Mem struct {
|
||||
Base Reg
|
||||
Mode AddrMode
|
||||
Sign int8
|
||||
Index Reg
|
||||
Shift Shift
|
||||
Count uint8
|
||||
Offset int16
|
||||
}
|
||||
|
||||
func (Mem) IsArg() {}
|
||||
|
||||
func (m Mem) String() string {
|
||||
R := m.Base.String()
|
||||
X := ""
|
||||
if m.Sign != 0 {
|
||||
X = "+"
|
||||
if m.Sign < 0 {
|
||||
X = "-"
|
||||
}
|
||||
X += m.Index.String()
|
||||
if m.Shift != ShiftLeft || m.Count != 0 {
|
||||
X += fmt.Sprintf(", %s #%d", m.Shift, m.Count)
|
||||
}
|
||||
} else {
|
||||
X = fmt.Sprintf("#%d", m.Offset)
|
||||
}
|
||||
|
||||
switch m.Mode {
|
||||
case AddrOffset:
|
||||
if X == "#0" {
|
||||
return fmt.Sprintf("[%s]", R)
|
||||
}
|
||||
return fmt.Sprintf("[%s, %s]", R, X)
|
||||
case AddrPreIndex:
|
||||
return fmt.Sprintf("[%s, %s]!", R, X)
|
||||
case AddrPostIndex:
|
||||
return fmt.Sprintf("[%s], %s", R, X)
|
||||
case AddrLDM:
|
||||
if X == "#0" {
|
||||
return R
|
||||
}
|
||||
case AddrLDM_WB:
|
||||
if X == "#0" {
|
||||
return R + "!"
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("[%s Mode(%d) %s]", R, int(m.Mode), X)
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjdumpARMTestdata(t *testing.T) { testObjdumpARM(t, testdataCases(t)) }
|
||||
func TestObjdumpARMManual(t *testing.T) { testObjdumpARM(t, hexCases(t, objdumpManualTests)) }
|
||||
func TestObjdumpARMCond(t *testing.T) { testObjdumpARM(t, condCases(t)) }
|
||||
func TestObjdumpARMUncond(t *testing.T) { testObjdumpARM(t, uncondCases(t)) }
|
||||
func TestObjdumpARMVFP(t *testing.T) { testObjdumpARM(t, vfpCases(t)) }
|
||||
|
||||
// objdumpManualTests holds test cases that will be run by TestObjdumpARMManual.
|
||||
// If you are debugging a few cases that turned up in a longer run, it can be useful
|
||||
// to list them here and then use -run=Manual, particularly with tracing enabled.
|
||||
// Note that these are byte sequences, so they must be reversed from the usual
|
||||
// word presentation.
|
||||
var objdumpManualTests = `
|
||||
002a9b1d
|
||||
001b9bed
|
||||
020b8ded
|
||||
003a9b1d
|
||||
060b8ded
|
||||
fcde1100
|
||||
b4de1100
|
||||
bc480000
|
||||
0b008de7
|
||||
0b00ade7
|
||||
fdbcfaf7
|
||||
`
|
||||
|
||||
// allowedMismatchObjdump reports whether the mismatch between text and dec
|
||||
// should be allowed by the test.
|
||||
func allowedMismatchObjdump(text string, size int, inst *Inst, dec ExtInst) bool {
|
||||
if hasPrefix(text, "error:") {
|
||||
if hasPrefix(dec.text, unsupported...) || strings.Contains(dec.text, "invalid:") || strings.HasSuffix(dec.text, "^") || strings.Contains(dec.text, "f16.f64") || strings.Contains(dec.text, "f64.f16") {
|
||||
return true
|
||||
}
|
||||
// word 4320F02C: libopcodes says 'nopmi {44}'.
|
||||
if hasPrefix(dec.text, "nop") && strings.Contains(dec.text, "{") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if hasPrefix(dec.text, "error:") && text == "undef" && inst.Enc == 0xf7fabcfd {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 00f02053: libopcodes says 'noppl {0}'.
|
||||
if hasPrefix(dec.text, "nop") && hasPrefix(text, "nop") && dec.text == text+" {0}" {
|
||||
return true
|
||||
}
|
||||
|
||||
// word F57FF04F. we say 'dsb #15', libopcodes says 'dsb sy'.
|
||||
if hasPrefix(text, "dsb") && hasPrefix(dec.text, "dsb") {
|
||||
return true
|
||||
}
|
||||
// word F57FF06F. we say 'isb #15', libopcodes says 'isb sy'.
|
||||
if hasPrefix(text, "isb") && hasPrefix(dec.text, "isb") {
|
||||
return true
|
||||
}
|
||||
// word F57FF053. we say 'dmb #3', libopcodes says 'dmb osh'.
|
||||
if hasPrefix(text, "dmb") && hasPrefix(dec.text, "dmb") {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 992D0000. push/stmdb with no registers (undefined).
|
||||
// we say 'stmdbls sp!, {}', libopcodes says 'pushls {}'.
|
||||
if hasPrefix(text, "stmdb") && hasPrefix(dec.text, "push") && strings.Contains(text, "{}") && strings.Contains(dec.text, "{}") {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 28BD0000. pop/ldm with no registers (undefined).
|
||||
// we say 'ldmcs sp!, {}', libopcodes says 'popcs {}'.
|
||||
if hasPrefix(text, "ldm") && hasPrefix(dec.text, "pop") && strings.Contains(text, "{}") && strings.Contains(dec.text, "{}") {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 014640F0.
|
||||
// libopcodes emits #-0 for negative zero; we don't.
|
||||
if strings.Replace(dec.text, "#-0", "#0", -1) == text || strings.Replace(dec.text, ", #-0", "", -1) == text {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 91EF90F0. we say 'strdls r9, [pc, #0]!' but libopcodes says 'strdls r9, [pc]'.
|
||||
// word D16F60F0. we say 'strdle r6, [pc, #0]!' but libopcodes says 'strdle r6, [pc, #-0]'.
|
||||
if strings.Replace(text, ", #0]!", "]", -1) == strings.Replace(dec.text, ", #-0]", "]", -1) {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 510F4000. we say apsr, libopcodes says CPSR.
|
||||
if strings.Replace(dec.text, "CPSR", "apsr", -1) == text {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 06A4B059.
|
||||
// for ssat and usat, libopcodes decodes asr #0 as asr #0 but the manual seems to say it should be asr #32.
|
||||
// There is never an asr #0.
|
||||
if strings.Replace(dec.text, ", asr #0", ", asr #32", -1) == text {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(dec.enc) >= 4 {
|
||||
raw := binary.LittleEndian.Uint32(dec.enc[:4])
|
||||
|
||||
// word 21FFF0B5.
|
||||
// the manual is clear that this is pre-indexed mode (with !) but libopcodes generates post-index (without !).
|
||||
if raw&0x01200000 == 0x01200000 && strings.Replace(text, "!", "", -1) == dec.text {
|
||||
return true
|
||||
}
|
||||
|
||||
// word C100543E: libopcodes says tst, but no evidence for that.
|
||||
if strings.HasPrefix(dec.text, "tst") && raw&0x0ff00000 != 0x03100000 && raw&0x0ff00000 != 0x01100000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word C3203CE8: libopcodes says teq, but no evidence for that.
|
||||
if strings.HasPrefix(dec.text, "teq") && raw&0x0ff00000 != 0x03300000 && raw&0x0ff00000 != 0x01300000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word D14C552E: libopcodes says cmp but no evidence for that.
|
||||
if strings.HasPrefix(dec.text, "cmp") && raw&0x0ff00000 != 0x03500000 && raw&0x0ff00000 != 0x01500000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word 2166AA4A: libopcodes says cmn but no evidence for that.
|
||||
if strings.HasPrefix(dec.text, "cmn") && raw&0x0ff00000 != 0x03700000 && raw&0x0ff00000 != 0x01700000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word E70AEEEF: libopcodes says str but no evidence for that.
|
||||
if strings.HasPrefix(dec.text, "str") && len(dec.text) >= 5 && (dec.text[3] == ' ' || dec.text[5] == ' ') && raw&0x0e500018 != 0x06000000 && raw&0x0e500000 != 0x0400000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word B0AF48F4: libopcodes says strd but P=0,W=1 which is unpredictable.
|
||||
if hasPrefix(dec.text, "ldr", "str") && raw&0x01200000 == 0x00200000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word B6CC1C76: libopcodes inexplicably says 'uxtab16lt r1, ip, r6, ROR #24' instead of 'uxtab16lt r1, ip, r6, ror #24'
|
||||
if strings.ToLower(dec.text) == text {
|
||||
return true
|
||||
}
|
||||
|
||||
// word F410FDA1: libopcodes says PLDW but the manual is clear that PLDW is F5/F7, not F4.
|
||||
// word F7D0FB17: libopcodes says PLDW but the manual is clear that PLDW has 0x10 clear
|
||||
if hasPrefix(dec.text, "pld") && raw&0xfd000010 != 0xf5000000 {
|
||||
return true
|
||||
}
|
||||
|
||||
// word F650FE14: libopcodes says PLI but the manual is clear that PLI has 0x10 clear
|
||||
if hasPrefix(dec.text, "pli") && raw&0xff000010 != 0xf6000000 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Instructions known to libopcodes (or xed) but not to us.
|
||||
// Most of these are floating point coprocessor instructions.
|
||||
var unsupported = strings.Fields(`
|
||||
abs
|
||||
acs
|
||||
adf
|
||||
aes
|
||||
asn
|
||||
atn
|
||||
cdp
|
||||
cf
|
||||
cmf
|
||||
cnf
|
||||
cos
|
||||
cps
|
||||
crc32
|
||||
dvf
|
||||
eret
|
||||
exp
|
||||
fadd
|
||||
fcmp
|
||||
fcpy
|
||||
fcvt
|
||||
fdiv
|
||||
fdv
|
||||
fix
|
||||
fld
|
||||
flt
|
||||
fmac
|
||||
fmd
|
||||
fml
|
||||
fmr
|
||||
fms
|
||||
fmul
|
||||
fmx
|
||||
fneg
|
||||
fnm
|
||||
frd
|
||||
fsit
|
||||
fsq
|
||||
fst
|
||||
fsu
|
||||
fto
|
||||
fui
|
||||
hlt
|
||||
hvc
|
||||
lda
|
||||
ldc
|
||||
ldf
|
||||
lfm
|
||||
lgn
|
||||
log
|
||||
mar
|
||||
mcr
|
||||
mcrr
|
||||
mia
|
||||
mnf
|
||||
mra
|
||||
mrc
|
||||
mrrc
|
||||
mrs
|
||||
msr
|
||||
msr
|
||||
muf
|
||||
mvf
|
||||
nrm
|
||||
pol
|
||||
pow
|
||||
rdf
|
||||
rfc
|
||||
rfe
|
||||
rfs
|
||||
rmf
|
||||
rnd
|
||||
rpw
|
||||
rsf
|
||||
sdiv
|
||||
sev
|
||||
sfm
|
||||
sha1
|
||||
sha256
|
||||
sin
|
||||
smc
|
||||
sqt
|
||||
srs
|
||||
stc
|
||||
stf
|
||||
stl
|
||||
suf
|
||||
tan
|
||||
udf
|
||||
udiv
|
||||
urd
|
||||
vfma
|
||||
vfms
|
||||
vfnma
|
||||
vfnms
|
||||
vrint
|
||||
wfc
|
||||
wfs
|
||||
`)
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copied and simplified from ../../x86/x86asm/objdumpext_test.go.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const objdumpPath = "/usr/local/bin/arm-linux-elf-objdump"
|
||||
|
||||
func testObjdumpARM(t *testing.T, generate func(func([]byte))) {
|
||||
testObjdumpArch(t, generate, ModeARM)
|
||||
}
|
||||
|
||||
func testObjdumpArch(t *testing.T, generate func(func([]byte)), arch Mode) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping objdump test in short mode")
|
||||
}
|
||||
if _, err := os.Stat(objdumpPath); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
testExtDis(t, "gnu", arch, objdump, generate, allowedMismatchObjdump)
|
||||
}
|
||||
|
||||
func objdump(ext *ExtDis) error {
|
||||
// File already written with instructions; add ELF header.
|
||||
if ext.Arch == ModeARM {
|
||||
if err := writeELF32(ext.File, ext.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
panic("unknown arch")
|
||||
}
|
||||
|
||||
b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
nmatch int
|
||||
reading bool
|
||||
next uint32 = start
|
||||
addr uint32
|
||||
encbuf [4]byte
|
||||
enc []byte
|
||||
text string
|
||||
)
|
||||
flush := func() {
|
||||
if addr == next {
|
||||
if m := pcrel.FindStringSubmatch(text); m != nil {
|
||||
targ, _ := strconv.ParseUint(m[2], 16, 64)
|
||||
text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc))))
|
||||
}
|
||||
if strings.HasPrefix(text, "stmia") {
|
||||
text = "stm" + text[5:]
|
||||
}
|
||||
if strings.HasPrefix(text, "stmfd") {
|
||||
text = "stmdb" + text[5:]
|
||||
}
|
||||
if strings.HasPrefix(text, "ldmfd") {
|
||||
text = "ldm" + text[5:]
|
||||
}
|
||||
text = strings.Replace(text, "#0.0", "#0", -1)
|
||||
if text == "undefined" && len(enc) == 4 {
|
||||
text = "error: unknown instruction"
|
||||
enc = nil
|
||||
}
|
||||
if len(enc) == 4 {
|
||||
// prints as word but we want to record bytes
|
||||
enc[0], enc[3] = enc[3], enc[0]
|
||||
enc[1], enc[2] = enc[2], enc[1]
|
||||
}
|
||||
ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
|
||||
encbuf = [4]byte{}
|
||||
enc = nil
|
||||
next += 4
|
||||
}
|
||||
}
|
||||
var textangle = []byte("<.text>:")
|
||||
for {
|
||||
line, err := b.ReadSlice('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("reading objdump output: %v", err)
|
||||
}
|
||||
if bytes.Contains(line, textangle) {
|
||||
reading = true
|
||||
continue
|
||||
}
|
||||
if !reading {
|
||||
continue
|
||||
}
|
||||
if debug {
|
||||
os.Stdout.Write(line)
|
||||
}
|
||||
if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil {
|
||||
enc = enc1
|
||||
continue
|
||||
}
|
||||
flush()
|
||||
nmatch++
|
||||
addr, enc, text = parseLine(line, encbuf[:0])
|
||||
if addr > next {
|
||||
return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
|
||||
}
|
||||
}
|
||||
flush()
|
||||
if next != start+uint32(ext.Size) {
|
||||
return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
|
||||
}
|
||||
if err := ext.Wait(); err != nil {
|
||||
return fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
undefined = []byte("<UNDEFINED>")
|
||||
unpredictable = []byte("<UNPREDICTABLE>")
|
||||
illegalShifter = []byte("<illegal shifter operand>")
|
||||
)
|
||||
|
||||
func parseLine(line []byte, encstart []byte) (addr uint32, enc []byte, text string) {
|
||||
oline := line
|
||||
i := index(line, ":\t")
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
x, err := strconv.ParseUint(string(trimSpace(line[:i])), 16, 32)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
addr = uint32(x)
|
||||
line = line[i+2:]
|
||||
i = bytes.IndexByte(line, '\t')
|
||||
if i < 0 {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
enc, ok := parseHex(line[:i], encstart)
|
||||
if !ok {
|
||||
log.Fatalf("cannot parse disassembly: %q", oline)
|
||||
}
|
||||
line = trimSpace(line[i:])
|
||||
if bytes.Contains(line, undefined) {
|
||||
text = "undefined"
|
||||
return
|
||||
}
|
||||
if bytes.Contains(line, illegalShifter) {
|
||||
text = "undefined"
|
||||
return
|
||||
}
|
||||
if false && bytes.Contains(line, unpredictable) {
|
||||
text = "unpredictable"
|
||||
return
|
||||
}
|
||||
if i := bytes.IndexByte(line, ';'); i >= 0 {
|
||||
line = trimSpace(line[:i])
|
||||
}
|
||||
text = string(fixSpace(line))
|
||||
return
|
||||
}
|
||||
|
||||
func parseContinuation(line []byte, enc []byte) []byte {
|
||||
i := index(line, ":\t")
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
line = line[i+1:]
|
||||
enc, _ = parseHex(line, enc)
|
||||
return enc
|
||||
}
|
||||
|
||||
// writeELF32 writes an ELF32 header to the file,
|
||||
// describing a text segment that starts at start
|
||||
// and extends for size bytes.
|
||||
func writeELF32(f *os.File, size int) error {
|
||||
f.Seek(0, io.SeekStart)
|
||||
var hdr elf.Header32
|
||||
var prog elf.Prog32
|
||||
var sect elf.Section32
|
||||
var buf bytes.Buffer
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
off1 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
off2 := buf.Len()
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
off3 := buf.Len()
|
||||
buf.Reset()
|
||||
data := byte(elf.ELFDATA2LSB)
|
||||
hdr = elf.Header32{
|
||||
Ident: [16]byte{0x7F, 'E', 'L', 'F', 1, data, 1},
|
||||
Type: 2,
|
||||
Machine: uint16(elf.EM_ARM),
|
||||
Version: 1,
|
||||
Entry: start,
|
||||
Phoff: uint32(off1),
|
||||
Shoff: uint32(off2),
|
||||
Flags: 0x05000002,
|
||||
Ehsize: uint16(off1),
|
||||
Phentsize: uint16(off2 - off1),
|
||||
Phnum: 1,
|
||||
Shentsize: uint16(off3 - off2),
|
||||
Shnum: 3,
|
||||
Shstrndx: 2,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &hdr)
|
||||
prog = elf.Prog32{
|
||||
Type: 1,
|
||||
Off: start,
|
||||
Vaddr: start,
|
||||
Paddr: start,
|
||||
Filesz: uint32(size),
|
||||
Memsz: uint32(size),
|
||||
Flags: 5,
|
||||
Align: start,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, &prog)
|
||||
binary.Write(&buf, binary.LittleEndian, §) // NULL section
|
||||
sect = elf.Section32{
|
||||
Name: 1,
|
||||
Type: uint32(elf.SHT_PROGBITS),
|
||||
Addr: start,
|
||||
Off: start,
|
||||
Size: uint32(size),
|
||||
Flags: uint32(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
|
||||
Addralign: 4,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §) // .text
|
||||
sect = elf.Section32{
|
||||
Name: uint32(len("\x00.text\x00")),
|
||||
Type: uint32(elf.SHT_STRTAB),
|
||||
Addr: 0,
|
||||
Off: uint32(off2 + (off3-off2)*3),
|
||||
Size: uint32(len("\x00.text\x00.shstrtab\x00")),
|
||||
Addralign: 1,
|
||||
}
|
||||
binary.Write(&buf, binary.LittleEndian, §)
|
||||
buf.WriteString("\x00.text\x00.shstrtab\x00")
|
||||
f.Write(buf.Bytes())
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package armasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GoSyntax returns the Go assembler syntax for the instruction.
|
||||
// The syntax was originally defined by Plan 9.
|
||||
// The pc is the program counter of the instruction, used for expanding
|
||||
// PC-relative addresses into absolute ones.
|
||||
// The symname function queries the symbol table for the program
|
||||
// being disassembled. Given a target address it returns the name and base
|
||||
// address of the symbol containing the target, if any; otherwise it returns "", 0.
|
||||
// The reader r should read from the text segment using text addresses
|
||||
// as offsets; it is used to display pc-relative loads as constant loads.
|
||||
func GoSyntax(inst Inst, pc uint64, symname func(uint64) (string, uint64), text io.ReaderAt) string {
|
||||
if symname == nil {
|
||||
symname = func(uint64) (string, uint64) { return "", 0 }
|
||||
}
|
||||
|
||||
var args []string
|
||||
for _, a := range inst.Args {
|
||||
if a == nil {
|
||||
break
|
||||
}
|
||||
args = append(args, plan9Arg(&inst, pc, symname, a))
|
||||
}
|
||||
|
||||
op := inst.Op.String()
|
||||
|
||||
switch inst.Op &^ 15 {
|
||||
case LDR_EQ, LDRB_EQ, LDRH_EQ, LDRSB_EQ, LDRSH_EQ, VLDR_EQ:
|
||||
// Check for RET
|
||||
reg, _ := inst.Args[0].(Reg)
|
||||
mem, _ := inst.Args[1].(Mem)
|
||||
if inst.Op&^15 == LDR_EQ && reg == R15 && mem.Base == SP && mem.Sign == 0 && mem.Mode == AddrPostIndex {
|
||||
return fmt.Sprintf("RET%s #%d", op[3:], mem.Offset)
|
||||
}
|
||||
|
||||
// Check for PC-relative load.
|
||||
if mem.Base == PC && mem.Sign == 0 && mem.Mode == AddrOffset && text != nil {
|
||||
addr := uint32(pc) + 8 + uint32(mem.Offset)
|
||||
buf := make([]byte, 8)
|
||||
switch inst.Op &^ 15 {
|
||||
case LDRB_EQ, LDRSB_EQ:
|
||||
if _, err := text.ReadAt(buf[:1], int64(addr)); err != nil {
|
||||
break
|
||||
}
|
||||
args[1] = fmt.Sprintf("$%#x", buf[0])
|
||||
|
||||
case LDRH_EQ, LDRSH_EQ:
|
||||
if _, err := text.ReadAt(buf[:2], int64(addr)); err != nil {
|
||||
break
|
||||
}
|
||||
args[1] = fmt.Sprintf("$%#x", binary.LittleEndian.Uint16(buf))
|
||||
|
||||
case LDR_EQ:
|
||||
if _, err := text.ReadAt(buf[:4], int64(addr)); err != nil {
|
||||
break
|
||||
}
|
||||
x := binary.LittleEndian.Uint32(buf)
|
||||
if s, base := symname(uint64(x)); s != "" && uint64(x) == base {
|
||||
args[1] = fmt.Sprintf("$%s(SB)", s)
|
||||
} else {
|
||||
args[1] = fmt.Sprintf("$%#x", x)
|
||||
}
|
||||
|
||||
case VLDR_EQ:
|
||||
switch {
|
||||
case strings.HasPrefix(args[0], "D"): // VLDR.F64
|
||||
if _, err := text.ReadAt(buf, int64(addr)); err != nil {
|
||||
break
|
||||
}
|
||||
args[1] = fmt.Sprintf("$%f", math.Float64frombits(binary.LittleEndian.Uint64(buf)))
|
||||
case strings.HasPrefix(args[0], "S"): // VLDR.F32
|
||||
if _, err := text.ReadAt(buf[:4], int64(addr)); err != nil {
|
||||
break
|
||||
}
|
||||
args[1] = fmt.Sprintf("$%f", math.Float32frombits(binary.LittleEndian.Uint32(buf)))
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong FP register: %v", inst))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move addressing mode into opcode suffix.
|
||||
suffix := ""
|
||||
switch inst.Op &^ 15 {
|
||||
case PLD, PLI, PLD_W:
|
||||
if mem, ok := inst.Args[0].(Mem); ok {
|
||||
args[0], suffix = memOpTrans(mem)
|
||||
} else {
|
||||
panic(fmt.Sprintf("illegal instruction: %v", inst))
|
||||
}
|
||||
case LDR_EQ, LDRB_EQ, LDRSB_EQ, LDRH_EQ, LDRSH_EQ, STR_EQ, STRB_EQ, STRH_EQ, VLDR_EQ, VSTR_EQ, LDREX_EQ, LDREXH_EQ, LDREXB_EQ:
|
||||
if mem, ok := inst.Args[1].(Mem); ok {
|
||||
args[1], suffix = memOpTrans(mem)
|
||||
} else {
|
||||
panic(fmt.Sprintf("illegal instruction: %v", inst))
|
||||
}
|
||||
case SWP_EQ, SWP_B_EQ, STREX_EQ, STREXB_EQ, STREXH_EQ:
|
||||
if mem, ok := inst.Args[2].(Mem); ok {
|
||||
args[2], suffix = memOpTrans(mem)
|
||||
} else {
|
||||
panic(fmt.Sprintf("illegal instruction: %v", inst))
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse args, placing dest last.
|
||||
for i, j := 0, len(args)-1; i < j; i, j = i+1, j-1 {
|
||||
args[i], args[j] = args[j], args[i]
|
||||
}
|
||||
// For MLA-like instructions, the addend is the third operand.
|
||||
switch inst.Op &^ 15 {
|
||||
case SMLAWT_EQ, SMLAWB_EQ, MLA_EQ, MLA_S_EQ, MLS_EQ, SMMLA_EQ, SMMLS_EQ, SMLABB_EQ, SMLATB_EQ, SMLABT_EQ, SMLATT_EQ, SMLAD_EQ, SMLAD_X_EQ, SMLSD_EQ, SMLSD_X_EQ:
|
||||
args = []string{args[1], args[2], args[0], args[3]}
|
||||
}
|
||||
// For STREX like instructions, the memory operands comes first.
|
||||
switch inst.Op &^ 15 {
|
||||
case STREX_EQ, STREXB_EQ, STREXH_EQ, SWP_EQ, SWP_B_EQ:
|
||||
args = []string{args[1], args[0], args[2]}
|
||||
}
|
||||
|
||||
// special process for FP instructions
|
||||
op, args = fpTrans(&inst, op, args)
|
||||
|
||||
// LDR/STR like instructions -> MOV like
|
||||
switch inst.Op &^ 15 {
|
||||
case MOV_EQ:
|
||||
op = "MOVW" + op[3:]
|
||||
case LDR_EQ, MSR_EQ, MRS_EQ:
|
||||
op = "MOVW" + op[3:] + suffix
|
||||
case VMRS_EQ, VMSR_EQ:
|
||||
op = "MOVW" + op[4:] + suffix
|
||||
case LDRB_EQ, UXTB_EQ:
|
||||
op = "MOVBU" + op[4:] + suffix
|
||||
case LDRSB_EQ:
|
||||
op = "MOVBS" + op[5:] + suffix
|
||||
case SXTB_EQ:
|
||||
op = "MOVBS" + op[4:] + suffix
|
||||
case LDRH_EQ, UXTH_EQ:
|
||||
op = "MOVHU" + op[4:] + suffix
|
||||
case LDRSH_EQ:
|
||||
op = "MOVHS" + op[5:] + suffix
|
||||
case SXTH_EQ:
|
||||
op = "MOVHS" + op[4:] + suffix
|
||||
case STR_EQ:
|
||||
op = "MOVW" + op[3:] + suffix
|
||||
args[0], args[1] = args[1], args[0]
|
||||
case STRB_EQ:
|
||||
op = "MOVB" + op[4:] + suffix
|
||||
args[0], args[1] = args[1], args[0]
|
||||
case STRH_EQ:
|
||||
op = "MOVH" + op[4:] + suffix
|
||||
args[0], args[1] = args[1], args[0]
|
||||
case VSTR_EQ:
|
||||
args[0], args[1] = args[1], args[0]
|
||||
default:
|
||||
op = op + suffix
|
||||
}
|
||||
|
||||
if args != nil {
|
||||
op += " " + strings.Join(args, ", ")
|
||||
}
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
// assembler syntax for the various shifts.
|
||||
// @x> is a lie; the assembler uses @> 0
|
||||
// instead of @x> 1, but i wanted to be clear that it
|
||||
// was a different operation (rotate right extended, not rotate right).
|
||||
var plan9Shift = []string{"<<", ">>", "->", "@>", "@x>"}
|
||||
|
||||
func plan9Arg(inst *Inst, pc uint64, symname func(uint64) (string, uint64), arg Arg) string {
|
||||
switch a := arg.(type) {
|
||||
case Endian:
|
||||
|
||||
case Imm:
|
||||
return fmt.Sprintf("$%d", uint32(a))
|
||||
|
||||
case Mem:
|
||||
|
||||
case PCRel:
|
||||
addr := uint32(pc) + 8 + uint32(a)
|
||||
if s, base := symname(uint64(addr)); s != "" && uint64(addr) == base {
|
||||
return fmt.Sprintf("%s(SB)", s)
|
||||
}
|
||||
return fmt.Sprintf("%#x", addr)
|
||||
|
||||
case Reg:
|
||||
if a < 16 {
|
||||
return fmt.Sprintf("R%d", int(a))
|
||||
}
|
||||
|
||||
case RegList:
|
||||
var buf bytes.Buffer
|
||||
start := -2
|
||||
end := -2
|
||||
fmt.Fprintf(&buf, "[")
|
||||
flush := func() {
|
||||
if start >= 0 {
|
||||
if buf.Len() > 1 {
|
||||
fmt.Fprintf(&buf, ",")
|
||||
}
|
||||
if start == end {
|
||||
fmt.Fprintf(&buf, "R%d", start)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "R%d-R%d", start, end)
|
||||
}
|
||||
start = -2
|
||||
end = -2
|
||||
}
|
||||
}
|
||||
for i := 0; i < 16; i++ {
|
||||
if a&(1<<uint(i)) != 0 {
|
||||
if i == end+1 {
|
||||
end++
|
||||
continue
|
||||
}
|
||||
start = i
|
||||
end = i
|
||||
} else {
|
||||
flush()
|
||||
}
|
||||
}
|
||||
flush()
|
||||
fmt.Fprintf(&buf, "]")
|
||||
return buf.String()
|
||||
|
||||
case RegShift:
|
||||
return fmt.Sprintf("R%d%s$%d", int(a.Reg), plan9Shift[a.Shift], int(a.Count))
|
||||
|
||||
case RegShiftReg:
|
||||
return fmt.Sprintf("R%d%sR%d", int(a.Reg), plan9Shift[a.Shift], int(a.RegCount))
|
||||
}
|
||||
return strings.ToUpper(arg.String())
|
||||
}
|
||||
|
||||
// convert memory operand from GNU syntax to Plan 9 syntax, for example,
|
||||
// [r5] -> (R5)
|
||||
// [r6, #4080] -> 0xff0(R6)
|
||||
// [r2, r0, ror #1] -> (R2)(R0@>1)
|
||||
// inst [r2, -r0, ror #1] -> INST.U (R2)(R0@>1)
|
||||
// input:
|
||||
//
|
||||
// a memory operand
|
||||
//
|
||||
// return values:
|
||||
//
|
||||
// corresponding memory operand in Plan 9 syntax
|
||||
// .W/.P/.U suffix
|
||||
func memOpTrans(mem Mem) (string, string) {
|
||||
suffix := ""
|
||||
switch mem.Mode {
|
||||
case AddrOffset, AddrLDM:
|
||||
// no suffix
|
||||
case AddrPreIndex, AddrLDM_WB:
|
||||
suffix = ".W"
|
||||
case AddrPostIndex:
|
||||
suffix = ".P"
|
||||
}
|
||||
off := ""
|
||||
if mem.Offset != 0 {
|
||||
off = fmt.Sprintf("%#x", mem.Offset)
|
||||
}
|
||||
base := fmt.Sprintf("(R%d)", int(mem.Base))
|
||||
index := ""
|
||||
if mem.Sign != 0 {
|
||||
sign := ""
|
||||
if mem.Sign < 0 {
|
||||
suffix += ".U"
|
||||
}
|
||||
shift := ""
|
||||
if mem.Count != 0 {
|
||||
shift = fmt.Sprintf("%s%d", plan9Shift[mem.Shift], mem.Count)
|
||||
}
|
||||
index = fmt.Sprintf("(%sR%d%s)", sign, int(mem.Index), shift)
|
||||
}
|
||||
return off + base + index, suffix
|
||||
}
|
||||
|
||||
type goFPInfo struct {
|
||||
op Op
|
||||
transArgs []int // indexes of arguments which need transformation
|
||||
gnuName string // instruction name in GNU syntax
|
||||
goName string // instruction name in Plan 9 syntax
|
||||
}
|
||||
|
||||
var fpInst []goFPInfo = []goFPInfo{
|
||||
{VADD_EQ_F32, []int{2, 1, 0}, "VADD", "ADDF"},
|
||||
{VADD_EQ_F64, []int{2, 1, 0}, "VADD", "ADDD"},
|
||||
{VSUB_EQ_F32, []int{2, 1, 0}, "VSUB", "SUBF"},
|
||||
{VSUB_EQ_F64, []int{2, 1, 0}, "VSUB", "SUBD"},
|
||||
{VMUL_EQ_F32, []int{2, 1, 0}, "VMUL", "MULF"},
|
||||
{VMUL_EQ_F64, []int{2, 1, 0}, "VMUL", "MULD"},
|
||||
{VNMUL_EQ_F32, []int{2, 1, 0}, "VNMUL", "NMULF"},
|
||||
{VNMUL_EQ_F64, []int{2, 1, 0}, "VNMUL", "NMULD"},
|
||||
{VMLA_EQ_F32, []int{2, 1, 0}, "VMLA", "MULAF"},
|
||||
{VMLA_EQ_F64, []int{2, 1, 0}, "VMLA", "MULAD"},
|
||||
{VMLS_EQ_F32, []int{2, 1, 0}, "VMLS", "MULSF"},
|
||||
{VMLS_EQ_F64, []int{2, 1, 0}, "VMLS", "MULSD"},
|
||||
{VNMLA_EQ_F32, []int{2, 1, 0}, "VNMLA", "NMULAF"},
|
||||
{VNMLA_EQ_F64, []int{2, 1, 0}, "VNMLA", "NMULAD"},
|
||||
{VNMLS_EQ_F32, []int{2, 1, 0}, "VNMLS", "NMULSF"},
|
||||
{VNMLS_EQ_F64, []int{2, 1, 0}, "VNMLS", "NMULSD"},
|
||||
{VDIV_EQ_F32, []int{2, 1, 0}, "VDIV", "DIVF"},
|
||||
{VDIV_EQ_F64, []int{2, 1, 0}, "VDIV", "DIVD"},
|
||||
{VNEG_EQ_F32, []int{1, 0}, "VNEG", "NEGF"},
|
||||
{VNEG_EQ_F64, []int{1, 0}, "VNEG", "NEGD"},
|
||||
{VABS_EQ_F32, []int{1, 0}, "VABS", "ABSF"},
|
||||
{VABS_EQ_F64, []int{1, 0}, "VABS", "ABSD"},
|
||||
{VSQRT_EQ_F32, []int{1, 0}, "VSQRT", "SQRTF"},
|
||||
{VSQRT_EQ_F64, []int{1, 0}, "VSQRT", "SQRTD"},
|
||||
{VCMP_EQ_F32, []int{1, 0}, "VCMP", "CMPF"},
|
||||
{VCMP_EQ_F64, []int{1, 0}, "VCMP", "CMPD"},
|
||||
{VCMP_E_EQ_F32, []int{1, 0}, "VCMP.E", "CMPF"},
|
||||
{VCMP_E_EQ_F64, []int{1, 0}, "VCMP.E", "CMPD"},
|
||||
{VLDR_EQ, []int{1}, "VLDR", "MOV"},
|
||||
{VSTR_EQ, []int{1}, "VSTR", "MOV"},
|
||||
{VMOV_EQ_F32, []int{1, 0}, "VMOV", "MOVF"},
|
||||
{VMOV_EQ_F64, []int{1, 0}, "VMOV", "MOVD"},
|
||||
{VMOV_EQ_32, []int{1, 0}, "VMOV", "MOVW"},
|
||||
{VMOV_EQ, []int{1, 0}, "VMOV", "MOVW"},
|
||||
{VCVT_EQ_F64_F32, []int{1, 0}, "VCVT", "MOVFD"},
|
||||
{VCVT_EQ_F32_F64, []int{1, 0}, "VCVT", "MOVDF"},
|
||||
{VCVT_EQ_F32_U32, []int{1, 0}, "VCVT", "MOVWF.U"},
|
||||
{VCVT_EQ_F32_S32, []int{1, 0}, "VCVT", "MOVWF"},
|
||||
{VCVT_EQ_S32_F32, []int{1, 0}, "VCVT", "MOVFW"},
|
||||
{VCVT_EQ_U32_F32, []int{1, 0}, "VCVT", "MOVFW.U"},
|
||||
{VCVT_EQ_F64_U32, []int{1, 0}, "VCVT", "MOVWD.U"},
|
||||
{VCVT_EQ_F64_S32, []int{1, 0}, "VCVT", "MOVWD"},
|
||||
{VCVT_EQ_S32_F64, []int{1, 0}, "VCVT", "MOVDW"},
|
||||
{VCVT_EQ_U32_F64, []int{1, 0}, "VCVT", "MOVDW.U"},
|
||||
}
|
||||
|
||||
// convert FP instructions from GNU syntax to Plan 9 syntax, for example,
|
||||
// vadd.f32 s0, s3, s4 -> ADDF F0, S3, F2
|
||||
// vsub.f64 d0, d2, d4 -> SUBD F0, F2, F4
|
||||
// vldr s2, [r11] -> MOVF (R11), F1
|
||||
// inputs: instruction name and arguments in GNU syntax
|
||||
// return values: corresponding instruction name and arguments in Plan 9 syntax
|
||||
func fpTrans(inst *Inst, op string, args []string) (string, []string) {
|
||||
for _, fp := range fpInst {
|
||||
if inst.Op&^15 == fp.op {
|
||||
// remove gnu syntax suffixes
|
||||
op = strings.Replace(op, ".F32", "", -1)
|
||||
op = strings.Replace(op, ".F64", "", -1)
|
||||
op = strings.Replace(op, ".S32", "", -1)
|
||||
op = strings.Replace(op, ".U32", "", -1)
|
||||
op = strings.Replace(op, ".32", "", -1)
|
||||
// compose op name
|
||||
if fp.op == VLDR_EQ || fp.op == VSTR_EQ {
|
||||
switch {
|
||||
case strings.HasPrefix(args[fp.transArgs[0]], "D"):
|
||||
op = "MOVD" + op[len(fp.gnuName):]
|
||||
case strings.HasPrefix(args[fp.transArgs[0]], "S"):
|
||||
op = "MOVF" + op[len(fp.gnuName):]
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong FP register: %v", inst))
|
||||
}
|
||||
} else {
|
||||
op = fp.goName + op[len(fp.gnuName):]
|
||||
}
|
||||
// transform registers
|
||||
for ix, ri := range fp.transArgs {
|
||||
switch {
|
||||
case strings.HasSuffix(args[ri], "[1]"): // MOVW Rx, Dy[1]
|
||||
break
|
||||
case strings.HasSuffix(args[ri], "[0]"): // Dx[0] -> Fx
|
||||
args[ri] = strings.Replace(args[ri], "[0]", "", -1)
|
||||
fallthrough
|
||||
case strings.HasPrefix(args[ri], "D"): // Dx -> Fx
|
||||
args[ri] = "F" + args[ri][1:]
|
||||
case strings.HasPrefix(args[ri], "S"):
|
||||
if inst.Args[ix].(Reg)&1 == 0 { // Sx -> Fy, y = x/2, if x is even
|
||||
args[ri] = fmt.Sprintf("F%d", (inst.Args[ix].(Reg)-S0)/2)
|
||||
}
|
||||
case strings.HasPrefix(args[ri], "$"): // CMPF/CMPD $0, Fx
|
||||
break
|
||||
case strings.HasPrefix(args[ri], "R"): // MOVW Rx, Dy[1]
|
||||
break
|
||||
default:
|
||||
panic(fmt.Sprintf("wrong FP register: %v", inst))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return op, args
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
newdecode.txt:
|
||||
cd ..; go test -cover -run 'ObjdumpARMCond' -v -timeout 10h -printtests -long 2>&1 | tee log
|
||||
cd ..; go test -cover -run 'ObjdumpARMUncond' -v -timeout 10h -printtests -long 2>&1 | tee -a log
|
||||
egrep ' (gnu|plan9) ' ../log |sort >newdecode.txt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user