From 9eeb14ae31151fff40a211ece0e18281106e470b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 7 Sep 2025 14:56:39 +0800 Subject: [PATCH] feat: support generic bin and intel hex firmware --- go.mod | 1 + go.sum | 2 ++ internal/firmware/firmware.go | 47 ++++++----------------------------- internal/firmware/objcopy.go | 42 +++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 0bd566d3..03269ef9 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( ) require ( + github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84 github.com/mattn/go-tty v0.0.7 github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 go.bug.st/serial v1.6.4 diff --git a/go.sum b/go.sum index ab56b0c4..b3d12fd7 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs= github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94= github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50= +github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84 h1:hyAgCuG5nqTMDeUD8KZs7HSPs6KprPgPP8QmGV8nyvk= +github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= diff --git a/internal/firmware/firmware.go b/internal/firmware/firmware.go index 0b23d8a8..b5da6f6b 100644 --- a/internal/firmware/firmware.go +++ b/internal/firmware/firmware.go @@ -2,7 +2,6 @@ package firmware import ( "fmt" - "io" "os" "strings" ) @@ -35,9 +34,14 @@ func ExtractFileFormatFromCommand(cmd string) string { // ConvertFormats processes format conversions for embedded targets only func ConvertFormats(binFmt, fmtDetail string, envMap map[string]string) error { + var err error // Convert to bin format first (needed for img) if envMap["bin"] != "" { - err := makeFirmwareImage(envMap["out"], envMap["bin"], binFmt, fmtDetail) + if strings.HasPrefix(binFmt, "esp") { + err = makeFirmwareImage(envMap["out"], envMap["bin"], binFmt, fmtDetail) + } else { + err = objcopy(envMap["out"], envMap["bin"], "bin") + } if err != nil { return fmt.Errorf("failed to convert to bin format: %w", err) } @@ -45,7 +49,7 @@ func ConvertFormats(binFmt, fmtDetail string, envMap map[string]string) error { // Convert to hex format if envMap["hex"] != "" { - err := makeFirmwareImage(envMap["out"], envMap["hex"], binFmt, fmtDetail) + err := objcopy(envMap["out"], envMap["hex"], "hex") if err != nil { return fmt.Errorf("failed to convert to hex format: %w", err) } @@ -53,7 +57,7 @@ func ConvertFormats(binFmt, fmtDetail string, envMap map[string]string) error { // Convert to img format if envMap["img"] != "" { - err := makeFirmwareImage(envMap["out"], envMap["img"], binFmt+"-img", fmtDetail) + err = makeFirmwareImage(envMap["out"], envMap["img"], binFmt+"-img", fmtDetail) if err != nil { return fmt.Errorf("failed to convert to img format: %w", err) } @@ -77,38 +81,3 @@ func ConvertFormats(binFmt, fmtDetail string, envMap map[string]string) error { return nil } - -// convertToHex converts binary file to hex format (each byte as two hex characters) -func convertToHex(infile, outfile string) error { - srcFile, err := os.Open(infile) - if err != nil { - return err - } - defer srcFile.Close() - - dstFile, err := os.Create(outfile) - if err != nil { - return err - } - defer dstFile.Close() - - // Read input file and convert each byte to two hex characters - buf := make([]byte, 4096) // Read in chunks - for { - n, err := srcFile.Read(buf) - if n > 0 { - for i := 0; i < n; i++ { - if _, writeErr := fmt.Fprintf(dstFile, "%02x", buf[i]); writeErr != nil { - return writeErr - } - } - } - if err == io.EOF { - break - } - if err != nil { - return err - } - } - return nil -} diff --git a/internal/firmware/objcopy.go b/internal/firmware/objcopy.go index dd978667..c301f75a 100644 --- a/internal/firmware/objcopy.go +++ b/internal/firmware/objcopy.go @@ -4,8 +4,12 @@ package firmware import ( "debug/elf" + "fmt" "io" + "os" "sort" + + "github.com/marcinbor85/gohex" ) // maxPadBytes is the maximum allowed bytes to be padded in a rom extraction @@ -103,3 +107,41 @@ func extractROM(path string) (uint64, []byte, error) { return progs[0].Paddr, rom, nil } } + +// From tinygo/builder/builder/objcopy.go objcopy +// objcopy converts an ELF file to a different (simpler) output file format: +// .bin or .hex. It extracts only the .text section. +func objcopy(infile, outfile, binaryFormat string) error { + f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer f.Close() + + // Read the .text segment. + addr, data, err := extractROM(infile) + if err != nil { + return err + } + + // Write to the file, in the correct format. + switch binaryFormat { + case "hex": + fmt.Fprintf(os.Stderr, "Converting firmware format: %s -> %s (intel hex format: %s)\n", infile, outfile, binaryFormat) + // Intel hex file, includes the firmware start address. + mem := gohex.NewMemory() + err := mem.AddBinary(uint32(addr), data) + if err != nil { + return objcopyError{"failed to create .hex file", err} + } + return mem.DumpIntelHex(f, 16) + case "bin": + fmt.Fprintf(os.Stderr, "Converting firmware format: %s -> %s (format: %s)\n", infile, outfile, binaryFormat) + // The start address is not stored in raw firmware files (therefore you + // should use .hex files in most cases). + _, err := f.Write(data) + return err + default: + panic("unreachable") + } +}