github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/ar.go (about)

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