1const Object = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const assert = std.debug.assert;
6const eh_frame = @import("eh_frame.zig");
7const elf = std.elf;
8const fs = std.fs;
9const log = std.log.scoped(.link);
10const math = std.math;
11const mem = std.mem;
12const Path = std.Build.Cache.Path;
13const Allocator = std.mem.Allocator;
14
15const Diags = @import("../../link.zig").Diags;
16const Archive = @import("Archive.zig");
17const Atom = @import("Atom.zig");
18const AtomList = @import("AtomList.zig");
19const Cie = eh_frame.Cie;
20const Elf = @import("../Elf.zig");
21const Fde = eh_frame.Fde;
22const File = @import("file.zig").File;
23const Merge = @import("Merge.zig");
24const Symbol = @import("Symbol.zig");
25const Alignment = Atom.Alignment;
26const riscv = @import("../riscv.zig");
27
28archive: ?InArchive = null,
29/// Archive files cannot contain subdirectories, so only the basename is needed
30/// for output. However, the full path is kept for error reporting.
31path: Path,
32file_handle: File.HandleIndex,
33index: File.Index,
34
35header: ?elf.Elf64_Ehdr = null,
36shdrs: std.ArrayList(elf.Elf64_Shdr) = .empty,
37
38symtab: std.ArrayList(elf.Elf64_Sym) = .empty,
39strtab: std.ArrayList(u8) = .empty,
40first_global: ?Symbol.Index = null,
41symbols: std.ArrayList(Symbol) = .empty,
42symbols_extra: std.ArrayList(u32) = .empty,
43symbols_resolver: std.ArrayList(Elf.SymbolResolver.Index) = .empty,
44relocs: std.ArrayList(elf.Elf64_Rela) = .empty,
45
46atoms: std.ArrayList(Atom) = .empty,
47atoms_indexes: std.ArrayList(Atom.Index) = .empty,
48atoms_extra: std.ArrayList(u32) = .empty,
49
50groups: std.ArrayList(Elf.Group) = .empty,
51group_data: std.ArrayList(u32) = .empty,
52
53input_merge_sections: std.ArrayList(Merge.InputSection) = .empty,
54input_merge_sections_indexes: std.ArrayList(Merge.InputSection.Index) = .empty,
55
56fdes: std.ArrayList(Fde) = .empty,
57cies: std.ArrayList(Cie) = .empty,
58eh_frame_data: std.ArrayList(u8) = .empty,
59
60alive: bool = true,
61dirty: bool = true,
62num_dynrelocs: u32 = 0,
63
64output_symtab_ctx: Elf.SymtabCtx = .{},
65output_ar_state: Archive.ArState = .{},
66
67pub fn deinit(self: *Object, gpa: Allocator) void {
68 if (self.archive) |*ar| gpa.free(ar.path.sub_path);
69 gpa.free(self.path.sub_path);
70 self.shdrs.deinit(gpa);
71 self.symtab.deinit(gpa);
72 self.strtab.deinit(gpa);
73 self.symbols.deinit(gpa);
74 self.symbols_extra.deinit(gpa);
75 self.symbols_resolver.deinit(gpa);
76 self.atoms.deinit(gpa);
77 self.atoms_indexes.deinit(gpa);
78 self.atoms_extra.deinit(gpa);
79 self.groups.deinit(gpa);
80 self.group_data.deinit(gpa);
81 self.relocs.deinit(gpa);
82 self.fdes.deinit(gpa);
83 self.cies.deinit(gpa);
84 self.eh_frame_data.deinit(gpa);
85 for (self.input_merge_sections.items) |*isec| {
86 isec.deinit(gpa);
87 }
88 self.input_merge_sections.deinit(gpa);
89 self.input_merge_sections_indexes.deinit(gpa);
90}
91
92pub fn parse(
93 self: *Object,
94 gpa: Allocator,
95 io: Io,
96 diags: *Diags,
97 /// For error reporting purposes only.
98 path: Path,
99 handle: Io.File,
100 target: *const std.Target,
101 debug_fmt_strip: bool,
102 default_sym_version: elf.Versym,
103) !void {
104 // Append null input merge section
105 try self.input_merge_sections.append(gpa, .{});
106 // Allocate atom index 0 to null atom
107 try self.atoms.append(gpa, .{ .extra_index = try self.addAtomExtra(gpa, .{}) });
108
109 try self.initAtoms(gpa, io, diags, path, handle, debug_fmt_strip, target);
110 try self.initSymbols(gpa, default_sym_version);
111
112 for (self.shdrs.items, 0..) |shdr, i| {
113 const atom_ptr = self.atom(self.atoms_indexes.items[i]) orelse continue;
114 if (!atom_ptr.alive) continue;
115 if ((target.cpu.arch == .x86_64 and shdr.sh_type == elf.SHT_X86_64_UNWIND) or
116 mem.eql(u8, self.getString(atom_ptr.name_offset), ".eh_frame"))
117 {
118 try self.parseEhFrame(gpa, io, handle, @intCast(i), target);
119 }
120 }
121}
122
123pub fn parseCommon(
124 self: *Object,
125 gpa: Allocator,
126 io: Io,
127 diags: *Diags,
128 path: Path,
129 handle: Io.File,
130 target: *const std.Target,
131) !void {
132 const offset = if (self.archive) |ar| ar.offset else 0;
133 const file_size = (try handle.stat(io)).size;
134
135 const header_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset, @sizeOf(elf.Elf64_Ehdr));
136 defer gpa.free(header_buffer);
137 self.header = @as(*align(1) const elf.Elf64_Ehdr, @ptrCast(header_buffer)).*;
138 if (!mem.eql(u8, self.header.?.e_ident[0..4], elf.MAGIC)) {
139 return diags.failParse(path, "not an ELF file", .{});
140 }
141
142 const em = target.toElfMachine();
143 if (em != self.header.?.e_machine) {
144 return diags.failParse(path, "invalid ELF machine type: {s}", .{
145 @tagName(self.header.?.e_machine),
146 });
147 }
148 try validateEFlags(diags, path, target, self.header.?.e_flags);
149
150 if (self.header.?.e_shnum == 0) return;
151
152 const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
153 const shnum = math.cast(usize, self.header.?.e_shnum) orelse return error.Overflow;
154 const shsize = shnum * @sizeOf(elf.Elf64_Shdr);
155 if (file_size < offset + shoff or file_size < offset + shoff + shsize) {
156 return diags.failParse(path, "corrupt header: section header table extends past the end of file", .{});
157 }
158
159 const shdrs_buffer = try Elf.preadAllAlloc(gpa, io, handle, offset + shoff, shsize);
160 defer gpa.free(shdrs_buffer);
161 const shdrs = @as([*]align(1) const elf.Elf64_Shdr, @ptrCast(shdrs_buffer.ptr))[0..shnum];
162 try self.shdrs.appendUnalignedSlice(gpa, shdrs);
163
164 for (self.shdrs.items) |shdr| {
165 if (shdr.sh_type != elf.SHT_NOBITS) {
166 if (file_size < offset + shdr.sh_offset or file_size < offset + shdr.sh_offset + shdr.sh_size) {
167 return diags.failParse(path, "corrupt section: extends past the end of file", .{});
168 }
169 }
170 }
171
172 const shstrtab = try self.preadShdrContentsAlloc(gpa, io, handle, self.header.?.e_shstrndx);
173 defer gpa.free(shstrtab);
174 for (self.shdrs.items) |shdr| {
175 if (shdr.sh_name >= shstrtab.len) {
176 return diags.failParse(path, "corrupt section name offset", .{});
177 }
178 }
179 try self.strtab.appendSlice(gpa, shstrtab);
180
181 const symtab_index = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
182 elf.SHT_SYMTAB => break @as(u32, @intCast(i)),
183 else => {},
184 } else null;
185
186 if (symtab_index) |index| {
187 const shdr = self.shdrs.items[index];
188 self.first_global = shdr.sh_info;
189
190 const raw_symtab = try self.preadShdrContentsAlloc(gpa, io, handle, index);
191 defer gpa.free(raw_symtab);
192 const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {
193 return diags.failParse(path, "symbol table not evenly divisible", .{});
194 };
195 const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
196
197 const strtab_bias = @as(u32, @intCast(self.strtab.items.len));
198 const strtab = try self.preadShdrContentsAlloc(gpa, io, handle, shdr.sh_link);
199 defer gpa.free(strtab);
200 try self.strtab.appendSlice(gpa, strtab);
201
202 try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
203 for (symtab) |sym| {
204 const out_sym = self.symtab.addOneAssumeCapacity();
205 out_sym.* = sym;
206 out_sym.st_name = if (sym.st_name == 0 and sym.st_type() == elf.STT_SECTION)
207 shdrs[sym.st_shndx].sh_name
208 else
209 sym.st_name + strtab_bias;
210 }
211 }
212}
213
214pub fn validateEFlags(
215 diags: *Diags,
216 path: Path,
217 target: *const std.Target,
218 e_flags: elf.Word,
219) !void {
220 switch (target.cpu.arch) {
221 .riscv64, .riscv64be => {
222 const flags: riscv.Eflags = @bitCast(e_flags);
223 var any_errors: bool = false;
224
225 // For an input object to target an ABI that the target CPU doesn't have enabled
226 // is invalid, and will throw an error.
227
228 // Invalid when
229 // 1. The input uses C and we do not.
230 if (flags.rvc and !target.cpu.has(.riscv, .c)) {
231 any_errors = true;
232 diags.addParseError(
233 path,
234 "cannot link object file targeting the C feature without having the C feature enabled",
235 .{},
236 );
237 }
238
239 // Invalid when
240 // 1. We use E and the input does not.
241 // 2. The input uses E and we do not.
242 if (target.cpu.has(.riscv, .e) != flags.rve) {
243 any_errors = true;
244 diags.addParseError(
245 path,
246 "{s}",
247 .{
248 if (flags.rve)
249 "cannot link object file targeting the E feature without having the E feature enabled"
250 else
251 "cannot link object file not targeting the E feature while having the E feature enabled",
252 },
253 );
254 }
255
256 // Invalid when
257 // 1. We use total store order and the input does not.
258 // 2. The input uses total store order and we do not.
259 if (flags.tso != target.cpu.has(.riscv, .ztso)) {
260 any_errors = true;
261 diags.addParseError(
262 path,
263 "cannot link object file targeting the TSO memory model without having the ztso feature enabled",
264 .{},
265 );
266 }
267
268 const fabi: riscv.Eflags.FloatAbi =
269 if (target.cpu.has(.riscv, .d))
270 .double
271 else if (target.cpu.has(.riscv, .f))
272 .single
273 else
274 .soft;
275
276 if (flags.fabi != fabi) {
277 any_errors = true;
278 diags.addParseError(
279 path,
280 "cannot link object file targeting a different floating-point ABI. targeting {s}, found {s}",
281 .{ @tagName(fabi), @tagName(flags.fabi) },
282 );
283 }
284
285 if (any_errors) return error.AlreadyReported;
286 },
287 else => {},
288 }
289}
290
291fn initAtoms(
292 self: *Object,
293 gpa: Allocator,
294 io: Io,
295 diags: *Diags,
296 path: Path,
297 handle: Io.File,
298 debug_fmt_strip: bool,
299 target: *const std.Target,
300) !void {
301 const shdrs = self.shdrs.items;
302 try self.atoms.ensureTotalCapacityPrecise(gpa, shdrs.len);
303 try self.atoms_extra.ensureTotalCapacityPrecise(gpa, shdrs.len * @sizeOf(Atom.Extra));
304 try self.atoms_indexes.ensureTotalCapacityPrecise(gpa, shdrs.len);
305 try self.atoms_indexes.resize(gpa, shdrs.len);
306 @memset(self.atoms_indexes.items, 0);
307
308 for (shdrs, 0..) |shdr, i| {
309 if (shdr.sh_flags & elf.SHF_EXCLUDE != 0 and
310 shdr.sh_flags & elf.SHF_ALLOC == 0 and
311 shdr.sh_type != elf.SHT_LLVM_ADDRSIG) continue;
312
313 switch (shdr.sh_type) {
314 elf.SHT_GROUP => {
315 if (shdr.sh_info >= self.symtab.items.len) {
316 // TODO convert into an error
317 log.debug("{f}: invalid symbol index in sh_info", .{self.fmtPath()});
318 continue;
319 }
320 const group_info_sym = self.symtab.items[shdr.sh_info];
321 const group_signature = blk: {
322 if (group_info_sym.st_name == 0 and group_info_sym.st_type() == elf.STT_SECTION) {
323 const sym_shdr = shdrs[group_info_sym.st_shndx];
324 break :blk sym_shdr.sh_name;
325 }
326 break :blk group_info_sym.st_name;
327 };
328
329 const shndx: u32 = @intCast(i);
330 const group_raw_data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
331 defer gpa.free(group_raw_data);
332 const group_nmembers = math.divExact(usize, group_raw_data.len, @sizeOf(u32)) catch {
333 return diags.failParse(path, "corrupt section group: not evenly divisible ", .{});
334 };
335 if (group_nmembers == 0) {
336 return diags.failParse(path, "corrupt section group: empty section", .{});
337 }
338 const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers];
339
340 switch (group_members[0]) {
341 0, elf.GRP_COMDAT => {
342 const group_start: u32 = @intCast(self.group_data.items.len);
343 try self.group_data.appendUnalignedSlice(gpa, group_members[1..]);
344
345 self.group(try self.addGroup(gpa)).* = .{
346 .signature_off = group_signature,
347 .file_index = self.index,
348 .shndx = shndx,
349 .members_start = group_start,
350 .members_len = @intCast(group_nmembers - 1),
351 .is_comdat = group_members[0] == elf.GRP_COMDAT,
352 };
353 },
354 else => return diags.failParse(path, "corrupt section group: unknown SHT_GROUP format", .{}),
355 }
356 },
357
358 elf.SHT_SYMTAB_SHNDX => @panic("TODO SHT_SYMTAB_SHNDX"),
359
360 elf.SHT_NULL,
361 elf.SHT_REL,
362 elf.SHT_RELA,
363 elf.SHT_SYMTAB,
364 elf.SHT_STRTAB,
365 => {},
366
367 else => {
368 const shndx: u32 = @intCast(i);
369 if (self.skipShdr(shndx, debug_fmt_strip)) continue;
370 const size, const alignment = if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) blk: {
371 const data = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
372 defer gpa.free(data);
373 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
374 break :blk .{ chdr.ch_size, Alignment.fromNonzeroByteUnits(chdr.ch_addralign) };
375 } else .{ shdr.sh_size, Alignment.fromNonzeroByteUnits(shdr.sh_addralign) };
376 const atom_index = self.addAtomAssumeCapacity(.{
377 .name = shdr.sh_name,
378 .shndx = shndx,
379 .size = size,
380 .alignment = alignment,
381 });
382 self.atoms_indexes.items[shndx] = atom_index;
383 },
384 }
385 }
386
387 // Parse relocs sections if any.
388 for (shdrs, 0..) |shdr, i| switch (shdr.sh_type) {
389 elf.SHT_REL, elf.SHT_RELA => {
390 const atom_index = self.atoms_indexes.items[shdr.sh_info];
391 if (self.atom(atom_index)) |atom_ptr| {
392 const relocs = try self.preadRelocsAlloc(gpa, io, handle, @intCast(i));
393 defer gpa.free(relocs);
394 atom_ptr.relocs_section_index = @intCast(i);
395 const rel_index: u32 = @intCast(self.relocs.items.len);
396 const rel_count: u32 = @intCast(relocs.len);
397 self.setAtomFields(atom_ptr, .{ .rel_index = rel_index, .rel_count = rel_count });
398 try self.relocs.appendUnalignedSlice(gpa, relocs);
399 if (target.cpu.arch.isRiscv64()) {
400 sortRelocs(self.relocs.items[rel_index..][0..rel_count]);
401 }
402 }
403 },
404 else => {},
405 };
406}
407
408fn skipShdr(self: *Object, index: u32, debug_fmt_strip: bool) bool {
409 const shdr = self.shdrs.items[index];
410 const name = self.getString(shdr.sh_name);
411 const ignore = blk: {
412 if (mem.startsWith(u8, name, ".note")) break :blk true;
413 if (mem.startsWith(u8, name, ".llvm_addrsig")) break :blk true;
414 if (mem.startsWith(u8, name, ".riscv.attributes")) break :blk true; // TODO: riscv attributes
415 if (debug_fmt_strip and shdr.sh_flags & elf.SHF_ALLOC == 0 and
416 mem.startsWith(u8, name, ".debug")) break :blk true;
417 break :blk false;
418 };
419 return ignore;
420}
421
422fn initSymbols(
423 self: *Object,
424 gpa: Allocator,
425 default_sym_version: elf.Versym,
426) !void {
427 const first_global = self.first_global orelse self.symtab.items.len;
428 const nglobals = self.symtab.items.len - first_global;
429
430 try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.items.len);
431 try self.symbols_extra.ensureTotalCapacityPrecise(gpa, self.symtab.items.len * @sizeOf(Symbol.Extra));
432 try self.symbols_resolver.ensureTotalCapacityPrecise(gpa, nglobals);
433 self.symbols_resolver.resize(gpa, nglobals) catch unreachable;
434 @memset(self.symbols_resolver.items, 0);
435
436 for (self.symtab.items, 0..) |sym, i| {
437 const index = self.addSymbolAssumeCapacity();
438 const sym_ptr = &self.symbols.items[index];
439 sym_ptr.value = @intCast(sym.st_value);
440 sym_ptr.name_offset = sym.st_name;
441 sym_ptr.esym_index = @intCast(i);
442 sym_ptr.extra_index = self.addSymbolExtraAssumeCapacity(.{});
443 sym_ptr.version_index = if (i >= first_global) default_sym_version else .LOCAL;
444 sym_ptr.flags.weak = sym.st_bind() == elf.STB_WEAK;
445 if (sym.st_shndx != elf.SHN_ABS and sym.st_shndx != elf.SHN_COMMON) {
446 sym_ptr.ref = .{ .index = self.atoms_indexes.items[sym.st_shndx], .file = self.index };
447 }
448 }
449}
450
451fn parseEhFrame(
452 self: *Object,
453 gpa: Allocator,
454 io: Io,
455 handle: Io.File,
456 shndx: u32,
457 target: *const std.Target,
458) !void {
459 const relocs_shndx = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
460 elf.SHT_RELA => if (shdr.sh_info == shndx) break @as(u32, @intCast(i)),
461 else => {},
462 } else null;
463
464 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
465 defer gpa.free(raw);
466 const data_start: u32 = @intCast(self.eh_frame_data.items.len);
467 try self.eh_frame_data.appendSlice(gpa, raw);
468 const relocs = if (relocs_shndx) |index|
469 try self.preadRelocsAlloc(gpa, io, handle, index)
470 else
471 &[0]elf.Elf64_Rela{};
472 defer gpa.free(relocs);
473 const rel_start: u32 = @intCast(self.relocs.items.len);
474 try self.relocs.appendUnalignedSlice(gpa, relocs);
475
476 // We expect relocations to be sorted by r_offset as per this comment in mold linker:
477 // https://github.com/rui314/mold/blob/8e4f7b53832d8af4f48a633a8385cbc932d1944e/src/input-files.cc#L653
478 // Except for RISCV and Loongarch which do not seem to be uphold this convention.
479 if (target.cpu.arch.isRiscv64()) {
480 sortRelocs(self.relocs.items[rel_start..][0..relocs.len]);
481 }
482 const fdes_start = self.fdes.items.len;
483 const cies_start = self.cies.items.len;
484
485 var it = eh_frame.Iterator{ .data = raw };
486 while (try it.next()) |rec| {
487 const rel_range = filterRelocs(self.relocs.items[rel_start..][0..relocs.len], rec.offset, rec.size + 4);
488 switch (rec.tag) {
489 .cie => try self.cies.append(gpa, .{
490 .offset = data_start + rec.offset,
491 .size = rec.size,
492 .rel_index = rel_start + @as(u32, @intCast(rel_range.start)),
493 .rel_num = @intCast(rel_range.len),
494 .input_section_index = shndx,
495 .file_index = self.index,
496 }),
497 .fde => {
498 if (rel_range.len == 0) {
499 // No relocs for an FDE means we cannot associate this FDE to an Atom
500 // so we skip it. According to mold source code
501 // (https://github.com/rui314/mold/blob/a3e69502b0eaf1126d6093e8ea5e6fdb95219811/src/input-files.cc#L525-L528)
502 // this can happen for object files built with -r flag by the linker.
503 continue;
504 }
505 try self.fdes.append(gpa, .{
506 .offset = data_start + rec.offset,
507 .size = rec.size,
508 .cie_index = undefined,
509 .rel_index = rel_start + @as(u32, @intCast(rel_range.start)),
510 .rel_num = @intCast(rel_range.len),
511 .input_section_index = shndx,
512 .file_index = self.index,
513 });
514 },
515 }
516 }
517
518 // Tie each FDE to its CIE
519 for (self.fdes.items[fdes_start..]) |*fde| {
520 const cie_ptr = fde.offset + 4 - fde.ciePointer(self);
521 const cie_index = for (self.cies.items[cies_start..], cies_start..) |cie, cie_index| {
522 if (cie.offset == cie_ptr) break @as(u32, @intCast(cie_index));
523 } else {
524 // TODO convert into an error
525 log.debug("{f}: no matching CIE found for FDE at offset {x}", .{ self.fmtPath(), fde.offset });
526 continue;
527 };
528 fde.cie_index = cie_index;
529 }
530
531 // Tie each FDE record to its matching atom
532 const SortFdes = struct {
533 pub fn lessThan(ctx: *Object, lhs: Fde, rhs: Fde) bool {
534 const lhs_atom = lhs.atom(ctx);
535 const rhs_atom = rhs.atom(ctx);
536 return Atom.priorityLookup(ctx.index, lhs_atom.input_section_index) < Atom.priorityLookup(ctx.index, rhs_atom.input_section_index);
537 }
538 };
539 mem.sort(Fde, self.fdes.items[fdes_start..], self, SortFdes.lessThan);
540
541 // Create a back-link from atom to FDEs
542 var i: u32 = @intCast(fdes_start);
543 while (i < self.fdes.items.len) {
544 const fde = self.fdes.items[i];
545 const atom_ptr = fde.atom(self);
546 const start = i;
547 i += 1;
548 while (i < self.fdes.items.len) : (i += 1) {
549 const next_fde = self.fdes.items[i];
550 if (atom_ptr.atom_index != next_fde.atom(self).atom_index) break;
551 }
552 self.setAtomFields(atom_ptr, .{ .fde_start = start, .fde_count = i - start });
553 }
554}
555
556fn sortRelocs(relocs: []elf.Elf64_Rela) void {
557 const sortFn = struct {
558 fn lessThan(c: void, lhs: elf.Elf64_Rela, rhs: elf.Elf64_Rela) bool {
559 _ = c;
560 return lhs.r_offset < rhs.r_offset;
561 }
562 }.lessThan;
563 mem.sort(elf.Elf64_Rela, relocs, {}, sortFn);
564}
565
566fn filterRelocs(
567 relocs: []const elf.Elf64_Rela,
568 start: u64,
569 len: u64,
570) struct { start: u64, len: u64 } {
571 const Predicate = struct {
572 value: u64,
573
574 pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool {
575 return rel.r_offset < self.value;
576 }
577 };
578 const LPredicate = struct {
579 value: u64,
580
581 pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool {
582 return rel.r_offset >= self.value;
583 }
584 };
585
586 const f_start = Elf.bsearch(elf.Elf64_Rela, relocs, Predicate{ .value = start });
587 const f_len = Elf.lsearch(elf.Elf64_Rela, relocs[f_start..], LPredicate{ .value = start + len });
588
589 return .{ .start = f_start, .len = f_len };
590}
591
592pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void {
593 const comp = elf_file.base.comp;
594 const gpa = comp.gpa;
595 for (self.atoms_indexes.items) |atom_index| {
596 const atom_ptr = self.atom(atom_index) orelse continue;
597 if (!atom_ptr.alive) continue;
598 const shdr = atom_ptr.inputShdr(elf_file);
599 if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue;
600 if (shdr.sh_type == elf.SHT_NOBITS) continue;
601 if (atom_ptr.scanRelocsRequiresCode(elf_file)) {
602 // TODO ideally, we don't have to decompress at this stage (should already be done)
603 // and we just fetch the code slice.
604 const code = try self.codeDecompressAlloc(elf_file, atom_index);
605 defer gpa.free(code);
606 try atom_ptr.scanRelocs(elf_file, code, undefs);
607 } else try atom_ptr.scanRelocs(elf_file, null, undefs);
608 }
609
610 for (self.cies.items) |cie| {
611 for (cie.relocs(elf_file)) |rel| {
612 const sym = elf_file.symbol(self.resolveSymbol(rel.r_sym(), elf_file)).?;
613 if (sym.flags.import) {
614 if (sym.type(elf_file) != elf.STT_FUNC)
615 // TODO convert into an error
616 log.debug("{f}: {s}: CIE referencing external data reference", .{
617 self.fmtPath(), sym.name(elf_file),
618 });
619 sym.flags.needs_plt = true;
620 }
621 }
622 }
623}
624
625pub fn resolveSymbols(self: *Object, elf_file: *Elf) !void {
626 const gpa = elf_file.base.comp.gpa;
627
628 const first_global = self.first_global orelse return;
629 for (self.globals(), first_global..) |_, i| {
630 const esym = self.symtab.items[i];
631 const resolv = &self.symbols_resolver.items[i - first_global];
632 const gop = try elf_file.resolver.getOrPut(gpa, .{
633 .index = @intCast(i),
634 .file = self.index,
635 }, elf_file);
636 if (!gop.found_existing) {
637 gop.ref.* = .{ .index = 0, .file = 0 };
638 }
639 resolv.* = gop.index;
640
641 if (esym.st_shndx == elf.SHN_UNDEF) continue;
642 if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) {
643 const atom_index = self.atoms_indexes.items[esym.st_shndx];
644 const atom_ptr = self.atom(atom_index) orelse continue;
645 if (!atom_ptr.alive) continue;
646 }
647 if (elf_file.symbol(gop.ref.*) == null) {
648 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
649 continue;
650 }
651
652 if (self.asFile().symbolRank(esym, !self.alive) < elf_file.symbol(gop.ref.*).?.symbolRank(elf_file)) {
653 gop.ref.* = .{ .index = @intCast(i), .file = self.index };
654 }
655 }
656}
657
658pub fn claimUnresolved(self: *Object, elf_file: *Elf) void {
659 const first_global = self.first_global orelse return;
660 for (self.globals(), 0..) |*sym, i| {
661 const esym_index = @as(u32, @intCast(first_global + i));
662 const esym = self.symtab.items[esym_index];
663 if (esym.st_shndx != elf.SHN_UNDEF) continue;
664 if (elf_file.symbol(self.resolveSymbol(esym_index, elf_file)) != null) continue;
665
666 const is_import = blk: {
667 if (!elf_file.isEffectivelyDynLib()) break :blk false;
668 const vis: elf.STV = @fromBackingInt(@intCast(@as(u3, @truncate(esym.st_other))));
669 if (vis == .HIDDEN) break :blk false;
670 break :blk true;
671 };
672
673 sym.value = 0;
674 sym.ref = .{ .index = 0, .file = 0 };
675 sym.esym_index = esym_index;
676 sym.file_index = self.index;
677 sym.version_index = if (is_import) .LOCAL else elf_file.default_sym_version;
678 sym.flags.import = is_import;
679
680 const idx = self.symbols_resolver.items[i];
681 elf_file.resolver.values.items[idx - 1] = .{ .index = esym_index, .file = self.index };
682 }
683}
684
685pub fn claimUnresolvedRelocatable(self: *Object, elf_file: *Elf) void {
686 const first_global = self.first_global orelse return;
687 for (self.globals(), 0..) |*sym, i| {
688 const esym_index = @as(u32, @intCast(first_global + i));
689 const esym = self.symtab.items[esym_index];
690 if (esym.st_shndx != elf.SHN_UNDEF) continue;
691 if (elf_file.symbol(self.resolveSymbol(esym_index, elf_file)) != null) continue;
692
693 sym.value = 0;
694 sym.ref = .{ .index = 0, .file = 0 };
695 sym.esym_index = esym_index;
696 sym.file_index = self.index;
697
698 const idx = self.symbols_resolver.items[i];
699 elf_file.resolver.values.items[idx - 1] = .{ .index = esym_index, .file = self.index };
700 }
701}
702
703pub fn markLive(self: *Object, elf_file: *Elf) void {
704 const first_global = self.first_global orelse return;
705 for (0..self.globals().len) |i| {
706 const esym_idx = first_global + i;
707 const esym = self.symtab.items[esym_idx];
708 if (esym.st_bind() == elf.STB_WEAK) continue;
709
710 const ref = self.resolveSymbol(@intCast(esym_idx), elf_file);
711 const sym = elf_file.symbol(ref) orelse continue;
712 const file = sym.file(elf_file).?;
713 const should_keep = esym.st_shndx == elf.SHN_UNDEF or
714 (esym.st_shndx == elf.SHN_COMMON and sym.elfSym(elf_file).st_shndx != elf.SHN_COMMON);
715 if (should_keep and !file.isAlive()) {
716 file.setAlive();
717 file.markLive(elf_file);
718 }
719 }
720}
721
722pub fn markEhFrameAtomsDead(self: *Object, elf_file: *Elf) void {
723 const cpu_arch = elf_file.getTarget().cpu.arch;
724 for (self.atoms_indexes.items) |atom_index| {
725 const atom_ptr = self.atom(atom_index) orelse continue;
726 const is_eh_frame = (cpu_arch == .x86_64 and atom_ptr.inputShdr(elf_file).sh_type == elf.SHT_X86_64_UNWIND) or
727 mem.eql(u8, atom_ptr.name(elf_file), ".eh_frame");
728 if (atom_ptr.alive and is_eh_frame) atom_ptr.alive = false;
729 }
730}
731
732pub fn markImportsExports(self: *Object, elf_file: *Elf) void {
733 const first_global = self.first_global orelse return;
734 for (0..self.globals().len) |i| {
735 const idx = first_global + i;
736 const ref = self.resolveSymbol(@intCast(idx), elf_file);
737 const sym = elf_file.symbol(ref) orelse continue;
738 const file = sym.file(elf_file).?;
739 if (sym.version_index == elf.Versym.LOCAL) continue;
740 const vis: elf.STV = @fromBackingInt(@intCast(@as(u3, @truncate(sym.elfSym(elf_file).st_other))));
741 if (vis == .HIDDEN) continue;
742 if (file == .shared_object and !sym.isAbs(elf_file)) {
743 sym.flags.import = true;
744 continue;
745 }
746 if (file.index() == self.index) {
747 sym.flags.@"export" = true;
748 if (elf_file.isEffectivelyDynLib() and vis != .PROTECTED) {
749 sym.flags.import = true;
750 }
751 }
752 }
753}
754
755pub fn checkDuplicates(self: *Object, dupes: anytype, elf_file: *Elf) error{OutOfMemory}!void {
756 const gpa = elf_file.base.comp.gpa;
757
758 const first_global = self.first_global orelse return;
759 for (0..self.globals().len) |i| {
760 const esym_idx = first_global + i;
761 const esym = self.symtab.items[esym_idx];
762 const ref = self.resolveSymbol(@intCast(esym_idx), elf_file);
763 const ref_sym = elf_file.symbol(ref) orelse continue;
764 const ref_file = ref_sym.file(elf_file).?;
765
766 if (self.index == ref_file.index() or
767 esym.st_shndx == elf.SHN_UNDEF or
768 esym.st_bind() == elf.STB_WEAK or
769 esym.st_shndx == elf.SHN_COMMON) continue;
770
771 if (esym.st_shndx != elf.SHN_ABS) {
772 const atom_index = self.atoms_indexes.items[esym.st_shndx];
773 const atom_ptr = self.atom(atom_index) orelse continue;
774 if (!atom_ptr.alive) continue;
775 }
776
777 const gop = try dupes.getOrPut(gpa, self.symbols_resolver.items[i]);
778 if (!gop.found_existing) {
779 gop.value_ptr.* = .empty;
780 }
781 try gop.value_ptr.append(gpa, self.index);
782 }
783}
784
785pub fn initInputMergeSections(self: *Object, elf_file: *Elf) !void {
786 const gpa = elf_file.base.comp.gpa;
787 const diags = &elf_file.base.comp.link_diags;
788
789 try self.input_merge_sections.ensureUnusedCapacity(gpa, self.shdrs.items.len);
790 try self.input_merge_sections_indexes.resize(gpa, self.shdrs.items.len);
791 @memset(self.input_merge_sections_indexes.items, 0);
792
793 for (self.shdrs.items, 0..) |shdr, shndx| {
794 if (shdr.sh_flags & elf.SHF_MERGE == 0) continue;
795
796 const atom_index = self.atoms_indexes.items[shndx];
797 const atom_ptr = self.atom(atom_index) orelse continue;
798 if (!atom_ptr.alive) continue;
799 if (atom_ptr.relocs(elf_file).len > 0) continue;
800
801 const imsec_idx = try self.addInputMergeSection(gpa);
802 const imsec = self.inputMergeSection(imsec_idx).?;
803 self.input_merge_sections_indexes.items[shndx] = imsec_idx;
804 imsec.atom_index = atom_index;
805
806 const data = try self.codeDecompressAlloc(elf_file, atom_index);
807 defer gpa.free(data);
808
809 if (shdr.sh_flags & elf.SHF_STRINGS != 0) {
810 const sh_entsize: u32 = switch (shdr.sh_entsize) {
811 // According to mold's source code, GHC emits MS sections with sh_entsize = 0.
812 // This actually can also happen for output created with `-r` mode.
813 0 => 1,
814 else => |x| @intCast(x),
815 };
816
817 const isNull = struct {
818 fn isNull(slice: []u8) bool {
819 for (slice) |x| if (x != 0) return false;
820 return true;
821 }
822 }.isNull;
823
824 var start: u32 = 0;
825 while (start < data.len) {
826 var end = start;
827 while (end < data.len - sh_entsize and !isNull(data[end .. end + sh_entsize])) : (end += sh_entsize) {}
828 if (!isNull(data[end .. end + sh_entsize])) {
829 var err = try diags.addErrorWithNotes(1);
830 try err.addMsg("string not null terminated", .{});
831 err.addNote("in {f}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
832 return error.AlreadyReported;
833 }
834 end += sh_entsize;
835 const string = data[start..end];
836 try imsec.insert(gpa, string);
837 try imsec.offsets.append(gpa, start);
838 start = end;
839 }
840 } else {
841 const sh_entsize: u32 = @intCast(shdr.sh_entsize);
842 if (sh_entsize == 0) continue; // Malformed, don't split but don't error out
843 if (shdr.sh_size % sh_entsize != 0) {
844 var err = try diags.addErrorWithNotes(1);
845 try err.addMsg("size not a multiple of sh_entsize", .{});
846 err.addNote("in {f}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
847 return error.AlreadyReported;
848 }
849
850 var pos: u32 = 0;
851 while (pos < data.len) : (pos += sh_entsize) {
852 const string = data.ptr[pos..][0..sh_entsize];
853 try imsec.insert(gpa, string);
854 try imsec.offsets.append(gpa, pos);
855 }
856 }
857
858 atom_ptr.alive = false;
859 }
860}
861
862pub fn initOutputMergeSections(self: *Object, elf_file: *Elf) !void {
863 for (self.input_merge_sections_indexes.items) |index| {
864 const imsec = self.inputMergeSection(index) orelse continue;
865 const atom_ptr = self.atom(imsec.atom_index).?;
866 const shdr = atom_ptr.inputShdr(elf_file);
867 imsec.merge_section_index = try elf_file.getOrCreateMergeSection(
868 atom_ptr.name(elf_file),
869 shdr.sh_flags,
870 shdr.sh_type,
871 );
872 }
873}
874
875pub fn resolveMergeSubsections(self: *Object, elf_file: *Elf) error{
876 AlreadyReported,
877 OutOfMemory,
878 /// TODO report the error and remove this
879 Overflow,
880}!void {
881 const gpa = elf_file.base.comp.gpa;
882 const diags = &elf_file.base.comp.link_diags;
883
884 for (self.input_merge_sections_indexes.items) |index| {
885 const imsec = self.inputMergeSection(index) orelse continue;
886 if (imsec.offsets.items.len == 0) continue;
887 const msec = elf_file.mergeSection(imsec.merge_section_index);
888 const atom_ptr = self.atom(imsec.atom_index).?;
889 const isec = atom_ptr.inputShdr(elf_file);
890
891 try imsec.subsections.resize(gpa, imsec.strings.items.len);
892
893 for (imsec.strings.items, imsec.subsections.items) |str, *imsec_msub| {
894 const string = imsec.bytes.items[str.pos..][0..str.len];
895 const res = try msec.insert(gpa, string);
896 if (res.found_existing) {
897 const msub = msec.mergeSubsection(res.sub.*);
898 msub.alignment = msub.alignment.maxStrict(atom_ptr.alignment);
899 } else {
900 const msub_index = try msec.addMergeSubsection(gpa);
901 const msub = msec.mergeSubsection(msub_index);
902 msub.merge_section_index = imsec.merge_section_index;
903 msub.string_index = res.key.pos;
904 msub.alignment = atom_ptr.alignment;
905 msub.size = res.key.len;
906 msub.entsize = math.cast(u32, isec.sh_entsize) orelse return error.Overflow;
907 msub.alive = !elf_file.base.gc_sections or isec.sh_flags & elf.SHF_ALLOC == 0;
908 res.sub.* = msub_index;
909 }
910 imsec_msub.* = res.sub.*;
911 }
912
913 imsec.clearAndFree(gpa);
914 }
915
916 for (self.symtab.items, 0..) |*esym, idx| {
917 const sym = &self.symbols.items[idx];
918 if (esym.st_shndx == elf.SHN_COMMON or esym.st_shndx == elf.SHN_UNDEF or esym.st_shndx == elf.SHN_ABS) continue;
919
920 const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx];
921 const imsec = self.inputMergeSection(imsec_index) orelse continue;
922 if (imsec.offsets.items.len == 0) continue;
923 const res = imsec.findSubsection(@intCast(esym.st_value)) orelse {
924 var err = try diags.addErrorWithNotes(2);
925 try err.addMsg("invalid symbol value: {x}", .{esym.st_value});
926 err.addNote("for symbol {s}", .{sym.name(elf_file)});
927 err.addNote("in {f}", .{self.fmtPath()});
928 return error.AlreadyReported;
929 };
930
931 sym.ref = .{ .index = res.msub_index, .file = imsec.merge_section_index };
932 sym.flags.merge_subsection = true;
933 sym.value = res.offset;
934 }
935
936 for (self.atoms_indexes.items) |atom_index| {
937 const atom_ptr = self.atom(atom_index) orelse continue;
938 if (!atom_ptr.alive) continue;
939 const extras = atom_ptr.extra(elf_file);
940 const relocs = self.relocs.items[extras.rel_index..][0..extras.rel_count];
941 for (relocs) |*rel| {
942 const esym = self.symtab.items[rel.r_sym()];
943 if (esym.st_type() != elf.STT_SECTION) continue;
944
945 const imsec_index = self.input_merge_sections_indexes.items[esym.st_shndx];
946 const imsec = self.inputMergeSection(imsec_index) orelse continue;
947 if (imsec.offsets.items.len == 0) continue;
948 const msec = elf_file.mergeSection(imsec.merge_section_index);
949 const res = imsec.findSubsection(@intCast(@as(i64, @intCast(esym.st_value)) + rel.r_addend)) orelse {
950 var err = try diags.addErrorWithNotes(1);
951 try err.addMsg("invalid relocation at offset 0x{x}", .{rel.r_offset});
952 err.addNote("in {f}:{s}", .{ self.fmtPath(), atom_ptr.name(elf_file) });
953 return error.AlreadyReported;
954 };
955
956 const sym_index = try self.addSymbol(gpa);
957 const sym = &self.symbols.items[sym_index];
958 const name = try std.fmt.allocPrint(gpa, "{s}$subsection{d}", .{ msec.name(elf_file), res.msub_index });
959 defer gpa.free(name);
960 sym.* = .{
961 .value = @bitCast(@as(i64, @intCast(res.offset)) - rel.r_addend),
962 .name_offset = try self.addString(gpa, name),
963 .esym_index = rel.r_sym(),
964 .file_index = self.index,
965 .extra_index = try self.addSymbolExtra(gpa, .{}),
966 };
967 sym.ref = .{ .index = res.msub_index, .file = imsec.merge_section_index };
968 sym.flags.merge_subsection = true;
969 rel.r_info = (@as(u64, @intCast(sym_index)) << 32) | rel.r_type();
970 }
971 }
972}
973
974/// We will create dummy shdrs per each resolved common symbols to make it
975/// play nicely with the rest of the system.
976pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
977 const first_global = self.first_global orelse return;
978 for (self.globals(), self.symbols_resolver.items, 0..) |*sym, resolv, i| {
979 const esym_idx = @as(u32, @intCast(first_global + i));
980 const esym = self.symtab.items[esym_idx];
981 if (esym.st_shndx != elf.SHN_COMMON) continue;
982 if (elf_file.resolver.get(resolv).?.file != self.index) continue;
983
984 const comp = elf_file.base.comp;
985 const gpa = comp.gpa;
986
987 const is_tls = sym.type(elf_file) == elf.STT_TLS;
988 const name = if (is_tls) ".tls_common" else ".common";
989 const name_offset = @as(u32, @intCast(self.strtab.items.len));
990 try self.strtab.print(gpa, "{s}\x00", .{name});
991
992 var sh_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE;
993 if (is_tls) sh_flags |= elf.SHF_TLS;
994 const shndx = @as(u32, @intCast(self.shdrs.items.len));
995 const shdr = try self.shdrs.addOne(gpa);
996 const sh_size = math.cast(usize, esym.st_size) orelse return error.Overflow;
997 shdr.* = .{
998 .sh_name = name_offset,
999 .sh_type = elf.SHT_NOBITS,
1000 .sh_flags = sh_flags,
1001 .sh_addr = 0,
1002 .sh_offset = 0,
1003 .sh_size = sh_size,
1004 .sh_link = 0,
1005 .sh_info = 0,
1006 .sh_addralign = esym.st_value,
1007 .sh_entsize = 0,
1008 };
1009
1010 const atom_index = try self.addAtom(gpa, .{
1011 .name = name_offset,
1012 .shndx = shndx,
1013 .size = esym.st_size,
1014 .alignment = Alignment.fromNonzeroByteUnits(esym.st_value),
1015 });
1016 try self.atoms_indexes.append(gpa, atom_index);
1017
1018 sym.value = 0;
1019 sym.ref = .{ .index = atom_index, .file = self.index };
1020 sym.flags.weak = false;
1021 }
1022}
1023
1024pub fn resolveGroups(self: *Object, elf_file: *Elf, table: anytype) !void {
1025 for (self.groups.items, 0..) |*g, gi| {
1026 const signature = g.signature(elf_file);
1027 const gop = try table.getOrPut(signature);
1028 if (!gop.found_existing) {
1029 gop.value_ptr.* = .{ .index = @intCast(gi), .file = self.index };
1030 continue;
1031 }
1032 const current = elf_file.group(gop.value_ptr.*);
1033 g.alive = false;
1034 if (self.index < current.file_index) {
1035 current.alive = false;
1036 g.alive = true;
1037 gop.value_ptr.* = .{ .index = @intCast(gi), .file = self.index };
1038 }
1039 }
1040}
1041
1042pub fn markGroupsDead(self: *Object, elf_file: *Elf) void {
1043 for (self.groups.items) |g| {
1044 if (g.alive) continue;
1045 for (g.members(elf_file)) |shndx| {
1046 const atom_index = self.atoms_indexes.items[shndx];
1047 if (self.atom(atom_index)) |atom_ptr| {
1048 atom_ptr.alive = false;
1049 atom_ptr.markFdesDead(self);
1050 }
1051 }
1052 }
1053}
1054
1055pub fn initOutputSections(self: *Object, elf_file: *Elf) !void {
1056 for (self.atoms_indexes.items) |atom_index| {
1057 const atom_ptr = self.atom(atom_index) orelse continue;
1058 if (!atom_ptr.alive) continue;
1059 const shdr = atom_ptr.inputShdr(elf_file);
1060 const osec = try elf_file.initOutputSection(.{
1061 .name = self.getString(shdr.sh_name),
1062 .flags = shdr.sh_flags,
1063 .type = shdr.sh_type,
1064 });
1065 const atom_list = &elf_file.sections.items(.atom_list_2)[osec];
1066 atom_list.output_section_index = osec;
1067 _ = try atom_list.atoms.getOrPut(elf_file.base.comp.gpa, atom_ptr.ref());
1068 }
1069}
1070
1071pub fn initRelaSections(self: *Object, elf_file: *Elf) !void {
1072 for (self.atoms_indexes.items) |atom_index| {
1073 const atom_ptr = self.atom(atom_index) orelse continue;
1074 if (!atom_ptr.alive) continue;
1075 if (atom_ptr.output_section_index == elf_file.section_indexes.eh_frame) continue;
1076 const shndx = atom_ptr.relocsShndx() orelse continue;
1077 const shdr = self.shdrs.items[shndx];
1078 const out_shndx = try elf_file.initOutputSection(.{
1079 .name = self.getString(shdr.sh_name),
1080 .flags = shdr.sh_flags,
1081 .type = shdr.sh_type,
1082 });
1083 const out_shdr = &elf_file.sections.items(.shdr)[out_shndx];
1084 out_shdr.sh_type = elf.SHT_RELA;
1085 out_shdr.sh_addralign = @alignOf(elf.Elf64_Rela);
1086 out_shdr.sh_entsize = @sizeOf(elf.Elf64_Rela);
1087 out_shdr.sh_flags |= elf.SHF_INFO_LINK;
1088 }
1089}
1090
1091pub fn addAtomsToRelaSections(self: *Object, elf_file: *Elf) !void {
1092 for (self.atoms_indexes.items) |atom_index| {
1093 const atom_ptr = self.atom(atom_index) orelse continue;
1094 if (!atom_ptr.alive) continue;
1095 if (atom_ptr.output_section_index == elf_file.section_indexes.eh_frame) continue;
1096 const shndx = blk: {
1097 const shndx = atom_ptr.relocsShndx() orelse continue;
1098 const shdr = self.shdrs.items[shndx];
1099 break :blk elf_file.initOutputSection(.{
1100 .name = self.getString(shdr.sh_name),
1101 .flags = shdr.sh_flags,
1102 .type = shdr.sh_type,
1103 }) catch unreachable;
1104 };
1105 const slice = elf_file.sections.slice();
1106 const shdr = &slice.items(.shdr)[shndx];
1107 shdr.sh_info = atom_ptr.output_section_index;
1108 shdr.sh_link = elf_file.section_indexes.symtab.?;
1109 const gpa = elf_file.base.comp.gpa;
1110 const atom_list = &elf_file.sections.items(.atom_list)[shndx];
1111 try atom_list.append(gpa, .{ .index = atom_index, .file = self.index });
1112 }
1113}
1114
1115pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) !void {
1116 const comp = elf_file.base.comp;
1117 const gpa = comp.gpa;
1118 const start = self.first_global orelse self.symtab.items.len;
1119
1120 try ar_symtab.symtab.ensureUnusedCapacity(gpa, self.symtab.items.len - start);
1121
1122 for (self.symtab.items[start..]) |sym| {
1123 if (sym.st_shndx == elf.SHN_UNDEF) continue;
1124 const off = try ar_symtab.strtab.insert(gpa, self.getString(sym.st_name));
1125 ar_symtab.symtab.appendAssumeCapacity(.{ .off = off, .file_index = self.index });
1126 }
1127}
1128
1129pub fn updateArSize(self: *Object, elf_file: *Elf) !void {
1130 const comp = elf_file.base.comp;
1131 const io = comp.io;
1132 self.output_ar_state.size = if (self.archive) |ar| ar.size else size: {
1133 const handle = elf_file.fileHandle(self.file_handle);
1134 break :size (try handle.stat(io)).size;
1135 };
1136}
1137
1138pub fn writeAr(self: Object, elf_file: *Elf, writer: anytype) !void {
1139 const comp = elf_file.base.comp;
1140 const gpa = comp.gpa;
1141 const io = comp.io;
1142 const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
1143 const offset: u64 = if (self.archive) |ar| ar.offset else 0;
1144 const name = fs.path.basename(self.path.sub_path);
1145 const hdr = Archive.setArHdr(.{
1146 .name = if (name.len <= Archive.max_member_name_len)
1147 .{ .name = name }
1148 else
1149 .{ .name_off = self.output_ar_state.name_off },
1150 .size = size,
1151 });
1152 try writer.writeAll(mem.asBytes(&hdr));
1153 const handle = elf_file.fileHandle(self.file_handle);
1154 const data = try gpa.alloc(u8, size);
1155 defer gpa.free(data);
1156 const amt = try handle.readPositionalAll(io, data, offset);
1157 if (amt != size) return error.InputOutput;
1158 try writer.writeAll(data);
1159}
1160
1161pub fn updateSymtabSize(self: *Object, elf_file: *Elf) void {
1162 const isAlive = struct {
1163 fn isAlive(sym: *const Symbol, ctx: *Elf) bool {
1164 if (sym.mergeSubsection(ctx)) |msub| return msub.alive;
1165 if (sym.atom(ctx)) |atom_ptr| return atom_ptr.alive;
1166 return true;
1167 }
1168 }.isAlive;
1169
1170 for (self.locals()) |*local| {
1171 if (!isAlive(local, elf_file)) continue;
1172 const esym = local.elfSym(elf_file);
1173 switch (esym.st_type()) {
1174 elf.STT_SECTION => continue,
1175 elf.STT_NOTYPE => if (esym.st_shndx == elf.SHN_UNDEF) continue,
1176 else => {},
1177 }
1178 local.flags.output_symtab = true;
1179 local.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, elf_file);
1180 self.output_symtab_ctx.nlocals += 1;
1181 self.output_symtab_ctx.strsize += @as(u32, @intCast(local.name(elf_file).len)) + 1;
1182 }
1183
1184 for (self.globals(), self.symbols_resolver.items) |*global, resolv| {
1185 const ref = elf_file.resolver.values.items[resolv - 1];
1186 const ref_sym = elf_file.symbol(ref) orelse continue;
1187 if (ref_sym.file(elf_file).?.index() != self.index) continue;
1188 if (!isAlive(global, elf_file)) continue;
1189 global.flags.output_symtab = true;
1190 if (global.isLocal(elf_file)) {
1191 global.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, elf_file);
1192 self.output_symtab_ctx.nlocals += 1;
1193 } else {
1194 global.addExtra(.{ .symtab = self.output_symtab_ctx.nglobals }, elf_file);
1195 self.output_symtab_ctx.nglobals += 1;
1196 }
1197 self.output_symtab_ctx.strsize += @as(u32, @intCast(global.name(elf_file).len)) + 1;
1198 }
1199}
1200
1201pub fn writeSymtab(self: *Object, elf_file: *Elf) void {
1202 for (self.locals()) |local| {
1203 const idx = local.outputSymtabIndex(elf_file) orelse continue;
1204 const out_sym = &elf_file.symtab.items[idx];
1205 out_sym.st_name = @intCast(elf_file.strtab.items.len);
1206 elf_file.strtab.appendSliceAssumeCapacity(local.name(elf_file));
1207 elf_file.strtab.appendAssumeCapacity(0);
1208 local.setOutputSym(elf_file, out_sym);
1209 }
1210
1211 for (self.globals(), self.symbols_resolver.items) |global, resolv| {
1212 const ref = elf_file.resolver.values.items[resolv - 1];
1213 const ref_sym = elf_file.symbol(ref) orelse continue;
1214 if (ref_sym.file(elf_file).?.index() != self.index) continue;
1215 const idx = global.outputSymtabIndex(elf_file) orelse continue;
1216 const st_name = @as(u32, @intCast(elf_file.strtab.items.len));
1217 elf_file.strtab.appendSliceAssumeCapacity(global.name(elf_file));
1218 elf_file.strtab.appendAssumeCapacity(0);
1219 const out_sym = &elf_file.symtab.items[idx];
1220 out_sym.st_name = st_name;
1221 global.setOutputSym(elf_file, out_sym);
1222 }
1223}
1224
1225/// Returns atom's code and optionally uncompresses data if required (for compressed sections).
1226/// Caller owns the memory.
1227pub fn codeDecompressAlloc(self: *Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
1228 const comp = elf_file.base.comp;
1229 const io = comp.io;
1230 const gpa = comp.gpa;
1231 const atom_ptr = self.atom(atom_index).?;
1232 const shdr = atom_ptr.inputShdr(elf_file);
1233 const handle = elf_file.fileHandle(self.file_handle);
1234 const data = try self.preadShdrContentsAlloc(gpa, io, handle, atom_ptr.input_section_index);
1235 defer if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) gpa.free(data);
1236
1237 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
1238 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
1239 var compressed_reader: Io.Reader = .fixed(data[@sizeOf(elf.Elf64_Chdr)..]);
1240 const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow;
1241 var aw: Io.Writer.Allocating = try .initCapacity(gpa, size);
1242 defer aw.deinit();
1243 switch (chdr.ch_type) {
1244 .ZLIB => {
1245 var decompress: std.compress.flate.Decompress = .init(&compressed_reader, .zlib, &.{});
1246 _ = try decompress.reader.streamRemaining(&aw.writer);
1247 },
1248 .ZSTD => {
1249 var decompress: std.compress.zstd.Decompress = .init(&compressed_reader, &.{}, .{});
1250 _ = try decompress.reader.streamRemaining(&aw.writer);
1251 },
1252 else => @panic("TODO unhandled compression scheme"),
1253 }
1254 return aw.toOwnedSlice();
1255 }
1256
1257 return data;
1258}
1259
1260fn locals(self: *Object) []Symbol {
1261 if (self.symbols.items.len == 0) return &[0]Symbol{};
1262 assert(self.symbols.items.len >= self.symtab.items.len);
1263 const end = self.first_global orelse self.symtab.items.len;
1264 return self.symbols.items[0..end];
1265}
1266
1267pub fn globals(self: *Object) []Symbol {
1268 if (self.symbols.items.len == 0) return &[0]Symbol{};
1269 assert(self.symbols.items.len >= self.symtab.items.len);
1270 const start = self.first_global orelse self.symtab.items.len;
1271 return self.symbols.items[start..self.symtab.items.len];
1272}
1273
1274pub fn resolveSymbol(self: Object, index: Symbol.Index, elf_file: *Elf) Elf.Ref {
1275 const start = self.first_global orelse self.symtab.items.len;
1276 const end = self.symtab.items.len;
1277 if (index < start or index >= end) return .{ .index = index, .file = self.index };
1278 const resolv = self.symbols_resolver.items[index - start];
1279 return elf_file.resolver.get(resolv).?;
1280}
1281
1282fn addSymbol(self: *Object, gpa: Allocator) !Symbol.Index {
1283 try self.symbols.ensureUnusedCapacity(gpa, 1);
1284 return self.addSymbolAssumeCapacity();
1285}
1286
1287fn addSymbolAssumeCapacity(self: *Object) Symbol.Index {
1288 const index: Symbol.Index = @intCast(self.symbols.items.len);
1289 self.symbols.appendAssumeCapacity(.{ .file_index = self.index });
1290 return index;
1291}
1292
1293pub fn addSymbolExtra(self: *Object, gpa: Allocator, extra: Symbol.Extra) !u32 {
1294 const field_count = @typeInfo(Symbol.Extra).@"struct".field_names.len;
1295 try self.symbols_extra.ensureUnusedCapacity(gpa, field_count);
1296 return self.addSymbolExtraAssumeCapacity(extra);
1297}
1298
1299pub fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 {
1300 const index = @as(u32, @intCast(self.symbols_extra.items.len));
1301 const info = @typeInfo(Symbol.Extra).@"struct";
1302 inline for (info.field_names, info.field_types) |field_name, field_type| {
1303 self.symbols_extra.appendAssumeCapacity(switch (field_type) {
1304 u32 => @field(extra, field_name),
1305 else => @compileError("bad field type"),
1306 });
1307 }
1308 return index;
1309}
1310
1311pub fn symbolExtra(self: *Object, index: u32) Symbol.Extra {
1312 const info = @typeInfo(Symbol.Extra).@"struct";
1313 var i: usize = index;
1314 var result: Symbol.Extra = undefined;
1315 inline for (info.field_names, info.field_types) |field_name, field_type| {
1316 @field(result, field_name) = switch (field_type) {
1317 u32 => self.symbols_extra.items[i],
1318 else => @compileError("bad field type"),
1319 };
1320 i += 1;
1321 }
1322 return result;
1323}
1324
1325pub fn setSymbolExtra(self: *Object, index: u32, extra: Symbol.Extra) void {
1326 const info = @typeInfo(Symbol.Extra).@"struct";
1327 inline for (info.field_names, info.field_types, 0..) |field_name, field_type, i| {
1328 self.symbols_extra.items[index + i] = switch (field_type) {
1329 u32 => @field(extra, field_name),
1330 else => @compileError("bad field type"),
1331 };
1332 }
1333}
1334
1335pub fn asFile(self: *Object) File {
1336 return .{ .object = self };
1337}
1338
1339pub fn getString(self: Object, off: u32) [:0]const u8 {
1340 assert(off < self.strtab.items.len);
1341 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
1342}
1343
1344fn addString(self: *Object, gpa: Allocator, str: []const u8) !u32 {
1345 const off: u32 = @intCast(self.strtab.items.len);
1346 try self.strtab.ensureUnusedCapacity(gpa, str.len + 1);
1347 self.strtab.appendSliceAssumeCapacity(str);
1348 self.strtab.appendAssumeCapacity(0);
1349 return off;
1350}
1351
1352/// Caller owns the memory.
1353fn preadShdrContentsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, index: u32) ![]u8 {
1354 assert(index < self.shdrs.items.len);
1355 const offset = if (self.archive) |ar| ar.offset else 0;
1356 const shdr = self.shdrs.items[index];
1357 const sh_offset = math.cast(u64, shdr.sh_offset) orelse return error.Overflow;
1358 const sh_size = math.cast(u64, shdr.sh_size) orelse return error.Overflow;
1359 return Elf.preadAllAlloc(gpa, io, handle, offset + sh_offset, sh_size);
1360}
1361
1362/// Caller owns the memory.
1363fn preadRelocsAlloc(self: Object, gpa: Allocator, io: Io, handle: Io.File, shndx: u32) ![]align(1) const elf.Elf64_Rela {
1364 const raw = try self.preadShdrContentsAlloc(gpa, io, handle, shndx);
1365 const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
1366 return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
1367}
1368
1369const AddAtomArgs = struct {
1370 name: u32,
1371 shndx: u32,
1372 size: u64,
1373 alignment: Alignment,
1374};
1375
1376fn addAtom(self: *Object, gpa: Allocator, args: AddAtomArgs) !Atom.Index {
1377 try self.atoms.ensureUnusedCapacity(gpa, 1);
1378 try self.atoms_extra.ensureUnusedCapacity(gpa, @sizeOf(Atom.Extra));
1379 return self.addAtomAssumeCapacity(args);
1380}
1381
1382fn addAtomAssumeCapacity(self: *Object, args: AddAtomArgs) Atom.Index {
1383 const atom_index: Atom.Index = @intCast(self.atoms.items.len);
1384 const atom_ptr = self.atoms.addOneAssumeCapacity();
1385 atom_ptr.* = .{
1386 .atom_index = atom_index,
1387 .name_offset = args.name,
1388 .file_index = self.index,
1389 .input_section_index = args.shndx,
1390 .extra_index = self.addAtomExtraAssumeCapacity(.{}),
1391 .size = args.size,
1392 .alignment = args.alignment,
1393 };
1394 return atom_index;
1395}
1396
1397pub fn atom(self: *Object, atom_index: Atom.Index) ?*Atom {
1398 if (atom_index == 0) return null;
1399 assert(atom_index < self.atoms.items.len);
1400 return &self.atoms.items[atom_index];
1401}
1402
1403pub fn addAtomExtra(self: *Object, gpa: Allocator, extra: Atom.Extra) !u32 {
1404 const field_count = @typeInfo(Atom.Extra).@"struct".field_names.len;
1405 try self.atoms_extra.ensureUnusedCapacity(gpa, field_count);
1406 return self.addAtomExtraAssumeCapacity(extra);
1407}
1408
1409pub fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 {
1410 const index: u32 = @intCast(self.atoms_extra.items.len);
1411 const info = @typeInfo(Atom.Extra).@"struct";
1412 inline for (info.field_names, info.field_types) |field_name, field_type| {
1413 self.atoms_extra.appendAssumeCapacity(switch (field_type) {
1414 u32 => @field(extra, field_name),
1415 else => @compileError("bad field type"),
1416 });
1417 }
1418 return index;
1419}
1420
1421pub fn atomExtra(self: *Object, index: u32) Atom.Extra {
1422 const info = @typeInfo(Atom.Extra).@"struct";
1423 var i: usize = index;
1424 var result: Atom.Extra = undefined;
1425 inline for (info.field_names, info.field_types) |field_name, field_type| {
1426 @field(result, field_name) = switch (field_type) {
1427 u32 => self.atoms_extra.items[i],
1428 else => @compileError("bad field type"),
1429 };
1430 i += 1;
1431 }
1432 return result;
1433}
1434
1435pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void {
1436 const info = @typeInfo(Atom.Extra).@"struct";
1437 inline for (info.field_names, info.field_types, 0..) |field_name, field_type, i| {
1438 self.atoms_extra.items[index + i] = switch (field_type) {
1439 u32 => @field(extra, field_name),
1440 else => @compileError("bad field type"),
1441 };
1442 }
1443}
1444
1445fn setAtomFields(o: *Object, atom_ptr: *Atom, opts: Atom.Extra.AsOptionals) void {
1446 assert(o.index == atom_ptr.file_index);
1447 var extras = o.atomExtra(atom_ptr.extra_index);
1448 inline for (@typeInfo(@TypeOf(opts)).@"struct".field_names) |field_name| {
1449 if (@field(opts, field_name)) |x| @field(extras, field_name) = x;
1450 }
1451 o.setAtomExtra(atom_ptr.extra_index, extras);
1452}
1453
1454fn addInputMergeSection(self: *Object, gpa: Allocator) !Merge.InputSection.Index {
1455 const index: Merge.InputSection.Index = @intCast(self.input_merge_sections.items.len);
1456 const msec = try self.input_merge_sections.addOne(gpa);
1457 msec.* = .{};
1458 return index;
1459}
1460
1461fn inputMergeSection(self: *Object, index: Merge.InputSection.Index) ?*Merge.InputSection {
1462 if (index == 0) return null;
1463 return &self.input_merge_sections.items[index];
1464}
1465
1466fn addGroup(self: *Object, gpa: Allocator) !Elf.Group.Index {
1467 const index: Elf.Group.Index = @intCast(self.groups.items.len);
1468 _ = try self.groups.addOne(gpa);
1469 return index;
1470}
1471
1472pub fn group(self: *Object, index: Elf.Group.Index) *Elf.Group {
1473 assert(index < self.groups.items.len);
1474 return &self.groups.items[index];
1475}
1476
1477pub fn fmtSymtab(self: *Object, elf_file: *Elf) std.fmt.Alt(Format, Format.symtab) {
1478 return .{ .data = .{
1479 .object = self,
1480 .elf_file = elf_file,
1481 } };
1482}
1483
1484const Format = struct {
1485 object: *Object,
1486 elf_file: *Elf,
1487
1488 fn symtab(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
1489 const object = f.object;
1490 const elf_file = f.elf_file;
1491 try writer.writeAll(" locals\n");
1492 for (object.locals()) |sym| {
1493 try writer.print(" {f}\n", .{sym.fmt(elf_file)});
1494 }
1495 try writer.writeAll(" globals\n");
1496 for (object.globals(), 0..) |sym, i| {
1497 const first_global = object.first_global.?;
1498 const ref = object.resolveSymbol(@intCast(i + first_global), elf_file);
1499 if (elf_file.symbol(ref)) |ref_sym| {
1500 try writer.print(" {f}\n", .{ref_sym.fmt(elf_file)});
1501 } else {
1502 try writer.print(" {s} : unclaimed\n", .{sym.name(elf_file)});
1503 }
1504 }
1505 }
1506
1507 fn atoms(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
1508 const object = f.object;
1509 try writer.writeAll(" atoms\n");
1510 for (object.atoms_indexes.items) |atom_index| {
1511 const atom_ptr = object.atom(atom_index) orelse continue;
1512 try writer.print(" {f}\n", .{atom_ptr.fmt(f.elf_file)});
1513 }
1514 }
1515
1516 fn cies(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
1517 const object = f.object;
1518 try writer.writeAll(" cies\n");
1519 for (object.cies.items, 0..) |cie, i| {
1520 try writer.print(" cie({d}) : {f}\n", .{ i, cie.fmt(f.elf_file) });
1521 }
1522 }
1523
1524 fn fdes(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
1525 const object = f.object;
1526 try writer.writeAll(" fdes\n");
1527 for (object.fdes.items, 0..) |fde, i| {
1528 try writer.print(" fde({d}) : {f}\n", .{ i, fde.fmt(f.elf_file) });
1529 }
1530 }
1531
1532 fn groups(f: Format, writer: *Io.Writer) Io.Writer.Error!void {
1533 const object = f.object;
1534 const elf_file = f.elf_file;
1535 try writer.writeAll(" groups\n");
1536 for (object.groups.items, 0..) |g, g_index| {
1537 try writer.print(" {s}({d})", .{ if (g.is_comdat) "COMDAT" else "GROUP", g_index });
1538 if (!g.alive) try writer.writeAll(" : [*]");
1539 try writer.writeByte('\n');
1540 const g_members = g.members(elf_file);
1541 for (g_members) |shndx| {
1542 const atom_index = object.atoms_indexes.items[shndx];
1543 const atom_ptr = object.atom(atom_index) orelse continue;
1544 try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom_ptr.name(elf_file) });
1545 }
1546 }
1547 }
1548};
1549
1550pub fn fmtAtoms(self: *Object, elf_file: *Elf) std.fmt.Alt(Format, Format.atoms) {
1551 return .{ .data = .{
1552 .object = self,
1553 .elf_file = elf_file,
1554 } };
1555}
1556
1557pub fn fmtCies(self: *Object, elf_file: *Elf) std.fmt.Alt(Format, Format.cies) {
1558 return .{ .data = .{
1559 .object = self,
1560 .elf_file = elf_file,
1561 } };
1562}
1563
1564pub fn fmtFdes(self: *Object, elf_file: *Elf) std.fmt.Alt(Format, Format.fdes) {
1565 return .{ .data = .{
1566 .object = self,
1567 .elf_file = elf_file,
1568 } };
1569}
1570
1571pub fn fmtGroups(self: *Object, elf_file: *Elf) std.fmt.Alt(Format, Format.groups) {
1572 return .{ .data = .{
1573 .object = self,
1574 .elf_file = elf_file,
1575 } };
1576}
1577
1578pub fn fmtPath(self: Object) std.fmt.Alt(Object, formatPath) {
1579 return .{ .data = self };
1580}
1581
1582fn formatPath(object: Object, writer: *Io.Writer) Io.Writer.Error!void {
1583 if (object.archive) |ar| {
1584 try writer.print("{f}({f})", .{ ar.path, object.path });
1585 } else {
1586 try writer.print("{f}", .{object.path});
1587 }
1588}
1589
1590const InArchive = struct {
1591 path: Path,
1592 offset: u64,
1593 size: u32,
1594};