1const std = @import("std");
2const assert = std.debug.assert;
3const elf = std.elf;
4const math = std.math;
5const mem = std.mem;
6const Path = std.Build.Cache.Path;
7const log = std.log.scoped(.link);
8const state_log = std.log.scoped(.link_state);
9
10const build_options = @import("build_options");
11
12const eh_frame = @import("eh_frame.zig");
13const link = @import("../../link.zig");
14const Archive = @import("Archive.zig");
15const Compilation = @import("../../Compilation.zig");
16const Elf = @import("../Elf.zig");
17const File = @import("file.zig").File;
18const Object = @import("Object.zig");
19const Symbol = @import("Symbol.zig");
20
21pub fn flushStaticLib(elf_file: *Elf, comp: *Compilation) !void {
22 const gpa = comp.gpa;
23 const io = comp.io;
24 const diags = &comp.link_diags;
25
26 if (diags.hasErrors()) return error.AlreadyReported;
27
28 // First, we flush relocatable object file generated with our backends.
29 if (elf_file.zigObjectPtr()) |zig_object| {
30 try zig_object.resolveSymbols(elf_file);
31 elf_file.markEhFrameAtomsDead();
32 try elf_file.addCommentString();
33 try elf_file.finalizeMergeSections();
34 zig_object.claimUnresolvedRelocatable(elf_file);
35
36 try initSections(elf_file);
37 try Elf.sortShdrs(
38 gpa,
39 &elf_file.section_indexes,
40 &elf_file.sections,
41 elf_file.shstrtab.items,
42 elf_file.merge_sections.items,
43 elf_file.group_sections.items,
44 elf_file.zigObjectPtr(),
45 elf_file.files,
46 );
47 try zig_object.addAtomsToRelaSections(elf_file);
48 try elf_file.updateMergeSectionSizes();
49 try updateSectionSizes(elf_file);
50
51 try allocateAllocSections(elf_file);
52 try elf_file.allocateNonAllocSections();
53
54 if (build_options.enable_logging) {
55 state_log.debug("{f}", .{elf_file.dumpState()});
56 }
57
58 try elf_file.writeMergeSections();
59 try writeSyntheticSections(elf_file);
60 try elf_file.writeShdrTable();
61 try elf_file.writeElfHeader();
62
63 // TODO we can avoid reading in the file contents we just wrote if we give the linker
64 // ability to write directly to a buffer.
65 try zig_object.readFileContents(elf_file);
66 }
67
68 var files = std.array_list.Managed(File.Index).init(gpa);
69 defer files.deinit();
70 try files.ensureTotalCapacityPrecise(elf_file.objects.items.len + 1);
71 if (elf_file.zigObjectPtr()) |zig_object| files.appendAssumeCapacity(zig_object.index);
72 for (elf_file.objects.items) |index| files.appendAssumeCapacity(index);
73
74 // Update ar symtab from parsed objects
75 var ar_symtab: Archive.ArSymtab = .{};
76 defer ar_symtab.deinit(gpa);
77
78 for (files.items) |index| {
79 try elf_file.file(index).?.updateArSymtab(&ar_symtab, elf_file);
80 }
81
82 ar_symtab.sort();
83
84 // Save object paths in filenames strtab.
85 var ar_strtab: Archive.ArStrtab = .{};
86 defer ar_strtab.deinit(gpa);
87
88 for (files.items) |index| {
89 const file_ptr = elf_file.file(index).?;
90 try file_ptr.updateArStrtab(gpa, &ar_strtab);
91 try file_ptr.updateArSize(elf_file);
92 }
93
94 // Update file offsets of contributing objects.
95 const total_size: usize = blk: {
96 var pos: usize = elf.ARMAG.len;
97 pos += @sizeOf(elf.ar_hdr) + ar_symtab.size(.p64);
98 pos = mem.alignForward(usize, pos, 2);
99
100 if (ar_strtab.size() > 0) {
101 pos += @sizeOf(elf.ar_hdr) + ar_strtab.size();
102 pos = mem.alignForward(usize, pos, 2);
103 }
104
105 for (files.items) |index| {
106 const file_ptr = elf_file.file(index).?;
107 const state = switch (file_ptr) {
108 .zig_object => |x| &x.output_ar_state,
109 .object => |x| &x.output_ar_state,
110 else => unreachable,
111 };
112 state.file_off = pos;
113 pos += @sizeOf(elf.ar_hdr) + (math.cast(usize, state.size) orelse return error.Overflow);
114 pos = mem.alignForward(usize, pos, 2);
115 }
116
117 break :blk pos;
118 };
119
120 if (build_options.enable_logging) {
121 state_log.debug("ar_symtab\n{f}\n", .{ar_symtab.fmt(elf_file)});
122 state_log.debug("ar_strtab\n{f}\n", .{ar_strtab});
123 }
124
125 const buffer = try gpa.alloc(u8, total_size);
126 defer gpa.free(buffer);
127
128 var writer: std.Io.Writer = .fixed(buffer);
129
130 // Write magic
131 try writer.writeAll(elf.ARMAG);
132
133 // Write symtab
134 try ar_symtab.write(.p64, elf_file, &writer);
135 if (!mem.isAligned(writer.end, 2)) try writer.writeByte(0);
136
137 // Write strtab
138 if (ar_strtab.size() > 0) {
139 try ar_strtab.write(&writer);
140 if (!mem.isAligned(writer.end, 2)) try writer.writeByte(0);
141 }
142
143 // Write object files
144 for (files.items) |index| {
145 try elf_file.file(index).?.writeAr(elf_file, &writer);
146 if (!mem.isAligned(writer.end, 2)) try writer.writeByte(0);
147 }
148
149 assert(writer.buffered().len == total_size);
150
151 try elf_file.base.file.?.setLength(io, total_size);
152 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), 0);
153
154 if (diags.hasErrors()) return error.AlreadyReported;
155}
156
157pub fn flushObject(elf_file: *Elf, comp: *Compilation) !void {
158 const diags = &comp.link_diags;
159
160 if (diags.hasErrors()) return error.AlreadyReported;
161
162 // Now, we are ready to resolve the symbols across all input files.
163 // We will first resolve the files in the ZigObject, next in the parsed
164 // input Object files.
165 try elf_file.resolveSymbols();
166 elf_file.markEhFrameAtomsDead();
167 try elf_file.resolveMergeSections();
168 try elf_file.addCommentString();
169 try elf_file.finalizeMergeSections();
170 claimUnresolved(elf_file);
171
172 try initSections(elf_file);
173 try Elf.sortShdrs(
174 comp.gpa,
175 &elf_file.section_indexes,
176 &elf_file.sections,
177 elf_file.shstrtab.items,
178 elf_file.merge_sections.items,
179 elf_file.group_sections.items,
180 elf_file.zigObjectPtr(),
181 elf_file.files,
182 );
183 if (elf_file.zigObjectPtr()) |zig_object| {
184 try zig_object.addAtomsToRelaSections(elf_file);
185 }
186 for (elf_file.objects.items) |index| {
187 const object = elf_file.file(index).?.object;
188 try object.addAtomsToRelaSections(elf_file);
189 }
190 try elf_file.updateMergeSectionSizes();
191 try updateSectionSizes(elf_file);
192
193 try allocateAllocSections(elf_file);
194 try elf_file.allocateNonAllocSections();
195
196 if (build_options.enable_logging) {
197 state_log.debug("{f}", .{elf_file.dumpState()});
198 }
199
200 try writeAtoms(elf_file);
201 try elf_file.writeMergeSections();
202 try writeSyntheticSections(elf_file);
203 try elf_file.writeShdrTable();
204 try elf_file.writeElfHeader();
205
206 if (diags.hasErrors()) return error.AlreadyReported;
207}
208
209fn claimUnresolved(elf_file: *Elf) void {
210 if (elf_file.zigObjectPtr()) |zig_object| {
211 zig_object.claimUnresolvedRelocatable(elf_file);
212 }
213 for (elf_file.objects.items) |index| {
214 elf_file.file(index).?.object.claimUnresolvedRelocatable(elf_file);
215 }
216}
217
218fn initSections(elf_file: *Elf) !void {
219 if (elf_file.zigObjectPtr()) |zo| {
220 try zo.initRelaSections(elf_file);
221 }
222 for (elf_file.objects.items) |index| {
223 const object = elf_file.file(index).?.object;
224 try object.initOutputSections(elf_file);
225 try object.initRelaSections(elf_file);
226 }
227
228 for (elf_file.merge_sections.items) |*msec| {
229 if (msec.finalized_subsections.items.len == 0) continue;
230 try msec.initOutputSection(elf_file);
231 }
232
233 const needs_eh_frame = blk: {
234 if (elf_file.zigObjectPtr()) |zo|
235 if (zo.eh_frame_index != null) break :blk true;
236 break :blk for (elf_file.objects.items) |index| {
237 if (elf_file.file(index).?.object.cies.items.len > 0) break true;
238 } else false;
239 };
240 if (needs_eh_frame) {
241 if (elf_file.section_indexes.eh_frame == null) {
242 elf_file.section_indexes.eh_frame = elf_file.sectionByName(".eh_frame") orelse
243 try elf_file.addSection(.{
244 .name = try elf_file.insertShString(".eh_frame"),
245 .type = if (elf_file.getTarget().cpu.arch == .x86_64)
246 elf.SHT_X86_64_UNWIND
247 else
248 elf.SHT_PROGBITS,
249 .flags = elf.SHF_ALLOC,
250 .addralign = elf_file.ptrWidthBytes(),
251 });
252 }
253 elf_file.section_indexes.eh_frame_rela = elf_file.sectionByName(".rela.eh_frame") orelse
254 try elf_file.addRelaShdr(
255 try elf_file.insertShString(".rela.eh_frame"),
256 elf_file.section_indexes.eh_frame.?,
257 );
258 }
259
260 try initGroups(elf_file);
261 try elf_file.initSymtab();
262 try elf_file.initShStrtab();
263}
264
265fn initGroups(elf_file: *Elf) !void {
266 const gpa = elf_file.base.comp.gpa;
267
268 for (elf_file.objects.items) |index| {
269 const object = elf_file.file(index).?.object;
270 for (object.groups.items, 0..) |cg, cg_index| {
271 if (!cg.alive) continue;
272 const cg_sec = try elf_file.group_sections.addOne(gpa);
273 cg_sec.* = .{
274 .shndx = try elf_file.addSection(.{
275 .name = try elf_file.insertShString(".group"),
276 .type = elf.SHT_GROUP,
277 .entsize = @sizeOf(u32),
278 .addralign = @alignOf(u32),
279 }),
280 .cg_ref = .{ .index = @intCast(cg_index), .file = index },
281 };
282 }
283 }
284}
285
286fn updateSectionSizes(elf_file: *Elf) !void {
287 const slice = elf_file.sections.slice();
288 for (slice.items(.atom_list_2)) |*atom_list| {
289 if (atom_list.atoms.keys().len == 0) continue;
290 if (!atom_list.dirty) continue;
291 atom_list.updateSize(elf_file);
292 try atom_list.allocate(elf_file);
293 atom_list.dirty = false;
294 }
295
296 for (slice.items(.shdr), 0..) |*shdr, shndx| {
297 const atom_list = slice.items(.atom_list)[shndx];
298 if (shdr.sh_type != elf.SHT_RELA) continue;
299 if (@as(u32, @intCast(shndx)) == elf_file.section_indexes.eh_frame) continue;
300 for (atom_list.items) |ref| {
301 const atom_ptr = elf_file.atom(ref) orelse continue;
302 if (!atom_ptr.alive) continue;
303 const relocs = atom_ptr.relocs(elf_file);
304 shdr.sh_size += shdr.sh_entsize * relocs.len;
305 }
306
307 if (shdr.sh_size == 0) shdr.sh_offset = 0;
308 }
309
310 if (elf_file.section_indexes.eh_frame) |index| {
311 slice.items(.shdr)[index].sh_size = try eh_frame.calcEhFrameSize(elf_file);
312 }
313 if (elf_file.section_indexes.eh_frame_rela) |index| {
314 const shdr = &slice.items(.shdr)[index];
315 shdr.sh_size = eh_frame.calcEhFrameRelocs(elf_file) * shdr.sh_entsize;
316 }
317
318 try elf_file.updateSymtabSize();
319 updateGroupsSizes(elf_file);
320 elf_file.updateShStrtabSize();
321}
322
323fn updateGroupsSizes(elf_file: *Elf) void {
324 for (elf_file.group_sections.items) |cg| {
325 const shdr = &elf_file.sections.items(.shdr)[cg.shndx];
326 shdr.sh_size = cg.size(elf_file);
327 shdr.sh_link = elf_file.section_indexes.symtab.?;
328
329 const sym = cg.symbol(elf_file);
330 shdr.sh_info = sym.outputSymtabIndex(elf_file) orelse sym.outputShndx(elf_file).?;
331 }
332}
333
334/// Allocates alloc sections when merging relocatable objects files together.
335fn allocateAllocSections(elf_file: *Elf) !void {
336 for (elf_file.sections.items(.shdr), 0..) |*shdr, shndx| {
337 if (shdr.sh_type == elf.SHT_NULL) continue;
338 if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue;
339 if (shdr.sh_type == elf.SHT_NOBITS) {
340 shdr.sh_offset = 0;
341 continue;
342 }
343 const needed_size = shdr.sh_size;
344 if (needed_size > elf_file.allocatedSize(shdr.sh_offset)) {
345 shdr.sh_size = 0;
346 const new_offset = try elf_file.findFreeSpace(needed_size, shdr.sh_addralign);
347
348 log.debug("moving {s} from 0x{x} to 0x{x}", .{
349 elf_file.getShString(shdr.sh_name),
350 shdr.sh_offset,
351 new_offset,
352 });
353
354 if (shdr.sh_offset > 0) {
355 const existing_size = elf_file.sectionSize(@intCast(shndx));
356 try elf_file.base.copyRangeAll(shdr.sh_offset, new_offset, existing_size);
357 }
358
359 shdr.sh_offset = new_offset;
360 shdr.sh_size = needed_size;
361 }
362 }
363}
364
365fn writeAtoms(elf_file: *Elf) !void {
366 const gpa = elf_file.base.comp.gpa;
367
368 var buffer = std.array_list.Managed(u8).init(gpa);
369 defer buffer.deinit();
370
371 const slice = elf_file.sections.slice();
372 for (slice.items(.shdr), slice.items(.atom_list_2)) |shdr, atom_list| {
373 if (shdr.sh_type == elf.SHT_NOBITS) continue;
374 if (atom_list.atoms.keys().len == 0) continue;
375 try atom_list.writeRelocatable(&buffer, elf_file);
376 }
377}
378
379fn writeSyntheticSections(elf_file: *Elf) !void {
380 const comp = elf_file.base.comp;
381 const io = comp.io;
382 const gpa = comp.gpa;
383 const slice = elf_file.sections.slice();
384
385 const SortRelocs = struct {
386 pub fn lessThan(ctx: void, lhs: elf.Elf64_Rela, rhs: elf.Elf64_Rela) bool {
387 _ = ctx;
388 return lhs.r_offset < rhs.r_offset;
389 }
390 };
391
392 for (slice.items(.shdr), slice.items(.atom_list), 0..) |shdr, atom_list, shndx| {
393 if (shdr.sh_type != elf.SHT_RELA) continue;
394 if (atom_list.items.len == 0) continue;
395 if (@as(u32, @intCast(shndx)) == elf_file.section_indexes.eh_frame) continue;
396
397 const num_relocs = math.cast(usize, @divExact(shdr.sh_size, shdr.sh_entsize)) orelse
398 return error.Overflow;
399 var relocs = try std.array_list.Managed(elf.Elf64_Rela).initCapacity(gpa, num_relocs);
400 defer relocs.deinit();
401
402 for (atom_list.items) |ref| {
403 const atom_ptr = elf_file.atom(ref) orelse continue;
404 if (!atom_ptr.alive) continue;
405 try atom_ptr.writeRelocs(elf_file, &relocs);
406 }
407 assert(relocs.items.len == num_relocs);
408 // Sort output relocations by r_offset which is usually an expected (and desired) condition
409 // by the linkers.
410 mem.sortUnstable(elf.Elf64_Rela, relocs.items, {}, SortRelocs.lessThan);
411
412 log.debug("writing {s} from 0x{x} to 0x{x}", .{
413 elf_file.getShString(shdr.sh_name),
414 shdr.sh_offset,
415 shdr.sh_offset + shdr.sh_size,
416 });
417
418 try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset);
419 }
420
421 if (elf_file.section_indexes.eh_frame) |shndx| {
422 const existing_size = existing_size: {
423 const zo = elf_file.zigObjectPtr() orelse break :existing_size 0;
424 const sym = zo.symbol(zo.eh_frame_index orelse break :existing_size 0);
425 break :existing_size sym.atom(elf_file).?.size;
426 };
427 const shdr = slice.items(.shdr)[shndx];
428 const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
429 const buffer = try gpa.alloc(u8, @intCast(sh_size - existing_size));
430 defer gpa.free(buffer);
431 var writer: std.Io.Writer = .fixed(buffer);
432 try eh_frame.writeEhFrameRelocatable(elf_file, &writer);
433 log.debug("writing .eh_frame from 0x{x} to 0x{x}", .{
434 shdr.sh_offset + existing_size,
435 shdr.sh_offset + sh_size,
436 });
437 assert(writer.buffered().len == sh_size - existing_size);
438 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset + existing_size);
439 }
440 if (elf_file.section_indexes.eh_frame_rela) |shndx| {
441 const shdr = slice.items(.shdr)[shndx];
442 const num_relocs = math.cast(usize, @divExact(shdr.sh_size, shdr.sh_entsize)) orelse
443 return error.Overflow;
444 var relocs = try std.array_list.Managed(elf.Elf64_Rela).initCapacity(gpa, num_relocs);
445 defer relocs.deinit();
446 try eh_frame.writeEhFrameRelocs(elf_file, &relocs);
447 assert(relocs.items.len == num_relocs);
448 // Sort output relocations by r_offset which is usually an expected (and desired) condition
449 // by the linkers.
450 mem.sortUnstable(elf.Elf64_Rela, relocs.items, {}, SortRelocs.lessThan);
451
452 log.debug("writing .rela.eh_frame from 0x{x} to 0x{x}", .{
453 shdr.sh_offset,
454 shdr.sh_offset + shdr.sh_size,
455 });
456 try elf_file.base.file.?.writePositionalAll(io, @ptrCast(relocs.items), shdr.sh_offset);
457 }
458
459 try writeGroups(elf_file);
460 try elf_file.writeSymtab();
461 try elf_file.writeShStrtab();
462}
463
464fn writeGroups(elf_file: *Elf) !void {
465 const comp = elf_file.base.comp;
466 const io = comp.io;
467 const gpa = comp.gpa;
468 for (elf_file.group_sections.items) |cgs| {
469 const shdr = elf_file.sections.items(.shdr)[cgs.shndx];
470 const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
471 const buffer = try gpa.alloc(u8, sh_size);
472 defer gpa.free(buffer);
473 var writer: std.Io.Writer = .fixed(buffer);
474 try cgs.write(elf_file, &writer);
475 assert(writer.buffered().len == sh_size);
476 log.debug("writing group from 0x{x} to 0x{x}", .{
477 shdr.sh_offset,
478 shdr.sh_offset + shdr.sh_size,
479 });
480 try elf_file.base.file.?.writePositionalAll(io, writer.buffered(), shdr.sh_offset);
481 }
482}