@@ -2,8 +2,10 @@ package main
22
33import (
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" )
0 commit comments