From a466dd8f2bae702f443cc49aee3f4c3f26878d4b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 11 Mar 2019 16:18:09 +0100 Subject: [PATCH] main: include .data section in .hex file The function extracting the firmware image for .hex and .bin files wasn't working correctly: it only extracted the .text segment and not the .data segment. This commit fixes this issue, so that it behaves (hopefully) just like objcopy -O{ihex|binary}. Another small change is that the formatting of the .hex file was made more like the output of objcopy: no entry addres (old Intel CPU holdover) and 16 bytes of data on each line. --- objcopy.go | 75 ++++++++++++++++++++++++++++++++++++++++++++---------- uf2.go | 2 +- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/objcopy.go b/objcopy.go index d67bcab5..073fb720 100644 --- a/objcopy.go +++ b/objcopy.go @@ -2,8 +2,10 @@ package main import ( "debug/elf" + "io/ioutil" "os" "path/filepath" + "sort" "github.com/marcinbor85/gohex" ) @@ -21,24 +23,72 @@ func (e ObjcopyError) Error() string { return e.Op + ": " + e.Err.Error() } -// ExtractTextSegment returns the .text segment and the first address from the -// ELF file in the given path. -func ExtractTextSegment(path string) (uint64, []byte, error) { +type ProgSlice []*elf.Prog + +func (s ProgSlice) Len() int { return len(s) } +func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr } +func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// ExtractROM extracts a firmware image and the first load address from the +// given ELF file. It tries to emulate the behavior of objcopy. +func ExtractROM(path string) (uint64, []byte, error) { f, err := elf.Open(path) if err != nil { return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err} } defer f.Close() - text := f.Section(".text") - if text == nil { - return 0, nil, ObjcopyError{"file does not contain .text segment: " + path, nil} + // The GNU objcopy command does the following for firmware extraction (from + // the man page): + // > When objcopy generates a raw binary file, it will essentially produce a + // > memory dump of the contents of the input object file. All symbols and + // > relocation information will be discarded. The memory dump will start at + // > the load address of the lowest section copied into the output file. + + // Find the lowest section address. + startAddr := ^uint64(0) + for _, section := range f.Sections { + if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 { + continue + } + if section.Addr < startAddr { + startAddr = section.Addr + } } - data, err := text.Data() - if err != nil { - return 0, nil, ObjcopyError{"failed to extract .text segment from ELF file", err} + + progs := make(ProgSlice, 0, 2) + for _, prog := range f.Progs { + if prog.Type != elf.PT_LOAD || prog.Filesz == 0 { + continue + } + progs = append(progs, prog) + } + if len(progs) == 0 { + return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil} + } + sort.Sort(progs) + + var rom []byte + for _, prog := range progs { + if prog.Paddr != progs[0].Paddr+uint64(len(rom)) { + return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil} + } + data, err := ioutil.ReadAll(prog.Open()) + if err != nil { + return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err} + } + rom = append(rom, data...) + } + if progs[0].Paddr < startAddr { + // The lowest memory address is before the first section. This means + // that there is some extra data loaded at the start of the image that + // should be discarded. + // Example: ELF files where .text doesn't start at address 0 because + // there is a bootloader at the start. + return startAddr, rom[startAddr-progs[0].Paddr:], nil + } else { + return progs[0].Paddr, rom, nil } - return text.Addr, data, nil } // Objcopy converts an ELF file to a different (simpler) output file format: @@ -51,7 +101,7 @@ func Objcopy(infile, outfile string) error { defer f.Close() // Read the .text segment. - addr, data, err := ExtractTextSegment(infile) + addr, data, err := ExtractROM(infile) if err != nil { return err } @@ -65,12 +115,11 @@ func Objcopy(infile, outfile string) error { return err case ".hex": mem := gohex.NewMemory() - mem.SetStartAddress(uint32(addr)) // ignored in most cases (Intel-specific) err := mem.AddBinary(uint32(addr), data) if err != nil { return ObjcopyError{"failed to create .hex file", err} } - mem.DumpIntelHex(f, 32) // TODO: handle error + mem.DumpIntelHex(f, 16) // TODO: handle error return nil default: panic("unreachable") diff --git a/uf2.go b/uf2.go index 6d76a025..04013317 100644 --- a/uf2.go +++ b/uf2.go @@ -15,7 +15,7 @@ import ( // ConvertELFFileToUF2File converts an ELF file to a UF2 file. func ConvertELFFileToUF2File(infile, outfile string) error { // Read the .text segment. - _, data, err := ExtractTextSegment(infile) + _, data, err := ExtractROM(infile) if err != nil { return err }