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