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(©Buf, 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 }