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