Skip to content

Commit a466dd8

Browse files
aykevldeadprogram
authored andcommitted
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.
1 parent b1744db commit a466dd8

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

objcopy.go

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package main
22

33
import (
44
"debug/elf"
5+
"io/ioutil"
56
"os"
67
"path/filepath"
8+
"sort"
79

810
"github.com/marcinbor85/gohex"
911
)
@@ -21,24 +23,72 @@ func (e ObjcopyError) Error() string {
2123
return e.Op + ": " + e.Err.Error()
2224
}
2325

24-
// ExtractTextSegment returns the .text segment and the first address from the
25-
// ELF file in the given path.
26-
func ExtractTextSegment(path string) (uint64, []byte, error) {
26+
type ProgSlice []*elf.Prog
27+
28+
func (s ProgSlice) Len() int { return len(s) }
29+
func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
30+
func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
31+
32+
// ExtractROM extracts a firmware image and the first load address from the
33+
// given ELF file. It tries to emulate the behavior of objcopy.
34+
func ExtractROM(path string) (uint64, []byte, error) {
2735
f, err := elf.Open(path)
2836
if err != nil {
2937
return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err}
3038
}
3139
defer f.Close()
3240

33-
text := f.Section(".text")
34-
if text == nil {
35-
return 0, nil, ObjcopyError{"file does not contain .text segment: " + path, nil}
41+
// The GNU objcopy command does the following for firmware extraction (from
42+
// the man page):
43+
// > When objcopy generates a raw binary file, it will essentially produce a
44+
// > memory dump of the contents of the input object file. All symbols and
45+
// > relocation information will be discarded. The memory dump will start at
46+
// > the load address of the lowest section copied into the output file.
47+
48+
// Find the lowest section address.
49+
startAddr := ^uint64(0)
50+
for _, section := range f.Sections {
51+
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
52+
continue
53+
}
54+
if section.Addr < startAddr {
55+
startAddr = section.Addr
56+
}
3657
}
37-
data, err := text.Data()
38-
if err != nil {
39-
return 0, nil, ObjcopyError{"failed to extract .text segment from ELF file", err}
58+
59+
progs := make(ProgSlice, 0, 2)
60+
for _, prog := range f.Progs {
61+
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
62+
continue
63+
}
64+
progs = append(progs, prog)
65+
}
66+
if len(progs) == 0 {
67+
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
68+
}
69+
sort.Sort(progs)
70+
71+
var rom []byte
72+
for _, prog := range progs {
73+
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
74+
return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil}
75+
}
76+
data, err := ioutil.ReadAll(prog.Open())
77+
if err != nil {
78+
return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err}
79+
}
80+
rom = append(rom, data...)
81+
}
82+
if progs[0].Paddr < startAddr {
83+
// The lowest memory address is before the first section. This means
84+
// that there is some extra data loaded at the start of the image that
85+
// should be discarded.
86+
// Example: ELF files where .text doesn't start at address 0 because
87+
// there is a bootloader at the start.
88+
return startAddr, rom[startAddr-progs[0].Paddr:], nil
89+
} else {
90+
return progs[0].Paddr, rom, nil
4091
}
41-
return text.Addr, data, nil
4292
}
4393

4494
// Objcopy converts an ELF file to a different (simpler) output file format:
@@ -51,7 +101,7 @@ func Objcopy(infile, outfile string) error {
51101
defer f.Close()
52102

53103
// Read the .text segment.
54-
addr, data, err := ExtractTextSegment(infile)
104+
addr, data, err := ExtractROM(infile)
55105
if err != nil {
56106
return err
57107
}
@@ -65,12 +115,11 @@ func Objcopy(infile, outfile string) error {
65115
return err
66116
case ".hex":
67117
mem := gohex.NewMemory()
68-
mem.SetStartAddress(uint32(addr)) // ignored in most cases (Intel-specific)
69118
err := mem.AddBinary(uint32(addr), data)
70119
if err != nil {
71120
return ObjcopyError{"failed to create .hex file", err}
72121
}
73-
mem.DumpIntelHex(f, 32) // TODO: handle error
122+
mem.DumpIntelHex(f, 16) // TODO: handle error
74123
return nil
75124
default:
76125
panic("unreachable")

uf2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
1616
func ConvertELFFileToUF2File(infile, outfile string) error {
1717
// Read the .text segment.
18-
_, data, err := ExtractTextSegment(infile)
18+
_, data, err := ExtractROM(infile)
1919
if err != nil {
2020
return err
2121
}

0 commit comments

Comments
 (0)