github.com/aykevl/tinygo@v0.5.0/objcopy.go (about) 1 package main 2 3 import ( 4 "debug/elf" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "sort" 9 10 "github.com/marcinbor85/gohex" 11 ) 12 13 // ObjcopyError is an error returned by functions that act like objcopy. 14 type ObjcopyError struct { 15 Op string 16 Err error 17 } 18 19 func (e ObjcopyError) Error() string { 20 if e.Err == nil { 21 return e.Op 22 } 23 return e.Op + ": " + e.Err.Error() 24 } 25 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) { 35 f, err := elf.Open(path) 36 if err != nil { 37 return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err} 38 } 39 defer f.Close() 40 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 } 57 } 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 91 } 92 } 93 94 // Objcopy converts an ELF file to a different (simpler) output file format: 95 // .bin or .hex. It extracts only the .text section. 96 func Objcopy(infile, outfile string) error { 97 f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 98 if err != nil { 99 return err 100 } 101 defer f.Close() 102 103 // Read the .text segment. 104 addr, data, err := ExtractROM(infile) 105 if err != nil { 106 return err 107 } 108 109 // Write to the file, in the correct format. 110 switch filepath.Ext(outfile) { 111 case ".bin": 112 // The address is not stored in a .bin file (therefore you 113 // should use .hex files in most cases). 114 _, err := f.Write(data) 115 return err 116 case ".hex": 117 mem := gohex.NewMemory() 118 err := mem.AddBinary(uint32(addr), data) 119 if err != nil { 120 return ObjcopyError{"failed to create .hex file", err} 121 } 122 mem.DumpIntelHex(f, 16) // TODO: handle error 123 return nil 124 default: 125 panic("unreachable") 126 } 127 }