Files
hakurei/internal/pkg/asm.go
2026-04-18 11:47:20 -05:00

294 lines
6.2 KiB
Go

package pkg
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
"unique"
)
type AsmMessage struct {
data AsmData
sig AsmSig
}
type AsmGenerator struct {
i uintptr
as chan AsmMessage
lbl []uintptr
}
type AsmSig int
const (
AsmSigEnd AsmSig = iota
AsmSigLbl
AsmSigHead
AsmSigVal
)
// GenerateAll collects all asm sent to this generator and outputs formatted data to the given [io.Writer].
func (g *AsmGenerator) GenerateAll(w io.Writer) error {
for a := <-g.as; a.sig != AsmSigEnd; a = <-g.as {
switch a.sig {
case AsmSigLbl:
break
case AsmSigHead:
break
case AsmSigVal:
_, err := w.Write([]byte(a.data.Line(g)))
if err != nil {
return err
}
break
default:
panic("invalid asm signal")
}
}
return nil
}
type AsmFormatter struct {
// if false, generate labels. if true, bare idents will be shown.
Real bool
// if true, header data and the dependencies list will be shown.
ShowHeader bool
// if true, don't generate raw byte data (only generate raw assembly).
Raw bool
}
type AsmData interface {
Line(*AsmGenerator) string
Offset(int)
}
type AsmDataNone struct{}
func (a AsmDataNone) Line(*AsmGenerator) string {
return ""
}
func (a AsmDataNone) Offset(int) {}
type AsmLine struct {
pos int
word int
kindData int64
valueData []byte
indent int
kind string
value string
}
func (l AsmLine) Line(gen *AsmGenerator) string {
return ""
}
func (l AsmLine) Offset(offset int) {
l.pos += offset
}
type AsmHeaderLine struct {
pos int
kind string
kindData int64
label string
id unique.Handle[ID]
}
var spacingLine = AsmLine{
pos: -1,
kindData: -1,
valueData: nil,
indent: 0,
kind: "",
value: "",
}
func Disassemble(r io.Reader, real bool, showHeader bool, force bool, raw bool) (s string, err error) {
var lines []AsmLine
sb := new(strings.Builder)
header := true
pos := new(int)
for err == nil {
if header {
var kind uint64
var size uint64
var bsize []byte
p := *pos
if _, kind, err = nextUint64(r, pos); err != nil {
break
}
if bsize, size, err = nextUint64(r, pos); err != nil {
break
}
if showHeader {
lines = append(lines, AsmLine{p, 8, int64(kind), bsize, 0, "head " + intToKind(kind), ""})
}
for i := 0; uint64(i) < size; i++ {
var did ID
var dkind uint64
p := *pos
if _, dkind, err = nextUint64(r, pos); err != nil {
break
}
if _, did, err = nextIdent(r, pos); err != nil {
break
}
if showHeader {
lines = append(lines, AsmLine{p, 8, int64(dkind), nil, 1, intToKind(dkind), Encode(did)})
}
}
header = false
}
var k uint32
p := *pos
if _, k, err = nextUint32(r, pos); err != nil {
break
}
kind := IRValueKind(k)
switch kind {
case IRKindEnd:
var a uint32
var ba []byte
if ba, a, err = nextUint32(r, pos); err != nil {
break
}
if a&1 != 0 {
var sum Checksum
if _, sum, err = nextIdent(r, pos); err != nil {
break
}
lines = append(lines, AsmLine{p, 4, int64(kind), ba, 1, "end ", Encode(sum)})
} else {
lines = append(lines, AsmLine{p, 4, int64(kind), []byte{0, 0, 0, 0}, 1, "end ", ""})
}
lines = append(lines, spacingLine)
header = true
continue
case IRKindIdent:
var a []byte
// discard ancillary
if a, _, err = nextUint32(r, pos); err != nil {
break
}
var id ID
if _, id, err = nextIdent(r, pos); err != nil {
break
}
lines = append(lines, AsmLine{p, 4, int64(kind), a, 1, "id ", Encode(id)})
continue
case IRKindUint32:
var i uint32
var bi []byte
if bi, i, err = nextUint32(r, pos); err != nil {
break
}
lines = append(lines, AsmLine{p, 4, int64(kind), bi, 1, "int ", strconv.FormatUint(uint64(i), 10)})
case IRKindString:
var l uint32
var bl []byte
if bl, l, err = nextUint32(r, pos); err != nil {
break
}
s := make([]byte, l+(wordSize-(l)%wordSize)%wordSize)
var n int
if n, err = r.Read(s); err != nil {
break
}
*pos = *pos + n
lines = append(lines, AsmLine{p, 4, int64(kind), bl, 1, "str ", strconv.Quote(string(s[:l]))})
continue
default:
var bi []byte
if bi, _, err = nextUint32(r, pos); err != nil {
break
}
lines = append(lines, AsmLine{p, 4, int64(kind), bi, 1, "????", ""})
}
}
if err != io.EOF {
return
}
err = nil
for _, line := range lines {
if raw {
if line.pos != -1 {
sb.WriteString(fmt.Sprintf("%s\t%s\n", line.kind, line.value))
}
} else {
if line.pos == -1 {
sb.WriteString("\n")
} else if line.word == 4 {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x%s %s %s\n", line.pos, binary.LittleEndian.AppendUint32(nil, uint32(line.kindData)), line.valueData, headerSpacing(showHeader), line.kind, line.value))
} else {
kind := binary.LittleEndian.AppendUint64(nil, uint64(line.kindData))
value := line.valueData
if len(value) == 8 {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x %04x %04x %s %s\n", line.pos, kind[:4], kind[4:], value[:4], value[4:], line.kind, line.value))
} else {
sb.WriteString(fmt.Sprintf("%06x: %04x %04x %s %s\n", line.pos, kind[:4], kind[4:], line.kind, line.value))
}
}
}
}
return sb.String(), err
}
func nextUint32(r io.Reader, pos *int) ([]byte, uint32, error) {
i := make([]byte, 4)
_, err := r.Read(i)
if err != nil {
return i, 0, err
}
p := *pos + 4
*pos = p
return i, binary.LittleEndian.Uint32(i), nil
}
func nextUint64(r io.Reader, pos *int) ([]byte, uint64, error) {
i := make([]byte, 8)
_, err := r.Read(i)
if err != nil {
return i, 0, err
}
p := *pos + 8
*pos = p
return i, binary.LittleEndian.Uint64(i), nil
}
func nextIdent(r io.Reader, pos *int) ([]byte, ID, error) {
i := make([]byte, 48)
if _, err := r.Read(i); err != nil {
return i, ID{}, err
}
p := *pos + 48
*pos = p
return i, ID(i), nil
}
func intToKind(i uint64) string {
switch Kind(i) {
case KindHTTPGet:
return "http"
case KindTar:
return "tar "
case KindExec:
return "exec"
case KindExecNet:
return "exen"
case KindFile:
return "file"
default:
return fmt.Sprintf("$%d ", i-KindCustomOffset)
}
}
func headerSpacing(showHeader bool) string {
if showHeader {
return " "
}
return ""
}