ar.go raw

   1  package builder
   2  
   3  import (
   4  	"bytes"
   5  	"debug/elf"
   6  	"debug/macho"
   7  	"debug/pe"
   8  	"encoding/binary"
   9  	"errors"
  10  	"fmt"
  11  	"io"
  12  	"os"
  13  	"path/filepath"
  14  	"time"
  15  
  16  	wasm "github.com/aykevl/go-wasm"
  17  	"github.com/blakesmith/ar"
  18  )
  19  
  20  // makeArchive creates an archive for static linking from a list of object files
  21  // given as a parameter. It is equivalent to the following command:
  22  //
  23  //	ar -rcs <archivePath> <objs...>
  24  func makeArchive(arfile *os.File, objs []string) error {
  25  	// Open the archive file.
  26  	arwriter := ar.NewWriter(arfile)
  27  	err := arwriter.WriteGlobalHeader()
  28  	if err != nil {
  29  		return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
  30  	}
  31  
  32  	// Open all object files and read the symbols for the symbol table.
  33  	symbolTable := []struct {
  34  		name      string // symbol name
  35  		fileIndex int    // index into objfiles
  36  	}{}
  37  	archiveOffsets := make([]int32, len(objs))
  38  	for i, objpath := range objs {
  39  		objfile, err := os.Open(objpath)
  40  		if err != nil {
  41  			return err
  42  		}
  43  
  44  		// Read the symbols and add them to the symbol table.
  45  		if dbg, err := elf.NewFile(objfile); err == nil {
  46  			symbols, err := dbg.Symbols()
  47  			if err != nil {
  48  				return err
  49  			}
  50  			for _, symbol := range symbols {
  51  				bind := elf.ST_BIND(symbol.Info)
  52  				if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
  53  					// Don't include local symbols (STB_LOCAL).
  54  					continue
  55  				}
  56  				if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
  57  					// Not a function.
  58  					continue
  59  				}
  60  				// Include in archive.
  61  				symbolTable = append(symbolTable, struct {
  62  					name      string
  63  					fileIndex int
  64  				}{symbol.Name, i})
  65  			}
  66  		} else if dbg, err := macho.NewFile(objfile); err == nil {
  67  			for _, symbol := range dbg.Symtab.Syms {
  68  				// See mach-o/nlist.h
  69  				if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT
  70  					continue // undefined symbol
  71  				}
  72  				if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0
  73  					continue // internal symbol (static, etc)
  74  				}
  75  				symbolTable = append(symbolTable, struct {
  76  					name      string
  77  					fileIndex int
  78  				}{symbol.Name, i})
  79  			}
  80  		} else if dbg, err := pe.NewFile(objfile); err == nil {
  81  			for _, symbol := range dbg.Symbols {
  82  				if symbol.StorageClass != 2 {
  83  					continue
  84  				}
  85  				if symbol.SectionNumber == 0 {
  86  					continue
  87  				}
  88  				symbolTable = append(symbolTable, struct {
  89  					name      string
  90  					fileIndex int
  91  				}{symbol.Name, i})
  92  			}
  93  		} else if dbg, err := wasm.Parse(objfile); err == nil {
  94  			for _, s := range dbg.Sections {
  95  				switch section := s.(type) {
  96  				case *wasm.SectionLinking:
  97  					for _, symbol := range section.Symbols {
  98  						if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 {
  99  							// Don't list undefined functions.
 100  							continue
 101  						}
 102  						if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 {
 103  							// Don't include local symbols.
 104  							continue
 105  						}
 106  						if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData {
 107  							// Link functions and data symbols.
 108  							// Some data symbols need to be included, such as
 109  							// __log_data.
 110  							continue
 111  						}
 112  						// Include in the archive.
 113  						symbolTable = append(symbolTable, struct {
 114  							name      string
 115  							fileIndex int
 116  						}{symbol.Name, i})
 117  					}
 118  				}
 119  			}
 120  		} else {
 121  			return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err)
 122  		}
 123  
 124  		// Close file, to avoid issues with too many open files (especially on
 125  		// MacOS X).
 126  		objfile.Close()
 127  	}
 128  
 129  	// Create the symbol table buffer.
 130  	// For some (sparse) details on the file format:
 131  	// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
 132  	buf := &bytes.Buffer{}
 133  	binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
 134  	for range symbolTable {
 135  		// This is a placeholder index, it will be updated after all files have
 136  		// been written to the archive (see the end of this function).
 137  		err = binary.Write(buf, binary.BigEndian, int32(0))
 138  		if err != nil {
 139  			return err
 140  		}
 141  	}
 142  	for _, sym := range symbolTable {
 143  		_, err := buf.Write([]byte(sym.name + "\x00"))
 144  		if err != nil {
 145  			return err
 146  		}
 147  	}
 148  	for buf.Len()%2 != 0 {
 149  		// The symbol table must be aligned.
 150  		// This appears to be required by lld.
 151  		buf.WriteByte(0)
 152  	}
 153  
 154  	// Write the symbol table.
 155  	err = arwriter.WriteHeader(&ar.Header{
 156  		Name:    "/",
 157  		ModTime: time.Unix(0, 0),
 158  		Uid:     0,
 159  		Gid:     0,
 160  		Mode:    0,
 161  		Size:    int64(buf.Len()),
 162  	})
 163  	if err != nil {
 164  		return err
 165  	}
 166  
 167  	// Keep track of the start of the symbol table.
 168  	symbolTableStart, err := arfile.Seek(0, io.SeekCurrent)
 169  	if err != nil {
 170  		return err
 171  	}
 172  
 173  	// Write symbol table contents.
 174  	_, err = arfile.Write(buf.Bytes())
 175  	if err != nil {
 176  		return err
 177  	}
 178  
 179  	// Add all object files to the archive.
 180  	var copyBuf bytes.Buffer
 181  	for i, objpath := range objs {
 182  		objfile, err := os.Open(objpath)
 183  		if err != nil {
 184  			return err
 185  		}
 186  		defer objfile.Close()
 187  
 188  		// Store the start index, for when we'll update the symbol table with
 189  		// the correct file start indices.
 190  		offset, err := arfile.Seek(0, io.SeekCurrent)
 191  		if err != nil {
 192  			return err
 193  		}
 194  		if int64(int32(offset)) != offset {
 195  			return errors.New("large archives (4GB+) not supported: " + arfile.Name())
 196  		}
 197  		archiveOffsets[i] = int32(offset)
 198  
 199  		// Write the file header.
 200  		st, err := objfile.Stat()
 201  		if err != nil {
 202  			return err
 203  		}
 204  		err = arwriter.WriteHeader(&ar.Header{
 205  			Name:    filepath.Base(objfile.Name()),
 206  			ModTime: time.Unix(0, 0),
 207  			Uid:     0,
 208  			Gid:     0,
 209  			Mode:    0644,
 210  			Size:    st.Size(),
 211  		})
 212  		if err != nil {
 213  			return err
 214  		}
 215  
 216  		// Copy the file contents into the archive.
 217  		// First load all contents into a buffer, then write it all in one go to
 218  		// the archive file. This is a bit complicated, but is necessary because
 219  		// io.Copy can't deal with files that are of an odd size.
 220  		copyBuf.Reset()
 221  		n, err := io.Copy(&copyBuf, objfile)
 222  		if err != nil {
 223  			return fmt.Errorf("could not copy object file into ar file: %w", err)
 224  		}
 225  		if n != st.Size() {
 226  			return errors.New("file modified during ar creation: " + arfile.Name())
 227  		}
 228  		_, err = arwriter.Write(copyBuf.Bytes())
 229  		if err != nil {
 230  			return fmt.Errorf("could not copy object file into ar file: %w", err)
 231  		}
 232  
 233  		// File is not needed anymore.
 234  		objfile.Close()
 235  	}
 236  
 237  	// Create symbol indices.
 238  	indicesBuf := &bytes.Buffer{}
 239  	for _, sym := range symbolTable {
 240  		err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
 241  		if err != nil {
 242  			return err
 243  		}
 244  	}
 245  
 246  	// Overwrite placeholder indices.
 247  	_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
 248  	return err
 249  }
 250