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