mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.1 KiB
141 lines
4.1 KiB
package builder
|
|
|
|
import (
|
|
"debug/elf"
|
|
"io/ioutil"
|
|
"os"
|
|
"sort"
|
|
|
|
"github.com/marcinbor85/gohex"
|
|
)
|
|
|
|
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
|
|
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
|
|
const maxPadBytes = 4095
|
|
|
|
// objcopyError is an error returned by functions that act like objcopy.
|
|
type objcopyError struct {
|
|
Op string
|
|
Err error
|
|
}
|
|
|
|
func (e objcopyError) Error() string {
|
|
if e.Err == nil {
|
|
return e.Op
|
|
}
|
|
return e.Op + ": " + e.Err.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()
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
progs := make(progSlice, 0, 2)
|
|
for _, prog := range f.Progs {
|
|
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 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 {
|
|
romEnd := progs[0].Paddr + uint64(len(rom))
|
|
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
|
// Sometimes, the linker seems to insert a bit of padding between
|
|
// segments. Simply zero-fill these parts.
|
|
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
|
}
|
|
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
|
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
|
if diff > maxPadBytes {
|
|
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
|
}
|
|
// Pad the difference
|
|
rom = append(rom, make([]byte, diff)...)
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
// 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":
|
|
// 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":
|
|
// 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")
|
|
}
|
|
}
|
|
|