diff --git a/cmd/irdump/main.go b/cmd/irdump/main.go new file mode 100644 index 0000000..e8faaeb --- /dev/null +++ b/cmd/irdump/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "errors" + "log" + "os" + + "hakurei.app/command" + "hakurei.app/internal/pkg" +) + +func main() { + log.SetFlags(0) + log.SetPrefix("irdump: ") + + var ( + flagOutput string + flagReal bool + flagHeader bool + flagForce bool + flagRaw bool + ) + c := command.New(os.Stderr, log.Printf, "irdump", func(args []string) (err error) { + var input *os.File + if len(args) != 1 { + return errors.New("irdump requires 1 argument") + } + if input, err = os.Open(args[0]); err != nil { + return + } + defer input.Close() + + var output *os.File + if flagOutput == "" { + output = os.Stdout + } else { + defer output.Close() + if output, err = os.Create(flagOutput); err != nil { + return + } + } + + var out string + if out, err = pkg.Disassemble(input, flagReal, flagHeader, flagForce, flagRaw); err != nil { + return + } + if _, err = output.WriteString(out); err != nil { + return + } + return + }).Flag( + &flagOutput, + "o", command.StringFlag(""), + "Output file for asm (leave empty for stdout)", + ).Flag( + &flagReal, + "r", command.BoolFlag(false), + "skip label generation; idents print real value", + ).Flag( + &flagHeader, + "H", command.BoolFlag(false), + "display artifact headers", + ).Flag( + &flagForce, + "f", command.BoolFlag(false), + "force display (skip validations)", + ).Flag( + &flagRaw, + "R", command.BoolFlag(false), + "don't format output", + ) + + c.MustParse(os.Args[1:], func(err error) { + log.Fatal(err) + }) +} diff --git a/internal/pkg/asm.go b/internal/pkg/asm.go new file mode 100644 index 0000000..86fc1e4 --- /dev/null +++ b/internal/pkg/asm.go @@ -0,0 +1,216 @@ +package pkg + +import ( + "encoding/binary" + "fmt" + "io" + "strconv" + "strings" +) + +type asmOutLine struct { + pos int + word int + kindData int64 + valueData []byte + indent int + kind string + value string +} + +var spacingLine = asmOutLine{ + 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 []asmOutLine + 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, asmOutLine{p, 8, int64(kind), bsize, 0, "head " + intToKind(kind), ""}) + } + for i := 0; uint64(i) < size; i++ { + var did Checksum + 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, asmOutLine{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, asmOutLine{p, 4, int64(kind), ba, 1, "end ", Encode(sum)}) + } else { + lines = append(lines, asmOutLine{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 sum Checksum + if _, sum, err = nextIdent(r, pos); err != nil { + break + } + + lines = append(lines, asmOutLine{p, 4, int64(kind), a, 1, "id ", Encode(sum)}) + continue + case IRKindUint32: + var i uint32 + var bi []byte + if bi, i, err = nextUint32(r, pos); err != nil { + break + } + lines = append(lines, asmOutLine{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, asmOutLine{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, asmOutLine{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, Checksum, error) { + i := make([]byte, 48) + if _, err := r.Read(i); err != nil { + return i, Checksum{}, err + } + p := *pos + 48 + *pos = p + return i, Checksum(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 "" +}