master
1path: Path,
2index: File.Index,
3
4parsed: Parsed,
5
6symbols: std.ArrayList(Symbol),
7symbols_extra: std.ArrayList(u32),
8symbols_resolver: std.ArrayList(Elf.SymbolResolver.Index),
9
10aliases: ?std.ArrayList(u32),
11
12needed: bool,
13alive: bool,
14
15output_symtab_ctx: Elf.SymtabCtx,
16
17pub fn deinit(so: *SharedObject, gpa: Allocator) void {
18 gpa.free(so.path.sub_path);
19 so.parsed.deinit(gpa);
20 so.symbols.deinit(gpa);
21 so.symbols_extra.deinit(gpa);
22 so.symbols_resolver.deinit(gpa);
23 if (so.aliases) |*aliases| aliases.deinit(gpa);
24 so.* = undefined;
25}
26
27pub const Header = struct {
28 dynamic_table: []const elf.Elf64_Dyn,
29 soname_index: ?u32,
30 verdefnum: ?u32,
31
32 sections: []const elf.Elf64_Shdr,
33 dynsym_sect_index: ?u32,
34 versym_sect_index: ?u32,
35 verdef_sect_index: ?u32,
36
37 stat: Stat,
38 strtab: std.ArrayList(u8),
39
40 pub fn deinit(header: *Header, gpa: Allocator) void {
41 gpa.free(header.sections);
42 gpa.free(header.dynamic_table);
43 header.strtab.deinit(gpa);
44 header.* = undefined;
45 }
46
47 pub fn soname(header: Header) ?[]const u8 {
48 const i = header.soname_index orelse return null;
49 return Elf.stringTableLookup(header.strtab.items, i);
50 }
51};
52
53pub const Parsed = struct {
54 stat: Stat,
55 strtab: []const u8,
56 soname_index: ?u32,
57 sections: []const elf.Elf64_Shdr,
58
59 /// Nonlocal symbols only.
60 symtab: []const elf.Elf64_Sym,
61 /// Version symtab contains version strings of the symbols if present.
62 /// Nonlocal symbols only.
63 versyms: []const elf.Versym,
64 /// Nonlocal symbols only.
65 symbols: []const Parsed.Symbol,
66
67 verstrings: []const u32,
68
69 const Symbol = struct {
70 mangled_name: u32,
71 };
72
73 pub fn deinit(p: *Parsed, gpa: Allocator) void {
74 gpa.free(p.strtab);
75 gpa.free(p.sections);
76 gpa.free(p.symtab);
77 gpa.free(p.versyms);
78 gpa.free(p.symbols);
79 gpa.free(p.verstrings);
80 p.* = undefined;
81 }
82
83 pub fn versionString(p: Parsed, index: elf.Versym) [:0]const u8 {
84 return versionStringLookup(p.strtab, p.verstrings, index);
85 }
86
87 pub fn soname(p: Parsed) ?[]const u8 {
88 const i = p.soname_index orelse return null;
89 return Elf.stringTableLookup(p.strtab, i);
90 }
91};
92
93pub fn parseHeader(
94 gpa: Allocator,
95 diags: *Diags,
96 file_path: Path,
97 fs_file: std.fs.File,
98 stat: Stat,
99 target: *const std.Target,
100) !Header {
101 var ehdr: elf.Elf64_Ehdr = undefined;
102 {
103 const buf = mem.asBytes(&ehdr);
104 const amt = try fs_file.preadAll(buf, 0);
105 if (amt != buf.len) return error.UnexpectedEndOfFile;
106 }
107 if (!mem.eql(u8, ehdr.e_ident[0..4], "\x7fELF")) return error.BadMagic;
108 if (ehdr.e_ident[elf.EI.VERSION] != 1) return error.BadElfVersion;
109 if (ehdr.e_type != elf.ET.DYN) return error.NotSharedObject;
110
111 if (target.toElfMachine() != ehdr.e_machine)
112 return diags.failParse(file_path, "invalid ELF machine type: {s}", .{@tagName(ehdr.e_machine)});
113
114 const shoff = std.math.cast(usize, ehdr.e_shoff) orelse return error.Overflow;
115 const shnum = std.math.cast(u32, ehdr.e_shnum) orelse return error.Overflow;
116
117 const sections = try gpa.alloc(elf.Elf64_Shdr, shnum);
118 errdefer gpa.free(sections);
119 {
120 const buf = mem.sliceAsBytes(sections);
121 const amt = try fs_file.preadAll(buf, shoff);
122 if (amt != buf.len) return error.UnexpectedEndOfFile;
123 }
124
125 var dynsym_sect_index: ?u32 = null;
126 var dynamic_sect_index: ?u32 = null;
127 var versym_sect_index: ?u32 = null;
128 var verdef_sect_index: ?u32 = null;
129 for (sections, 0..) |shdr, i_usize| {
130 const i: u32 = @intCast(i_usize);
131 switch (shdr.sh_type) {
132 elf.SHT_DYNSYM => dynsym_sect_index = i,
133 elf.SHT_DYNAMIC => dynamic_sect_index = i,
134 elf.SHT_GNU_VERSYM => versym_sect_index = i,
135 elf.SHT_GNU_VERDEF => verdef_sect_index = i,
136 else => continue,
137 }
138 }
139
140 const dynamic_table: []elf.Elf64_Dyn = if (dynamic_sect_index) |index| dt: {
141 const shdr = sections[index];
142 const n = std.math.cast(usize, shdr.sh_size / @sizeOf(elf.Elf64_Dyn)) orelse return error.Overflow;
143 const dynamic_table = try gpa.alloc(elf.Elf64_Dyn, n);
144 errdefer gpa.free(dynamic_table);
145 const buf = mem.sliceAsBytes(dynamic_table);
146 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
147 if (amt != buf.len) return error.UnexpectedEndOfFile;
148 break :dt dynamic_table;
149 } else &.{};
150 errdefer gpa.free(dynamic_table);
151
152 var strtab: std.ArrayList(u8) = .empty;
153 errdefer strtab.deinit(gpa);
154
155 if (dynsym_sect_index) |index| {
156 const dynsym_shdr = sections[index];
157 if (dynsym_shdr.sh_link >= sections.len) return error.BadStringTableIndex;
158 const strtab_shdr = sections[dynsym_shdr.sh_link];
159 const n = std.math.cast(usize, strtab_shdr.sh_size) orelse return error.Overflow;
160 const buf = try strtab.addManyAsSlice(gpa, n);
161 const amt = try fs_file.preadAll(buf, strtab_shdr.sh_offset);
162 if (amt != buf.len) return error.UnexpectedEndOfFile;
163 }
164
165 var soname_index: ?u32 = null;
166 var verdefnum: ?u32 = null;
167 for (dynamic_table) |entry| switch (entry.d_tag) {
168 elf.DT_SONAME => {
169 if (entry.d_val >= strtab.items.len) return error.BadSonameIndex;
170 soname_index = @intCast(entry.d_val);
171 },
172 elf.DT_VERDEFNUM => {
173 verdefnum = @intCast(entry.d_val);
174 },
175 else => continue,
176 };
177
178 return .{
179 .dynamic_table = dynamic_table,
180 .soname_index = soname_index,
181 .verdefnum = verdefnum,
182 .sections = sections,
183 .dynsym_sect_index = dynsym_sect_index,
184 .versym_sect_index = versym_sect_index,
185 .verdef_sect_index = verdef_sect_index,
186 .strtab = strtab,
187 .stat = stat,
188 };
189}
190
191pub fn parse(
192 gpa: Allocator,
193 /// Moves resources from header. Caller may unconditionally deinit.
194 header: *Header,
195 fs_file: std.fs.File,
196) !Parsed {
197 const symtab = if (header.dynsym_sect_index) |index| st: {
198 const shdr = header.sections[index];
199 const n = std.math.cast(usize, shdr.sh_size / @sizeOf(elf.Elf64_Sym)) orelse return error.Overflow;
200 const symtab = try gpa.alloc(elf.Elf64_Sym, n);
201 errdefer gpa.free(symtab);
202 const buf = mem.sliceAsBytes(symtab);
203 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
204 if (amt != buf.len) return error.UnexpectedEndOfFile;
205 break :st symtab;
206 } else &.{};
207 defer gpa.free(symtab);
208
209 var verstrings: std.ArrayList(u32) = .empty;
210 defer verstrings.deinit(gpa);
211
212 if (header.verdef_sect_index) |shndx| {
213 const shdr = header.sections[shndx];
214 const verdefs = try Elf.preadAllAlloc(gpa, fs_file, shdr.sh_offset, shdr.sh_size);
215 defer gpa.free(verdefs);
216
217 var offset: u32 = 0;
218 while (true) {
219 const verdef = mem.bytesAsValue(elf.Verdef, verdefs[offset..][0..@sizeOf(elf.Verdef)]);
220 if (verdef.ndx == .UNSPECIFIED) return error.VerDefSymbolTooLarge;
221
222 if (verstrings.items.len <= @intFromEnum(verdef.ndx))
223 try verstrings.appendNTimes(gpa, 0, @intFromEnum(verdef.ndx) + 1 - verstrings.items.len);
224
225 const aux = mem.bytesAsValue(elf.Verdaux, verdefs[offset + verdef.aux ..][0..@sizeOf(elf.Verdaux)]);
226 verstrings.items[@intFromEnum(verdef.ndx)] = aux.name;
227
228 if (verdef.next == 0) break;
229 offset += verdef.next;
230 }
231 }
232
233 const versyms = if (header.versym_sect_index) |versym_sect_index| vs: {
234 const shdr = header.sections[versym_sect_index];
235 if (shdr.sh_size != symtab.len * @sizeOf(elf.Versym)) return error.BadVerSymSectionSize;
236
237 const versyms = try gpa.alloc(elf.Versym, symtab.len);
238 errdefer gpa.free(versyms);
239 const buf = mem.sliceAsBytes(versyms);
240 const amt = try fs_file.preadAll(buf, shdr.sh_offset);
241 if (amt != buf.len) return error.UnexpectedEndOfFile;
242 break :vs versyms;
243 } else &.{};
244 defer gpa.free(versyms);
245
246 var nonlocal_esyms: std.ArrayList(elf.Elf64_Sym) = .empty;
247 defer nonlocal_esyms.deinit(gpa);
248
249 var nonlocal_versyms: std.ArrayList(elf.Versym) = .empty;
250 defer nonlocal_versyms.deinit(gpa);
251
252 var nonlocal_symbols: std.ArrayList(Parsed.Symbol) = .empty;
253 defer nonlocal_symbols.deinit(gpa);
254
255 var strtab = header.strtab;
256 header.strtab = .empty;
257 defer strtab.deinit(gpa);
258
259 for (symtab, 0..) |sym, i| {
260 const ver: elf.Versym = if (versyms.len == 0 or sym.st_shndx == elf.SHN_UNDEF)
261 .GLOBAL
262 else
263 .{ .VERSION = versyms[i].VERSION, .HIDDEN = false };
264
265 // https://github.com/ziglang/zig/issues/21678
266 //if (ver == .LOCAL) continue;
267 if (@as(u16, @bitCast(ver)) == 0) continue;
268
269 try nonlocal_esyms.ensureUnusedCapacity(gpa, 1);
270 try nonlocal_versyms.ensureUnusedCapacity(gpa, 1);
271 try nonlocal_symbols.ensureUnusedCapacity(gpa, 1);
272
273 const name = Elf.stringTableLookup(strtab.items, sym.st_name);
274 const is_default = versyms.len == 0 or !versyms[i].HIDDEN;
275 const mangled_name = if (is_default) sym.st_name else mn: {
276 const off: u32 = @intCast(strtab.items.len);
277 const version_string = versionStringLookup(strtab.items, verstrings.items, versyms[i]);
278 try strtab.ensureUnusedCapacity(gpa, name.len + version_string.len + 2);
279 // Reload since the string table might have been resized.
280 const name2 = Elf.stringTableLookup(strtab.items, sym.st_name);
281 const version_string2 = versionStringLookup(strtab.items, verstrings.items, versyms[i]);
282 strtab.appendSliceAssumeCapacity(name2);
283 strtab.appendAssumeCapacity('@');
284 strtab.appendSliceAssumeCapacity(version_string2);
285 strtab.appendAssumeCapacity(0);
286 break :mn off;
287 };
288
289 nonlocal_esyms.appendAssumeCapacity(sym);
290 nonlocal_versyms.appendAssumeCapacity(ver);
291 nonlocal_symbols.appendAssumeCapacity(.{
292 .mangled_name = mangled_name,
293 });
294 }
295
296 const sections = header.sections;
297 header.sections = &.{};
298 errdefer gpa.free(sections);
299
300 return .{
301 .sections = sections,
302 .stat = header.stat,
303 .soname_index = header.soname_index,
304 .strtab = try strtab.toOwnedSlice(gpa),
305 .symtab = try nonlocal_esyms.toOwnedSlice(gpa),
306 .versyms = try nonlocal_versyms.toOwnedSlice(gpa),
307 .symbols = try nonlocal_symbols.toOwnedSlice(gpa),
308 .verstrings = try verstrings.toOwnedSlice(gpa),
309 };
310}
311
312pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) !void {
313 const gpa = elf_file.base.comp.gpa;
314
315 for (self.parsed.symtab, self.symbols_resolver.items, 0..) |esym, *resolv, i| {
316 const gop = try elf_file.resolver.getOrPut(gpa, .{
317 .index = @intCast(i),
318 .file = self.index,
319 }, elf_file);
320 if (!gop.found_existing) {
321 gop.ref.* = .{ .index = 0, .file = 0 };
322 }
323 resolv.* = gop.index;
324
325 if (esym.st_shndx == elf.SHN_UNDEF) continue;
326 if (elf_file.symbol(gop.ref.*) == null) {
327 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
328 continue;
329 }
330
331 if (self.asFile().symbolRank(esym, false) < elf_file.symbol(gop.ref.*).?.symbolRank(elf_file)) {
332 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
333 }
334 }
335}
336
337pub fn markLive(self: *SharedObject, elf_file: *Elf) void {
338 for (self.parsed.symtab, 0..) |esym, i| {
339 if (esym.st_shndx != elf.SHN_UNDEF) continue;
340
341 const ref = self.resolveSymbol(@intCast(i), elf_file);
342 const sym = elf_file.symbol(ref) orelse continue;
343 const file = sym.file(elf_file).?;
344 const should_drop = switch (file) {
345 .shared_object => |sh| !sh.needed and esym.st_bind() == elf.STB_WEAK,
346 else => false,
347 };
348 if (!should_drop and !file.isAlive()) {
349 file.setAlive();
350 file.markLive(elf_file);
351 }
352 }
353}
354
355pub fn markImportExports(self: *SharedObject, elf_file: *Elf) void {
356 for (0..self.symbols.items.len) |i| {
357 const ref = self.resolveSymbol(@intCast(i), elf_file);
358 const ref_sym = elf_file.symbol(ref) orelse continue;
359 const ref_file = ref_sym.file(elf_file).?;
360 const vis: elf.STV = @enumFromInt(@as(u3, @truncate(ref_sym.elfSym(elf_file).st_other)));
361 if (ref_file != .shared_object and vis != .HIDDEN) ref_sym.flags.@"export" = true;
362 }
363}
364
365pub fn updateSymtabSize(self: *SharedObject, elf_file: *Elf) void {
366 for (self.symbols.items, self.symbols_resolver.items) |*global, resolv| {
367 const ref = elf_file.resolver.get(resolv).?;
368 const ref_sym = elf_file.symbol(ref) orelse continue;
369 if (ref_sym.file(elf_file).?.index() != self.index) continue;
370 if (global.isLocal(elf_file)) continue;
371 global.flags.output_symtab = true;
372 global.addExtra(.{ .symtab = self.output_symtab_ctx.nglobals }, elf_file);
373 self.output_symtab_ctx.nglobals += 1;
374 self.output_symtab_ctx.strsize += @as(u32, @intCast(global.name(elf_file).len)) + 1;
375 }
376}
377
378pub fn writeSymtab(self: *SharedObject, elf_file: *Elf) void {
379 for (self.symbols.items, self.symbols_resolver.items) |global, resolv| {
380 const ref = elf_file.resolver.get(resolv).?;
381 const ref_sym = elf_file.symbol(ref) orelse continue;
382 if (ref_sym.file(elf_file).?.index() != self.index) continue;
383 const idx = global.outputSymtabIndex(elf_file) orelse continue;
384 const st_name = @as(u32, @intCast(elf_file.strtab.items.len));
385 elf_file.strtab.appendSliceAssumeCapacity(global.name(elf_file));
386 elf_file.strtab.appendAssumeCapacity(0);
387 const out_sym = &elf_file.symtab.items[idx];
388 out_sym.st_name = st_name;
389 global.setOutputSym(elf_file, out_sym);
390 }
391}
392
393pub fn versionString(self: SharedObject, index: elf.Versym) [:0]const u8 {
394 return self.parsed.versionString(index);
395}
396
397fn versionStringLookup(strtab: []const u8, verstrings: []const u32, index: elf.Versym) [:0]const u8 {
398 const off = verstrings[index.VERSION];
399 return Elf.stringTableLookup(strtab, off);
400}
401
402pub fn asFile(self: *SharedObject) File {
403 return .{ .shared_object = self };
404}
405
406pub fn soname(self: *SharedObject) []const u8 {
407 return self.parsed.soname() orelse self.path.basename();
408}
409
410pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void {
411 assert(self.aliases == null);
412
413 const SortAlias = struct {
414 so: *SharedObject,
415 ef: *Elf,
416
417 pub fn lessThan(ctx: @This(), lhs: Symbol.Index, rhs: Symbol.Index) bool {
418 const lhs_sym = ctx.so.symbols.items[lhs].elfSym(ctx.ef);
419 const rhs_sym = ctx.so.symbols.items[rhs].elfSym(ctx.ef);
420 return lhs_sym.st_value < rhs_sym.st_value;
421 }
422 };
423
424 const comp = elf_file.base.comp;
425 const gpa = comp.gpa;
426 var aliases = std.array_list.Managed(Symbol.Index).init(gpa);
427 defer aliases.deinit();
428 try aliases.ensureTotalCapacityPrecise(self.symbols.items.len);
429
430 for (self.symbols_resolver.items, 0..) |resolv, index| {
431 const ref = elf_file.resolver.get(resolv).?;
432 const ref_sym = elf_file.symbol(ref) orelse continue;
433 if (ref_sym.file(elf_file).?.index() != self.index) continue;
434 aliases.appendAssumeCapacity(@intCast(index));
435 }
436
437 mem.sort(u32, aliases.items, SortAlias{ .so = self, .ef = elf_file }, SortAlias.lessThan);
438
439 self.aliases = aliases.moveToUnmanaged();
440}
441
442pub fn symbolAliases(self: *SharedObject, index: u32, elf_file: *Elf) []const u32 {
443 assert(self.aliases != null);
444
445 const symbol = self.symbols.items[index].elfSym(elf_file);
446 const aliases = self.aliases.?;
447
448 const start = for (aliases.items, 0..) |alias, i| {
449 const alias_sym = self.symbols.items[alias].elfSym(elf_file);
450 if (symbol.st_value == alias_sym.st_value) break i;
451 } else aliases.items.len;
452
453 const end = for (aliases.items[start..], 0..) |alias, i| {
454 const alias_sym = self.symbols.items[alias].elfSym(elf_file);
455 if (symbol.st_value < alias_sym.st_value) break i + start;
456 } else aliases.items.len;
457
458 return aliases.items[start..end];
459}
460
461pub fn getString(self: SharedObject, off: u32) [:0]const u8 {
462 return Elf.stringTableLookup(self.parsed.strtab, off);
463}
464
465pub fn resolveSymbol(self: SharedObject, index: Symbol.Index, elf_file: *Elf) Elf.Ref {
466 const resolv = self.symbols_resolver.items[index];
467 return elf_file.resolver.get(resolv).?;
468}
469
470pub fn addSymbolAssumeCapacity(self: *SharedObject) Symbol.Index {
471 const index: Symbol.Index = @intCast(self.symbols.items.len);
472 self.symbols.appendAssumeCapacity(.{ .file_index = self.index });
473 return index;
474}
475
476pub fn addSymbolExtraAssumeCapacity(self: *SharedObject, extra: Symbol.Extra) u32 {
477 const index: u32 = @intCast(self.symbols_extra.items.len);
478 const fields = @typeInfo(Symbol.Extra).@"struct".fields;
479 inline for (fields) |field| {
480 self.symbols_extra.appendAssumeCapacity(switch (field.type) {
481 u32 => @field(extra, field.name),
482 else => @compileError("bad field type"),
483 });
484 }
485 return index;
486}
487
488pub fn symbolExtra(self: *SharedObject, index: u32) Symbol.Extra {
489 const fields = @typeInfo(Symbol.Extra).@"struct".fields;
490 var i: usize = index;
491 var result: Symbol.Extra = undefined;
492 inline for (fields) |field| {
493 @field(result, field.name) = switch (field.type) {
494 u32 => self.symbols_extra.items[i],
495 else => @compileError("bad field type"),
496 };
497 i += 1;
498 }
499 return result;
500}
501
502pub fn setSymbolExtra(self: *SharedObject, index: u32, extra: Symbol.Extra) void {
503 const fields = @typeInfo(Symbol.Extra).@"struct".fields;
504 inline for (fields, 0..) |field, i| {
505 self.symbols_extra.items[index + i] = switch (field.type) {
506 u32 => @field(extra, field.name),
507 else => @compileError("bad field type"),
508 };
509 }
510}
511
512pub fn fmtSymtab(self: SharedObject, elf_file: *Elf) std.fmt.Alt(Format, Format.symtab) {
513 return .{ .data = .{
514 .shared = self,
515 .elf_file = elf_file,
516 } };
517}
518
519const Format = struct {
520 shared: SharedObject,
521 elf_file: *Elf,
522
523 fn symtab(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void {
524 const shared = f.shared;
525 const elf_file = f.elf_file;
526 try writer.writeAll(" globals\n");
527 for (shared.symbols.items, 0..) |sym, i| {
528 const ref = shared.resolveSymbol(@intCast(i), elf_file);
529 if (elf_file.symbol(ref)) |ref_sym| {
530 try writer.print(" {f}\n", .{ref_sym.fmt(elf_file)});
531 } else {
532 try writer.print(" {s} : unclaimed\n", .{sym.name(elf_file)});
533 }
534 }
535 }
536};
537
538const SharedObject = @This();
539
540const std = @import("std");
541const assert = std.debug.assert;
542const elf = std.elf;
543const log = std.log.scoped(.elf);
544const mem = std.mem;
545const Path = std.Build.Cache.Path;
546const Stat = std.Build.Cache.File.Stat;
547const Allocator = mem.Allocator;
548
549const Elf = @import("../Elf.zig");
550const File = @import("file.zig").File;
551const Symbol = @import("Symbol.zig");
552const Diags = @import("../../link.zig").Diags;