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  }