mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-15 03:36:37 +00:00
568 lines
12 KiB
Go
568 lines
12 KiB
Go
package x86_64
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/bits"
|
|
|
|
"github.com/chenzhuoyu/iasm/expr"
|
|
)
|
|
|
|
type (
|
|
_PseudoType int
|
|
_InstructionEncoder func(*Program, ...interface{}) *Instruction
|
|
)
|
|
|
|
const (
|
|
_PseudoNop _PseudoType = iota + 1
|
|
_PseudoByte
|
|
_PseudoWord
|
|
_PseudoLong
|
|
_PseudoQuad
|
|
_PseudoData
|
|
_PseudoAlign
|
|
)
|
|
|
|
func (self _PseudoType) String() string {
|
|
switch self {
|
|
case _PseudoNop:
|
|
return ".nop"
|
|
case _PseudoByte:
|
|
return ".byte"
|
|
case _PseudoWord:
|
|
return ".word"
|
|
case _PseudoLong:
|
|
return ".long"
|
|
case _PseudoQuad:
|
|
return ".quad"
|
|
case _PseudoData:
|
|
return ".data"
|
|
case _PseudoAlign:
|
|
return ".align"
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
type _Pseudo struct {
|
|
kind _PseudoType
|
|
data []byte
|
|
uint uint64
|
|
expr *expr.Expr
|
|
}
|
|
|
|
func (self *_Pseudo) free() {
|
|
if self.expr != nil {
|
|
self.expr.Free()
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encode(m *[]byte, pc uintptr) int {
|
|
switch self.kind {
|
|
case _PseudoNop:
|
|
return 0
|
|
case _PseudoByte:
|
|
self.encodeByte(m)
|
|
return 1
|
|
case _PseudoWord:
|
|
self.encodeWord(m)
|
|
return 2
|
|
case _PseudoLong:
|
|
self.encodeLong(m)
|
|
return 4
|
|
case _PseudoQuad:
|
|
self.encodeQuad(m)
|
|
return 8
|
|
case _PseudoData:
|
|
self.encodeData(m)
|
|
return len(self.data)
|
|
case _PseudoAlign:
|
|
self.encodeAlign(m, pc)
|
|
return self.alignSize(pc)
|
|
default:
|
|
panic("invalid pseudo instruction")
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) evalExpr(low int64, high int64) int64 {
|
|
if v, err := self.expr.Evaluate(); err != nil {
|
|
panic(err)
|
|
} else if v < low || v > high {
|
|
panic(fmt.Sprintf("expression out of range [%d, %d]: %d", low, high, v))
|
|
} else {
|
|
return v
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) alignSize(pc uintptr) int {
|
|
if !ispow2(self.uint) {
|
|
panic(fmt.Sprintf("aligment should be a power of 2, not %d", self.uint))
|
|
} else {
|
|
return align(int(pc), bits.TrailingZeros64(self.uint)) - int(pc)
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeData(m *[]byte) {
|
|
if m != nil {
|
|
*m = append(*m, self.data...)
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeByte(m *[]byte) {
|
|
if m != nil {
|
|
append8(m, byte(self.evalExpr(math.MinInt8, math.MaxUint8)))
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeWord(m *[]byte) {
|
|
if m != nil {
|
|
append16(m, uint16(self.evalExpr(math.MinInt16, math.MaxUint16)))
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeLong(m *[]byte) {
|
|
if m != nil {
|
|
append32(m, uint32(self.evalExpr(math.MinInt32, math.MaxUint32)))
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeQuad(m *[]byte) {
|
|
if m != nil {
|
|
if v, err := self.expr.Evaluate(); err != nil {
|
|
panic(err)
|
|
} else {
|
|
append64(m, uint64(v))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *_Pseudo) encodeAlign(m *[]byte, pc uintptr) {
|
|
if m != nil {
|
|
if self.expr == nil {
|
|
expandmm(m, self.alignSize(pc), 0)
|
|
} else {
|
|
expandmm(m, self.alignSize(pc), byte(self.evalExpr(math.MinInt8, math.MaxUint8)))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Operands represents a sequence of operand required by an instruction.
|
|
type Operands [_N_args]interface{}
|
|
|
|
// InstructionDomain represents the domain of an instruction.
|
|
type InstructionDomain uint8
|
|
|
|
const (
|
|
DomainGeneric InstructionDomain = iota
|
|
DomainMMXSSE
|
|
DomainAVX
|
|
DomainFMA
|
|
DomainCrypto
|
|
DomainMask
|
|
DomainAMDSpecific
|
|
DomainMisc
|
|
DomainPseudo
|
|
)
|
|
|
|
type (
|
|
_BranchType uint8
|
|
)
|
|
|
|
const (
|
|
_B_none _BranchType = iota
|
|
_B_conditional
|
|
_B_unconditional
|
|
)
|
|
|
|
// Instruction represents an unencoded instruction.
|
|
type Instruction struct {
|
|
next *Instruction
|
|
pc uintptr
|
|
nb int
|
|
len int
|
|
argc int
|
|
name string
|
|
argv Operands
|
|
forms [_N_forms]_Encoding
|
|
pseudo _Pseudo
|
|
branch _BranchType
|
|
domain InstructionDomain
|
|
prefix []byte
|
|
}
|
|
|
|
func (self *Instruction) add(flags int, encoder func(m *_Encoding, v []interface{})) {
|
|
self.forms[self.len].flags = flags
|
|
self.forms[self.len].encoder = encoder
|
|
self.len++
|
|
}
|
|
|
|
func (self *Instruction) free() {
|
|
self.clear()
|
|
self.pseudo.free()
|
|
//freeInstruction(self)
|
|
}
|
|
|
|
func (self *Instruction) clear() {
|
|
for i := 0; i < self.argc; i++ {
|
|
if v, ok := self.argv[i].(Disposable); ok {
|
|
v.Free()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *Instruction) check(e *_Encoding) bool {
|
|
if (e.flags & _F_rel1) != 0 {
|
|
return isRel8(self.argv[0])
|
|
} else if (e.flags & _F_rel4) != 0 {
|
|
return isRel32(self.argv[0]) || isLabel(self.argv[0])
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (self *Instruction) encode(m *[]byte) int {
|
|
n := math.MaxInt64
|
|
p := (*_Encoding)(nil)
|
|
|
|
/* encode prefixes if any */
|
|
if self.nb = len(self.prefix); m != nil {
|
|
*m = append(*m, self.prefix...)
|
|
}
|
|
|
|
/* check for pseudo-instructions */
|
|
if self.pseudo.kind != 0 {
|
|
self.nb += self.pseudo.encode(m, self.pc)
|
|
return self.nb
|
|
}
|
|
|
|
/* find the shortest encoding */
|
|
for i := 0; i < self.len; i++ {
|
|
if e := &self.forms[i]; self.check(e) {
|
|
if v := e.encode(self.argv[:self.argc]); v < n {
|
|
n = v
|
|
p = e
|
|
}
|
|
}
|
|
}
|
|
|
|
/* add to buffer if needed */
|
|
if m != nil {
|
|
*m = append(*m, p.bytes[:n]...)
|
|
}
|
|
|
|
/* update the instruction length */
|
|
self.nb += n
|
|
return self.nb
|
|
}
|
|
|
|
/** Instruction Prefixes **/
|
|
|
|
const (
|
|
_P_cs = 0x2e
|
|
_P_ds = 0x3e
|
|
_P_es = 0x26
|
|
_P_fs = 0x64
|
|
_P_gs = 0x65
|
|
_P_ss = 0x36
|
|
_P_lock = 0xf0
|
|
)
|
|
|
|
// CS overrides the memory operation of this instruction to CS.
|
|
func (self *Instruction) CS() *Instruction {
|
|
self.prefix = append(self.prefix, _P_cs)
|
|
return self
|
|
}
|
|
|
|
// DS overrides the memory operation of this instruction to DS,
|
|
// this is the default section for most instructions if not specified.
|
|
func (self *Instruction) DS() *Instruction {
|
|
self.prefix = append(self.prefix, _P_ds)
|
|
return self
|
|
}
|
|
|
|
// ES overrides the memory operation of this instruction to ES.
|
|
func (self *Instruction) ES() *Instruction {
|
|
self.prefix = append(self.prefix, _P_es)
|
|
return self
|
|
}
|
|
|
|
// FS overrides the memory operation of this instruction to FS.
|
|
func (self *Instruction) FS() *Instruction {
|
|
self.prefix = append(self.prefix, _P_fs)
|
|
return self
|
|
}
|
|
|
|
// GS overrides the memory operation of this instruction to GS.
|
|
func (self *Instruction) GS() *Instruction {
|
|
self.prefix = append(self.prefix, _P_gs)
|
|
return self
|
|
}
|
|
|
|
// SS overrides the memory operation of this instruction to SS.
|
|
func (self *Instruction) SS() *Instruction {
|
|
self.prefix = append(self.prefix, _P_ss)
|
|
return self
|
|
}
|
|
|
|
// LOCK causes the processor's LOCK# signal to be asserted during execution of
|
|
// the accompanying instruction (turns the instruction into an atomic instruction).
|
|
// In a multiprocessor environment, the LOCK# signal insures that the processor
|
|
// has exclusive use of any shared memory while the signal is asserted.
|
|
func (self *Instruction) LOCK() *Instruction {
|
|
self.prefix = append(self.prefix, _P_lock)
|
|
return self
|
|
}
|
|
|
|
/** Basic Instruction Properties **/
|
|
|
|
// Name returns the instruction name.
|
|
func (self *Instruction) Name() string {
|
|
return self.name
|
|
}
|
|
|
|
// Domain returns the domain of this instruction.
|
|
func (self *Instruction) Domain() InstructionDomain {
|
|
return self.domain
|
|
}
|
|
|
|
// Operands returns the operands of this instruction.
|
|
func (self *Instruction) Operands() []interface{} {
|
|
return self.argv[:self.argc]
|
|
}
|
|
|
|
// Program represents a sequence of instructions.
|
|
type Program struct {
|
|
arch *Arch
|
|
head *Instruction
|
|
tail *Instruction
|
|
}
|
|
|
|
const (
|
|
_N_near = 2 // near-branch (-128 ~ +127) takes 2 bytes to encode
|
|
_N_far_cond = 6 // conditional far-branch takes 6 bytes to encode
|
|
_N_far_uncond = 5 // unconditional far-branch takes 5 bytes to encode
|
|
)
|
|
|
|
func (self *Program) clear() {
|
|
for p, q := self.head, self.head; p != nil; p = q {
|
|
q = p.next
|
|
p.free()
|
|
}
|
|
}
|
|
|
|
func (self *Program) alloc(name string, argc int, argv Operands) *Instruction {
|
|
p := self.tail
|
|
q := newInstruction(name, argc, argv)
|
|
|
|
/* attach to tail if any */
|
|
if p != nil {
|
|
p.next = q
|
|
} else {
|
|
self.head = q
|
|
}
|
|
|
|
/* set the new tail */
|
|
self.tail = q
|
|
return q
|
|
}
|
|
|
|
func (self *Program) pseudo(kind _PseudoType) (p *Instruction) {
|
|
p = self.alloc(kind.String(), 0, Operands{})
|
|
p.domain = DomainPseudo
|
|
p.pseudo.kind = kind
|
|
return
|
|
}
|
|
|
|
func (self *Program) require(isa ISA) {
|
|
if !self.arch.HasISA(isa) {
|
|
panic("ISA '" + isa.String() + "' was not enabled")
|
|
}
|
|
}
|
|
|
|
func (self *Program) branchSize(p *Instruction) int {
|
|
switch p.branch {
|
|
case _B_none:
|
|
panic("p is not a branch")
|
|
case _B_conditional:
|
|
return _N_far_cond
|
|
case _B_unconditional:
|
|
return _N_far_uncond
|
|
default:
|
|
panic("invalid instruction")
|
|
}
|
|
}
|
|
|
|
/** Pseudo-Instructions **/
|
|
|
|
// Byte is a pseudo-instruction to add raw byte to the assembled code.
|
|
func (self *Program) Byte(v *expr.Expr) (p *Instruction) {
|
|
p = self.pseudo(_PseudoByte)
|
|
p.pseudo.expr = v
|
|
return
|
|
}
|
|
|
|
// Word is a pseudo-instruction to add raw uint16 as little-endian to the assembled code.
|
|
func (self *Program) Word(v *expr.Expr) (p *Instruction) {
|
|
p = self.pseudo(_PseudoWord)
|
|
p.pseudo.expr = v
|
|
return
|
|
}
|
|
|
|
// Long is a pseudo-instruction to add raw uint32 as little-endian to the assembled code.
|
|
func (self *Program) Long(v *expr.Expr) (p *Instruction) {
|
|
p = self.pseudo(_PseudoLong)
|
|
p.pseudo.expr = v
|
|
return
|
|
}
|
|
|
|
// Quad is a pseudo-instruction to add raw uint64 as little-endian to the assembled code.
|
|
func (self *Program) Quad(v *expr.Expr) (p *Instruction) {
|
|
p = self.pseudo(_PseudoQuad)
|
|
p.pseudo.expr = v
|
|
return
|
|
}
|
|
|
|
// Data is a pseudo-instruction to add raw bytes to the assembled code.
|
|
func (self *Program) Data(v []byte) (p *Instruction) {
|
|
p = self.pseudo(_PseudoData)
|
|
p.pseudo.data = v
|
|
return
|
|
}
|
|
|
|
// Align is a pseudo-instruction to ensure the PC is aligned to a certain value.
|
|
func (self *Program) Align(align uint64, padding *expr.Expr) (p *Instruction) {
|
|
p = self.pseudo(_PseudoAlign)
|
|
p.pseudo.uint = align
|
|
p.pseudo.expr = padding
|
|
return
|
|
}
|
|
|
|
/** Program Assembler **/
|
|
|
|
// Free returns the Program object into pool.
|
|
// Any operation performed after Free is undefined behavior.
|
|
//
|
|
// NOTE: This also frees all the instructions, labels, memory
|
|
//
|
|
// operands and expressions associated with this program.
|
|
func (self *Program) Free() {
|
|
self.clear()
|
|
//freeProgram(self)
|
|
}
|
|
|
|
// Link pins a label at the current position.
|
|
func (self *Program) Link(p *Label) {
|
|
if p.Dest != nil {
|
|
panic("lable was alreay linked")
|
|
} else {
|
|
p.Dest = self.pseudo(_PseudoNop)
|
|
}
|
|
}
|
|
|
|
// Assemble assembles and links the entire program into machine code.
|
|
func (self *Program) Assemble(pc uintptr) (ret []byte) {
|
|
orig := pc
|
|
next := true
|
|
offs := uintptr(0)
|
|
|
|
/* Pass 0: PC-precompute, assume all labeled branches are far-branches. */
|
|
for p := self.head; p != nil; p = p.next {
|
|
if p.pc = pc; !isLabel(p.argv[0]) || p.branch == _B_none {
|
|
pc += uintptr(p.encode(nil))
|
|
} else {
|
|
pc += uintptr(self.branchSize(p))
|
|
}
|
|
}
|
|
|
|
/* allocate space for the machine code */
|
|
nb := int(pc - orig)
|
|
ret = make([]byte, 0, nb)
|
|
|
|
/* Pass 1: adjust all the jumps */
|
|
for next {
|
|
next = false
|
|
offs = uintptr(0)
|
|
|
|
/* scan all the branches */
|
|
for p := self.head; p != nil; p = p.next {
|
|
var ok bool
|
|
var lb *Label
|
|
|
|
/* re-calculate the alignment here */
|
|
if nb = p.nb; p.pseudo.kind == _PseudoAlign {
|
|
p.pc -= offs
|
|
offs += uintptr(nb - p.encode(nil))
|
|
continue
|
|
}
|
|
|
|
/* adjust the program counter */
|
|
p.pc -= offs
|
|
lb, ok = p.argv[0].(*Label)
|
|
|
|
/* only care about labeled far-branches */
|
|
if !ok || p.nb == _N_near || p.branch == _B_none {
|
|
continue
|
|
}
|
|
|
|
/* calculate the jump offset */
|
|
size := self.branchSize(p)
|
|
diff := lb.offset(p.pc, size)
|
|
|
|
/* too far to be a near jump */
|
|
if diff > 127 || diff < -128 {
|
|
p.nb = size
|
|
continue
|
|
}
|
|
|
|
/* a far jump becomes a near jump, calculate
|
|
* the PC adjustment value and assemble again */
|
|
next = true
|
|
p.nb = _N_near
|
|
offs += uintptr(size - _N_near)
|
|
}
|
|
}
|
|
|
|
/* Pass 3: link all the cross-references */
|
|
for p := self.head; p != nil; p = p.next {
|
|
for i := 0; i < p.argc; i++ {
|
|
var ok bool
|
|
var lb *Label
|
|
var op *MemoryOperand
|
|
|
|
/* resolve labels */
|
|
if lb, ok = p.argv[i].(*Label); ok {
|
|
p.argv[i] = lb.offset(p.pc, p.nb)
|
|
continue
|
|
}
|
|
|
|
/* check for memory operands */
|
|
if op, ok = p.argv[i].(*MemoryOperand); !ok {
|
|
continue
|
|
}
|
|
|
|
/* check for label references */
|
|
if op.Addr.Type != Reference {
|
|
continue
|
|
}
|
|
|
|
/* replace the label with the real offset */
|
|
op.Addr.Type = Offset
|
|
op.Addr.Offset = op.Addr.Reference.offset(p.pc, p.nb)
|
|
}
|
|
}
|
|
|
|
/* Pass 4: actually encode all the instructions */
|
|
for p := self.head; p != nil; p = p.next {
|
|
p.encode(&ret)
|
|
}
|
|
|
|
/* all done */
|
|
return ret
|
|
}
|
|
|
|
// AssembleAndFree is like Assemble, but it frees the Program after assembling.
|
|
func (self *Program) AssembleAndFree(pc uintptr) (ret []byte) {
|
|
ret = self.Assemble(pc)
|
|
self.Free()
|
|
return
|
|
}
|