lib/tldr/src/formats/elf/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3 const tldr = @import("../../root.zig");
4 const elf = @import("root.zig");
5 const link = @import("link.zig");
6 const metadata = @import("metadata.zig");
7
8 pub const address_suite = @import("address/test.zig");
9 pub const archive_suite = @import("archive/test.zig");
10 pub const ehframe_suite = @import("ehframe/test.zig");
11 pub const group_suite = @import("group/test.zig");
12 pub const icf_suite = @import("icf/test.zig");
13 pub const image_suite = @import("image/test.zig");
14 pub const layout_suite = @import("layout/test.zig");
15 pub const liveness_suite = @import("liveness/test.zig");
16 pub const merge_suite = @import("merge/test.zig");
17 pub const object_suite = @import("object/test.zig");
18 pub const relocation_suite = @import("relocation/test.zig");
19
20 const archive = tldr.archive;
21 const incremental = tldr.incremental;
22 const model = tldr.model;
23 const parallel = tldr.parallel;
24 const format = elf.format;
25 const build_id_note = elf.note;
26 const ifunc = elf.ifunc;
27 const payload = elf.payload;
28 const symbol_table = elf.symbol_table;
29
30 const ehdr_size = format.ehdr_size;
31 const phdr_size = format.phdr_size;
32 const eh_frame_hdr_fixed_size = format.eh_frame_hdr_fixed_size;
33 const eh_frame_hdr_entry_size = format.eh_frame_hdr_entry_size;
34 const dwarf_eh_pe_udata4 = format.dwarf_eh_pe_udata4;
35 const dwarf_eh_pe_sdata4 = format.dwarf_eh_pe_sdata4;
36 const dwarf_eh_pe_pcrel = format.dwarf_eh_pe_pcrel;
37 const dwarf_eh_pe_datarel = format.dwarf_eh_pe_datarel;
38 const buildIdNoteSize = build_id_note.size;
39 const alignForward = format.alignForward;
40 const alignForwardU64 = format.alignForwardU64;
41 const hashU64 = format.hashU64;
42 const readU16 = format.readU16;
43 const readU32 = format.readU32;
44 const readU64 = format.readU64;
45
46 const Allocator = std.mem.Allocator;
47 const linkExecutable = elf.linkExecutable;
48 const patchIncrementalMetadata = elf.patchIncrementalMetadata;
49 const object_writer = elf.object;
50 const Section = object_writer.Section;
51 const Symbol = object_writer.Symbol;
52 const Relocation = object_writer.Relocation;
53 const groupBytes = object_writer.groupBytes;
54 const buildObject = object_writer.build;
55
56 fn coffHeaderBytes() [@sizeOf(std.coff.Header)]u8 {
57 var bytes = @as([@sizeOf(std.coff.Header)]u8, @splat(0));
58 std.mem.writeInt(u16, bytes[0..2], @backingInt(std.coff.IMAGE.FILE.MACHINE.AMD64), .little);
59 return bytes;
60 }
61
62 fn expectAllZeroes(bytes: []const u8) !void {
63 for (bytes) |byte| try std.testing.expectEqual(@as(u8, 0), byte);
64 }
65
66 test "ELF linker emits executable with incremental section manifest" {
67 const allocator = std.testing.allocator;
68 const text = [_]u8{
69 0xb8, 0x3c, 0x00, 0x00, 0x00,
70 0x31, 0xff, 0x0f, 0x05,
71 };
72 const text_index: u16 = 1;
73 const object = try buildObject(allocator, .{
74 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
75 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
76 });
77 defer allocator.free(object);
78
79 var linked = try linkExecutable(allocator, &.{.{ .name = "start.o", .bytes = object }}, .{});
80 defer linked.deinit(allocator);
81
82 try std.testing.expectEqualSlices(u8, std.elf.MAGIC, linked.bytes[0..4]);
83 try std.testing.expectEqual(@as(u16, @backingInt(std.elf.ET.EXEC)), readU16(linked.bytes, 16));
84 try std.testing.expectEqual(@as(u16, 2), readU16(linked.bytes, 56));
85 try std.testing.expect(linked.manifest.canReuseFor(.{}, &.{.{ .name = "start.o", .bytes = object }}));
86 try std.testing.expectEqualStrings(".text", linked.manifest.string(linked.manifest.sections[0].name_id));
87 try std.testing.expect(linked.manifest.sections[0].reserved_size > linked.manifest.sections[0].size);
88
89 const text_contribution = metadata.manifestContribution(linked.manifest, "start.o", .section, ".text", text_index) orelse return error.MissingSection;
90 try std.testing.expectEqualStrings(".text", linked.manifest.string(text_contribution.output_section_name_id));
91 try std.testing.expectEqual(linked.manifest.sections[0].address, text_contribution.address);
92 try std.testing.expectEqual(linked.manifest.sections[0].file_offset, text_contribution.file_offset);
93 try std.testing.expectEqual(@as(u64, text.len), text_contribution.size);
94 try std.testing.expect(text_contribution.reserved_size > text_contribution.size);
95 _ = try metadata.programHeaderByType(linked.bytes, std.elf.PT_PHDR);
96 _ = try metadata.loadProgramHeaderByAddress(linked.bytes, text_contribution.address);
97
98 const plan = linked.manifest.planContributionReplacement(&.{
99 .{ .input_name = "start.o", .input_index = 0, .kind = .section, .name = ".text", .ordinal = text_index, .size = text.len + 1, .alignment = 16 },
100 });
101 try std.testing.expectEqual(incremental.PatchDecision.in_place, plan.decision);
102 }
103
104 test "ELF linker mapped output matches heap output" {
105 const allocator = std.testing.allocator;
106 const text = [_]u8{
107 0xb8, 0x3c, 0x00, 0x00, 0x00,
108 0x31, 0xff, 0x0f, 0x05,
109 };
110 const text_index: u16 = 1;
111 const object = try buildObject(allocator, .{
112 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
113 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
114 });
115 defer allocator.free(object);
116 const inputs = [_]model.Input{.{ .name = "start.o", .bytes = object }};
117
118 var heap_linked = try linkExecutable(allocator, &inputs, .{});
119 defer heap_linked.deinit(allocator);
120 try std.testing.expect(!heap_linked.isMaterialized());
121
122 var tmp = std.testing.tmpDir(.{});
123 defer tmp.cleanup();
124 var output_file = try tmp.dir.createFile(sys.fs.debugIo(), "mapped.out", .{ .read = true });
125 defer output_file.close(sys.fs.debugIo());
126
127 var mapped_linked = try linkExecutable(allocator, &inputs, .{
128 .mapped_output_file = output_file,
129 .mapped_output_min_size = 0,
130 });
131 try std.testing.expect(mapped_linked.isMaterialized());
132 try std.testing.expectEqualSlices(u8, heap_linked.bytes, mapped_linked.bytes);
133 const mapped_len = mapped_linked.bytes.len;
134 mapped_linked.deinit(allocator);
135
136 const file_bytes = try tmp.dir.readFileAlloc(sys.fs.debugIo(), "mapped.out", allocator, .limited(mapped_len + 1));
137 defer allocator.free(file_bytes);
138 try std.testing.expectEqualSlices(u8, heap_linked.bytes, file_bytes);
139 }
140
141 test "ELF linker emits GNU SHA1 build id note" {
142 const allocator = std.testing.allocator;
143 const text = [_]u8{0xc3};
144 const text_index: u16 = 1;
145 const object = try buildObject(allocator, .{
146 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
147 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
148 });
149 defer allocator.free(object);
150
151 var linked = try linkExecutable(allocator, &.{.{ .name = "build-id.o", .bytes = object }}, .{
152 .incremental_mode = .off,
153 .build_id = .sha1,
154 });
155 defer linked.deinit(allocator);
156
157 const build_note = try metadata.linkedSectionByName(linked.bytes, build_id_note.section_name);
158 try std.testing.expectEqual(std.elf.SHT_NOTE, build_note.section_type);
159 try std.testing.expectEqual(std.elf.SHF_ALLOC, build_note.flags);
160 try std.testing.expectEqual(buildIdNoteSize(.sha1), build_note.size);
161 try std.testing.expectEqual(@as(u64, 4), build_note.alignment);
162
163 const note_program = try metadata.programHeaderByType(linked.bytes, std.elf.PT_NOTE);
164 try std.testing.expectEqual(build_note.offset, note_program.file_offset);
165 try std.testing.expectEqual(build_note.address, note_program.virtual_address);
166 try std.testing.expectEqual(build_note.size, note_program.file_size);
167 try std.testing.expectEqual(build_note.size, note_program.memory_size);
168
169 const start: usize = @intCast(build_note.offset);
170 try std.testing.expectEqual(@as(u32, 4), readU32(linked.bytes, start + 0));
171 try std.testing.expectEqual(@as(u32, build_id_note.sha1_desc_size), readU32(linked.bytes, start + 4));
172 try std.testing.expectEqual(@as(u32, std.elf.NT_GNU_BUILD_ID), readU32(linked.bytes, start + 8));
173 try std.testing.expectEqualSlices(u8, "GNU\x00", linked.bytes[start + 12 .. start + 16]);
174
175 const comparable = try allocator.dupe(u8, linked.bytes);
176 defer allocator.free(comparable);
177 @memset(comparable[start + build_id_note.desc_offset ..][0..build_id_note.sha1_desc_size], 0);
178 var expected: [build_id_note.sha1_desc_size]u8 = undefined;
179 std.crypto.hash.Sha1.hash(comparable, &expected, .{});
180 try std.testing.expectEqualSlices(u8, &expected, linked.bytes[start + build_id_note.desc_offset ..][0..build_id_note.sha1_desc_size]);
181
182 const note_record = metadata.manifestSection(linked.manifest, build_id_note.section_name) orelse return error.MissingSection;
183 try std.testing.expectEqual(build_note.offset, note_record.file_offset);
184 }
185
186 test "ELF linker emits fast GNU build id note" {
187 const allocator = std.testing.allocator;
188 const text = [_]u8{0xc3};
189 const text_index: u16 = 1;
190 const object = try buildObject(allocator, .{
191 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
192 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
193 });
194 defer allocator.free(object);
195
196 var linked = try linkExecutable(allocator, &.{.{ .name = "build-id-fast.o", .bytes = object }}, .{
197 .incremental_mode = .off,
198 .build_id = .fast,
199 });
200 defer linked.deinit(allocator);
201
202 const build_note = try metadata.linkedSectionByName(linked.bytes, build_id_note.section_name);
203 try std.testing.expectEqual(std.elf.SHT_NOTE, build_note.section_type);
204 try std.testing.expectEqual(std.elf.SHF_ALLOC, build_note.flags);
205 try std.testing.expectEqual(buildIdNoteSize(.fast), build_note.size);
206
207 const start: usize = @intCast(build_note.offset);
208 try std.testing.expectEqual(@as(u32, 4), readU32(linked.bytes, start + 0));
209 try std.testing.expectEqual(@as(u32, build_id_note.fast_desc_size), readU32(linked.bytes, start + 4));
210 try std.testing.expectEqual(@as(u32, std.elf.NT_GNU_BUILD_ID), readU32(linked.bytes, start + 8));
211 try std.testing.expectEqualSlices(u8, "GNU\x00", linked.bytes[start + 12 .. start + 16]);
212
213 const comparable = try allocator.dupe(u8, linked.bytes);
214 defer allocator.free(comparable);
215 @memset(comparable[start + build_id_note.desc_offset ..][0..build_id_note.fast_desc_size], 0);
216 var hasher = std.hash.XxHash64.init(0);
217 hasher.update(comparable);
218 try std.testing.expectEqual(hasher.final(), readU64(linked.bytes, start + build_id_note.desc_offset));
219 }
220
221 test "ELF linker zeroes executable padding without clearing payloads" {
222 const allocator = std.testing.allocator;
223 const start_text = [_]u8{0xc3};
224 const helper_text = [_]u8{ 0x90, 0xc3 };
225 const start_index: u16 = 1;
226 const helper_index: u16 = 2;
227 const object = try buildObject(allocator, .{
228 .sections = &.{
229 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 1),
230 Section.progbits(".text.helper", &helper_text, std.elf.SHF_EXECINSTR, 16),
231 },
232 .symbols = &.{
233 Symbol.section(start_index),
234 Symbol.section(helper_index),
235 Symbol.function("_start", start_index, 0, start_text.len),
236 Symbol.function("helper", helper_index, 0, helper_text.len),
237 },
238 });
239 defer allocator.free(object);
240
241 var linked = try linkExecutable(allocator, &.{.{ .name = "padding.o", .bytes = object }}, .{});
242 defer linked.deinit(allocator);
243
244 try expectAllZeroes(linked.bytes[8..16]);
245
246 const start_contribution = metadata.manifestContribution(linked.manifest, "padding.o", .section, ".text.start", start_index) orelse return error.MissingSection;
247 const helper_contribution = metadata.manifestContribution(linked.manifest, "padding.o", .section, ".text.helper", helper_index) orelse return error.MissingSection;
248 try std.testing.expectEqualSlices(u8, &start_text, linked.bytes[@intCast(start_contribution.file_offset)..][0..start_text.len]);
249 try std.testing.expectEqualSlices(u8, &helper_text, linked.bytes[@intCast(helper_contribution.file_offset)..][0..helper_text.len]);
250 try expectAllZeroes(linked.bytes[@intCast(start_contribution.file_offset + start_contribution.size)..@intCast(helper_contribution.file_offset)]);
251 try expectAllZeroes(linked.bytes[@intCast(helper_contribution.file_offset + helper_contribution.size)..@intCast(helper_contribution.file_offset + helper_contribution.reserved_size)]);
252 }
253
254 test "ELF linker keeps loadable file ranges compact and page-congruent" {
255 const allocator = std.testing.allocator;
256 const text = [_]u8{0xc3};
257 const rodata = [_]u8{ 1, 2, 3, 4 };
258 const text_index: u16 = 1;
259 const object = try buildObject(allocator, .{
260 .sections = &.{
261 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
262 Section.progbits(".rodata", &rodata, 0, 16),
263 },
264 .symbols = &.{
265 Symbol.section(text_index),
266 Symbol.section(2),
267 Symbol.function("_start", text_index, 0, text.len),
268 },
269 });
270 defer allocator.free(object);
271
272 const options = model.LinkOptions{};
273 var linked = try linkExecutable(allocator, &.{.{ .name = "compact.o", .bytes = object }}, options);
274 defer linked.deinit(allocator);
275
276 const text_section = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
277 const rodata_section = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
278 try std.testing.expect(text_section.file_offset < options.page_size);
279 try std.testing.expect(rodata_section.file_offset < options.page_size);
280 try std.testing.expect(rodata_section.address >= alignForwardU64(text_section.address + text_section.reserved_size, options.page_size));
281
282 const text_program = try metadata.loadProgramHeaderByAddress(linked.bytes, text_section.address);
283 const rodata_program = try metadata.loadProgramHeaderByAddress(linked.bytes, rodata_section.address);
284 const phdr_program = try metadata.programHeaderByType(linked.bytes, std.elf.PT_PHDR);
285 try std.testing.expectEqual(@as(u64, ehdr_size), phdr_program.file_offset);
286 try std.testing.expectEqual(@as(u64, readU16(linked.bytes, 56)) * phdr_size, phdr_program.file_size);
287 try std.testing.expectEqual(@as(u64, 0), text_program.file_offset);
288 try std.testing.expectEqual(rodata_section.file_offset, rodata_program.file_offset);
289 try std.testing.expectEqual(text_program.file_offset % options.page_size, text_program.virtual_address % options.page_size);
290 try std.testing.expectEqual(rodata_program.file_offset % options.page_size, rodata_program.virtual_address % options.page_size);
291 try std.testing.expectEqual(text_section.file_offset - text_program.file_offset, text_section.address - text_program.virtual_address);
292 }
293
294 test "ELF linker records duplicate section names with distinct ordinals" {
295 const allocator = std.testing.allocator;
296 const start_text = [_]u8{0xc3};
297 const helper_text = [_]u8{ 0x90, 0xc3 };
298 const start_index: u16 = 1;
299 const helper_index: u16 = 2;
300 const object = try buildObject(allocator, .{
301 .sections = &.{
302 Section.progbits(".text", &start_text, std.elf.SHF_EXECINSTR, 16),
303 Section.progbits(".text", &helper_text, std.elf.SHF_EXECINSTR, 16),
304 },
305 .symbols = &.{
306 Symbol.section(start_index),
307 Symbol.section(helper_index),
308 Symbol.function("_start", start_index, 0, start_text.len),
309 Symbol.function("helper", helper_index, 0, helper_text.len),
310 },
311 });
312 defer allocator.free(object);
313
314 var linked = try linkExecutable(allocator, &.{.{ .name = "dup.o", .bytes = object }}, .{});
315 defer linked.deinit(allocator);
316
317 const start_contribution = metadata.manifestContribution(linked.manifest, "dup.o", .section, ".text", start_index) orelse return error.MissingSection;
318 const helper_contribution = metadata.manifestContribution(linked.manifest, "dup.o", .section, ".text", helper_index) orelse return error.MissingSection;
319 try std.testing.expectEqual(@as(u64, start_text.len), start_contribution.size);
320 try std.testing.expectEqual(@as(u64, helper_text.len), helper_contribution.size);
321 try std.testing.expect(helper_contribution.address > start_contribution.address);
322
323 const helper_plan = linked.manifest.planContributionReplacement(&.{
324 .{ .input_name = "dup.o", .input_index = 0, .kind = .section, .name = ".text", .ordinal = helper_index, .size = helper_text.len + 1, .alignment = 16 },
325 });
326 try std.testing.expectEqual(incremental.PatchDecision.in_place, helper_plan.decision);
327 }
328
329 test "ELF linker applies PC-relative relocations across objects" {
330 const allocator = std.testing.allocator;
331
332 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
333 const caller_text_index: u16 = 1;
334 const callee_symbol: u32 = 3;
335 const caller_object = try buildObject(allocator, .{
336 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
337 .symbols = &.{
338 Symbol.section(caller_text_index),
339 Symbol.function("_start", caller_text_index, 0, caller_text.len),
340 Symbol.undefinedFunction("callee"),
341 },
342 .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
343 });
344 defer allocator.free(caller_object);
345
346 const callee_text = [_]u8{0xc3};
347 const callee_text_index: u16 = 1;
348 const callee_object = try buildObject(allocator, .{
349 .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
350 .symbols = &.{
351 Symbol.section(callee_text_index),
352 Symbol.function("callee", callee_text_index, 0, callee_text.len),
353 },
354 });
355 defer allocator.free(callee_object);
356
357 var linked = try linkExecutable(allocator, &.{
358 .{ .name = "caller.o", .bytes = caller_object },
359 .{ .name = "callee.o", .bytes = callee_object },
360 }, .{ .incremental_mode = .off });
361 defer linked.deinit(allocator);
362
363 const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
364 const patched = std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little);
365 try std.testing.expectEqual(@as(i32, 11), patched);
366 }
367
368 test "ELF incremental candidate relink patches changed relocation payloads" {
369 const allocator = std.testing.allocator;
370 const options = model.LinkOptions{};
371
372 const old_caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
373 const old_caller_text_index: u16 = 1;
374 const old_callee_symbol: u32 = 3;
375 const old_caller_object = try buildObject(allocator, .{
376 .sections = &.{Section.progbits(".text", &old_caller_text, std.elf.SHF_EXECINSTR, 16)},
377 .symbols = &.{
378 Symbol.section(old_caller_text_index),
379 Symbol.function("_start", old_caller_text_index, 0, old_caller_text.len),
380 Symbol.undefinedFunction("callee"),
381 },
382 .relocations = &.{Relocation.x86_64(1, 1, old_callee_symbol, .PLT32, -4)},
383 });
384 defer allocator.free(old_caller_object);
385
386 const new_caller_text = [_]u8{ 0x90, 0xe8, 0, 0, 0, 0 };
387 const new_caller_text_index: u16 = 1;
388 const new_callee_symbol: u32 = 3;
389 const new_caller_object = try buildObject(allocator, .{
390 .sections = &.{Section.progbits(".text", &new_caller_text, std.elf.SHF_EXECINSTR, 16)},
391 .symbols = &.{
392 Symbol.section(new_caller_text_index),
393 Symbol.function("_start", new_caller_text_index, 0, new_caller_text.len),
394 Symbol.undefinedFunction("callee"),
395 },
396 .relocations = &.{Relocation.x86_64(1, 2, new_callee_symbol, .PLT32, -4)},
397 });
398 defer allocator.free(new_caller_object);
399
400 const callee_text = [_]u8{0xc3};
401 const callee_text_index: u16 = 1;
402 const callee_object = try buildObject(allocator, .{
403 .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
404 .symbols = &.{
405 Symbol.section(callee_text_index),
406 Symbol.function("callee", callee_text_index, 0, callee_text.len),
407 },
408 });
409 defer allocator.free(callee_object);
410
411 const old_inputs = [_]model.Input{
412 .{ .name = "caller.o", .bytes = old_caller_object },
413 .{ .name = "callee.o", .bytes = callee_object },
414 };
415 const new_inputs = [_]model.Input{
416 .{ .name = "caller.o", .bytes = new_caller_object },
417 .{ .name = "callee.o", .bytes = callee_object },
418 };
419
420 var old_linked = try linkExecutable(allocator, &old_inputs, options);
421 defer old_linked.deinit(allocator);
422 var state = try old_linked.prepareIncrementalState(allocator);
423 defer state.deinit(allocator);
424
425 var candidate = try linkExecutable(allocator, &new_inputs, options);
426 defer candidate.deinit(allocator);
427
428 const input_change_limits = incremental.InputChangeStorage.Limits.inspect(
429 incremental.RecordedInputs.fromManifest(&state.manifest),
430 &new_inputs,
431 );
432 const input_change_capacity = try incremental.InputChangeStorage.Capacity.derive(
433 input_change_limits,
434 );
435 const input_change_bytes = try allocator.alignedAlloc(
436 u8,
437 .fromByteUnits(incremental.InputChangeStorage.storage_alignment),
438 input_change_capacity.storage_bytes,
439 );
440 defer allocator.free(input_change_bytes);
441 var input_change_storage = try incremental.InputChangeStorage.init(
442 input_change_bytes,
443 input_change_limits,
444 );
445 defer _ = input_change_storage.deinit();
446 input_change_storage.activate();
447 const application = try state.applyChangedInputRelinkFromCandidate(
448 allocator,
449 &input_change_storage,
450 old_linked.bytes,
451 options,
452 &new_inputs,
453 candidate.bytes,
454 candidate.manifest,
455 );
456
457 try std.testing.expectEqual(incremental.RelinkDecision.in_place, application.plan.decision);
458 try std.testing.expectEqual(@as(usize, 1), application.contributions_written);
459 try std.testing.expectEqualSlices(u8, candidate.bytes, old_linked.bytes);
460 }
461
462 test "ELF incremental candidate relink patches metadata after contribution growth" {
463 const allocator = std.testing.allocator;
464 const options = model.LinkOptions{ .build_id = .fast };
465
466 const old_text = [_]u8{0xc3};
467 const old_text_index: u16 = 1;
468 const old_object = try buildObject(allocator, .{
469 .sections = &.{Section.progbits(".text", &old_text, std.elf.SHF_EXECINSTR, 16)},
470 .symbols = &.{ Symbol.section(old_text_index), Symbol.function("_start", old_text_index, 0, old_text.len) },
471 });
472 defer allocator.free(old_object);
473
474 const new_text = [_]u8{ 0x90, 0xc3 };
475 const new_text_index: u16 = 1;
476 const new_object = try buildObject(allocator, .{
477 .sections = &.{Section.progbits(".text", &new_text, std.elf.SHF_EXECINSTR, 16)},
478 .symbols = &.{ Symbol.section(new_text_index), Symbol.function("_start", new_text_index, 0, new_text.len) },
479 });
480 defer allocator.free(new_object);
481
482 const old_inputs = [_]model.Input{.{ .name = "start.o", .bytes = old_object }};
483 const new_inputs = [_]model.Input{.{ .name = "start.o", .bytes = new_object }};
484
485 var old_linked = try linkExecutable(allocator, &old_inputs, options);
486 defer old_linked.deinit(allocator);
487 var state = try old_linked.prepareIncrementalState(allocator);
488 defer state.deinit(allocator);
489
490 var candidate = try linkExecutable(allocator, &new_inputs, options);
491 defer candidate.deinit(allocator);
492
493 const input_change_limits = incremental.InputChangeStorage.Limits.inspect(
494 incremental.RecordedInputs.fromManifest(&state.manifest),
495 &new_inputs,
496 );
497 const input_change_capacity = try incremental.InputChangeStorage.Capacity.derive(
498 input_change_limits,
499 );
500 const input_change_bytes = try allocator.alignedAlloc(
501 u8,
502 .fromByteUnits(incremental.InputChangeStorage.storage_alignment),
503 input_change_capacity.storage_bytes,
504 );
505 defer allocator.free(input_change_bytes);
506 var input_change_storage = try incremental.InputChangeStorage.init(
507 input_change_bytes,
508 input_change_limits,
509 );
510 defer _ = input_change_storage.deinit();
511 input_change_storage.activate();
512 const input_changes = try state.classifyInputs(
513 &input_change_storage,
514 &new_inputs,
515 );
516 const replacements = try incremental.replacementContributionsFromImage(
517 allocator,
518 candidate.bytes,
519 candidate.manifest,
520 input_changes,
521 );
522 defer allocator.free(replacements);
523
524 const application = try state.applyChangedInputRelinkFromInputChanges(
525 options,
526 input_changes,
527 old_linked.bytes,
528 replacements,
529 );
530 try std.testing.expectEqual(incremental.RelinkDecision.in_place, application.plan.decision);
531 try std.testing.expect(!std.mem.eql(u8, candidate.bytes, old_linked.bytes));
532
533 try patchIncrementalMetadata(old_linked.bytes, candidate.bytes);
534 try state.updateManifestForCandidateRelinkFromInputChanges(
535 allocator,
536 &new_inputs,
537 input_changes,
538 replacements,
539 candidate.manifest,
540 );
541
542 const text_header = try metadata.linkedSectionByName(old_linked.bytes, ".text");
543 const candidate_text_header = try metadata.linkedSectionByName(candidate.bytes, ".text");
544 try std.testing.expectEqual(candidate_text_header.size, text_header.size);
545 try std.testing.expectEqual(candidate.manifest.sections[0].size, state.manifest.sections[0].size);
546 try std.testing.expectEqual(@as(u64, new_text.len), state.manifest.contributions[0].size);
547 try std.testing.expectEqualSlices(u8, candidate.bytes, old_linked.bytes);
548 }
549
550 test "ELF linker applies repeated relocations to one global symbol" {
551 const allocator = std.testing.allocator;
552
553 const caller_text = [_]u8{
554 0xe8, 0, 0, 0, 0,
555 0xe8, 0, 0, 0, 0,
556 0xe8, 0, 0, 0, 0,
557 0xc3,
558 };
559 const caller_text_index: u16 = 1;
560 const callee_symbol: u32 = 3;
561 const caller_object = try buildObject(allocator, .{
562 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
563 .symbols = &.{
564 Symbol.section(caller_text_index),
565 Symbol.function("_start", caller_text_index, 0, caller_text.len),
566 Symbol.undefinedFunction("callee"),
567 },
568 .relocations = &.{
569 Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4),
570 Relocation.x86_64(1, 6, callee_symbol, .PLT32, -4),
571 Relocation.x86_64(1, 11, callee_symbol, .PLT32, -4),
572 },
573 });
574 defer allocator.free(caller_object);
575
576 const callee_text = [_]u8{0xc3};
577 const callee_text_index: u16 = 1;
578 const callee_object = try buildObject(allocator, .{
579 .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
580 .symbols = &.{
581 Symbol.section(callee_text_index),
582 Symbol.function("callee", callee_text_index, 0, callee_text.len),
583 },
584 });
585 defer allocator.free(callee_object);
586
587 var linked = try linkExecutable(allocator, &.{
588 .{ .name = "caller.o", .bytes = caller_object },
589 .{ .name = "callee.o", .bytes = callee_object },
590 }, .{ .incremental_mode = .off });
591 defer linked.deinit(allocator);
592
593 const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
594 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
595 try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
596 try std.testing.expectEqual(@as(i32, 1), std.mem.readInt(i32, linked.bytes[text_file_offset + 11 ..][0..4], .little));
597 }
598
599 test "ELF linker resolves absolute symbol relocations" {
600 const allocator = std.testing.allocator;
601
602 const text = [_]u8{
603 0x48, 0xb8,
604 0, 0,
605 0, 0,
606 0, 0,
607 0, 0,
608 0xc3,
609 };
610 const text_index: u16 = 1;
611 const absolute_symbol: u32 = 3;
612 const caller_object = try buildObject(allocator, .{
613 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
614 .symbols = &.{
615 Symbol.section(text_index),
616 Symbol.function("_start", text_index, 0, text.len),
617 Symbol.undefinedObject("absolute_value"),
618 },
619 .relocations = &.{Relocation.x86_64(text_index, 2, absolute_symbol, .@"64", 5)},
620 });
621 defer allocator.free(caller_object);
622
623 const absolute_object = try buildObject(allocator, .{
624 .sections = &.{},
625 .symbols = &.{Symbol.absoluteObject("absolute_value", 37, 0)},
626 });
627 defer allocator.free(absolute_object);
628
629 var linked = try linkExecutable(
630 allocator,
631 &.{
632 .{ .name = "caller.o", .bytes = caller_object },
633 .{ .name = "absolute.o", .bytes = absolute_object },
634 },
635 .{
636 .incremental_mode = .off,
637 .gc_sections = true,
638 },
639 );
640 defer linked.deinit(allocator);
641
642 const text_section = try metadata.linkedSectionByName(linked.bytes, ".text");
643 try std.testing.expectEqual(@as(u64, 42), readU64(linked.bytes, @intCast(text_section.offset + 2)));
644 }
645
646 test "ELF linker resolves signed absolute relocations" {
647 const allocator = std.testing.allocator;
648
649 const text = [_]u8{0xc3};
650 const data = @as([12]u8, @splat(0));
651 const text_index: u16 = 1;
652 const data_index: u16 = 2;
653 const neg_symbol: u32 = 4;
654 const caller_object = try buildObject(allocator, .{
655 .sections = &.{
656 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
657 Section.progbits(".data.signed", &data, std.elf.SHF_WRITE, 8),
658 },
659 .symbols = &.{
660 Symbol.section(text_index),
661 Symbol.section(data_index),
662 Symbol.function("_start", text_index, 0, text.len),
663 Symbol.undefinedObject("negative_absolute"),
664 },
665 .relocations = &.{
666 Relocation.x86_64(data_index, 0, neg_symbol, .@"32S", 0),
667 Relocation.x86_64(data_index, 4, neg_symbol, .@"64", 0),
668 },
669 });
670 defer allocator.free(caller_object);
671
672 const absolute_object = try buildObject(allocator, .{
673 .sections = &.{},
674 .symbols = &.{Symbol.absoluteObject("negative_absolute", std.math.maxInt(u64), 0)},
675 });
676 defer allocator.free(absolute_object);
677
678 var linked = try linkExecutable(
679 allocator,
680 &.{
681 .{ .name = "caller.o", .bytes = caller_object },
682 .{ .name = "absolute.o", .bytes = absolute_object },
683 },
684 .{ .incremental_mode = .off },
685 );
686 defer linked.deinit(allocator);
687
688 const data_section = try metadata.linkedSectionByName(linked.bytes, ".data");
689 const offset: usize = @intCast(data_section.offset);
690 try std.testing.expectEqual(@as(i32, -1), std.mem.readInt(i32, linked.bytes[offset..][0..4], .little));
691 try std.testing.expectEqual(std.math.maxInt(u64), std.mem.readInt(u64, linked.bytes[offset + 4 ..][0..8], .little));
692 }
693
694 test "ELF linker resolves narrow absolute relocations" {
695 const allocator = std.testing.allocator;
696
697 const text = [_]u8{0xc3};
698 const data = [_]u8{ 0, 0, 0, 0 };
699 const text_index: u16 = 1;
700 const data_index: u16 = 2;
701 const abs16_symbol: u32 = 4;
702 const abs8_symbol: u32 = 5;
703 const caller_object = try buildObject(allocator, .{
704 .sections = &.{
705 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
706 Section.progbits(".data.narrow", &data, std.elf.SHF_WRITE, 2),
707 },
708 .symbols = &.{
709 Symbol.section(text_index),
710 Symbol.section(data_index),
711 Symbol.function("_start", text_index, 0, text.len),
712 Symbol.undefinedObject("abs16"),
713 Symbol.undefinedObject("abs8"),
714 },
715 .relocations = &.{
716 Relocation.x86_64(data_index, 0, abs16_symbol, .@"16", 0),
717 Relocation.x86_64(data_index, 2, abs8_symbol, .@"8", 0),
718 Relocation.x86_64(data_index, 3, abs8_symbol, .@"8", -91),
719 },
720 });
721 defer allocator.free(caller_object);
722
723 const absolute_object = try buildObject(allocator, .{
724 .sections = &.{},
725 .symbols = &.{ Symbol.absoluteObject("abs16", 0x1234, 0), Symbol.absoluteObject("abs8", 0x5a, 0) },
726 });
727 defer allocator.free(absolute_object);
728
729 var linked = try linkExecutable(
730 allocator,
731 &.{
732 .{ .name = "caller.o", .bytes = caller_object },
733 .{ .name = "absolute.o", .bytes = absolute_object },
734 },
735 .{ .incremental_mode = .off },
736 );
737 defer linked.deinit(allocator);
738
739 const data_section = try metadata.linkedSectionByName(linked.bytes, ".data");
740 const offset: usize = @intCast(data_section.offset);
741 try std.testing.expectEqual(@as(u16, 0x1234), std.mem.readInt(u16, linked.bytes[offset..][0..2], .little));
742 try std.testing.expectEqual(@as(u8, 0x5a), linked.bytes[offset + 2]);
743 try std.testing.expectEqual(@as(u8, 0xff), linked.bytes[offset + 3]);
744 }
745
746 test "ELF linker resolves narrow PC-relative relocations" {
747 const allocator = std.testing.allocator;
748
749 const text = [_]u8{0xc3};
750 const data = [_]u8{ 0, 0, 0, 0 };
751 const text_index: u16 = 1;
752 const data_index: u16 = 2;
753 const target_symbol: u32 = 4;
754 const object = try buildObject(allocator, .{
755 .sections = &.{
756 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
757 Section.progbits(".data.pc", &data, std.elf.SHF_WRITE, 2),
758 },
759 .symbols = &.{
760 Symbol.section(text_index),
761 Symbol.section(data_index),
762 Symbol.function("_start", text_index, 0, text.len),
763 Symbol.object("pc_target", data_index, 3, 1),
764 },
765 .relocations = &.{
766 Relocation.x86_64(data_index, 0, target_symbol, .PC16, 0),
767 Relocation.x86_64(data_index, 2, target_symbol, .PC8, 0),
768 },
769 });
770 defer allocator.free(object);
771
772 var linked = try linkExecutable(allocator, &.{.{ .name = "pc.o", .bytes = object }}, .{ .incremental_mode = .off });
773 defer linked.deinit(allocator);
774
775 const data_record = metadata.manifestSection(linked.manifest, ".data") orelse return error.MissingSection;
776 const offset: usize = @intCast(data_record.file_offset);
777 try std.testing.expectEqual(@as(i16, 3), std.mem.readInt(i16, linked.bytes[offset..][0..2], .little));
778 try std.testing.expectEqual(@as(u8, 1), linked.bytes[offset + 2]);
779 try std.testing.expectEqual(@as(u8, 0), linked.bytes[offset + 3]);
780 }
781
782 test "ELF linker discards duplicate COMDAT groups by signature" {
783 const allocator = std.testing.allocator;
784
785 const retained_text = [_]u8{0xc3};
786 const retained_text_index: u16 = 1;
787 const retained_symbol: u32 = 2;
788 var retained_group_payload: [(1 + 1) * 4]u8 = undefined;
789 const retained_group_bytes = groupBytes(&retained_group_payload, &.{retained_text_index});
790 const retained_object = try buildObject(allocator, .{
791 .sections = &.{
792 Section.progbits(".text.inline", &retained_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
793 Section.group(".group", retained_group_bytes, retained_symbol),
794 },
795 .symbols = &.{
796 Symbol.section(retained_text_index),
797 Symbol.function("inline_fn", retained_text_index, 0, retained_text.len),
798 },
799 });
800 defer allocator.free(retained_object);
801
802 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
803 const caller_text_index: u16 = 1;
804 const duplicate_text = [_]u8{0xcc};
805 const duplicate_text_index: u16 = 2;
806 const duplicate_symbol: u32 = 4;
807 var caller_group_payload: [(1 + 1) * 4]u8 = undefined;
808 const caller_group_bytes = groupBytes(&caller_group_payload, &.{duplicate_text_index});
809 const caller_object = try buildObject(allocator, .{
810 .sections = &.{
811 Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16),
812 Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
813 Section.group(".group", caller_group_bytes, duplicate_symbol),
814 },
815 .symbols = &.{
816 Symbol.section(caller_text_index),
817 Symbol.section(duplicate_text_index),
818 Symbol.function("_start", caller_text_index, 0, caller_text.len),
819 Symbol.function("inline_fn", duplicate_text_index, 0, duplicate_text.len),
820 },
821 .relocations = &.{Relocation.x86_64(1, 1, duplicate_symbol, .PLT32, -4)},
822 });
823 defer allocator.free(caller_object);
824
825 const inputs = [_]model.Input{
826 .{ .name = "retained.o", .bytes = retained_object },
827 .{ .name = "caller.o", .bytes = caller_object },
828 };
829
830 var linked = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
831 defer linked.deinit(allocator);
832
833 var manifest_linked = try linkExecutable(allocator, &inputs, .{});
834 defer manifest_linked.deinit(allocator);
835
836 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
837 try std.testing.expectEqual(@as(u64, 22), text_record.size);
838 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retained.o", .section, ".text.inline", retained_text_index) != null);
839 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "caller.o", .section, ".text.inline", duplicate_text_index) == null);
840 const discarded_duplicate = metadata.manifestDiscardedContribution(manifest_linked.manifest, "caller.o", ".text.inline", duplicate_text_index) orelse return error.MissingSection;
841 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_duplicate.reason);
842
843 const text_file_offset: usize = @intCast(text_record.file_offset);
844 try std.testing.expectEqual(@as(u8, 0xc3), linked.bytes[text_file_offset]);
845 try std.testing.expectEqual(@as(i32, -21), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
846 }
847
848 test "ELF linker uses section names for unnamed COMDAT section signatures" {
849 const allocator = std.testing.allocator;
850
851 const start_text = [_]u8{0xc3};
852 const start_text_index: u16 = 1;
853 const start_object = try buildObject(allocator, .{
854 .sections = &.{Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16)},
855 .symbols = &.{
856 Symbol.section(start_text_index),
857 Symbol.function("_start", start_text_index, 0, start_text.len),
858 },
859 });
860 defer allocator.free(start_object);
861
862 const first_text = [_]u8{0x90};
863 const first_text_index: u16 = 1;
864 const first_section_symbol: u32 = 1;
865 var first_group_payload: [(1 + 1) * 4]u8 = undefined;
866 const first_group_bytes = groupBytes(&first_group_payload, &.{first_text_index});
867 const first_object = try buildObject(allocator, .{
868 .sections = &.{
869 Section.progbits(".text.inline", &first_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
870 Section.group(".group", first_group_bytes, first_section_symbol),
871 },
872 .symbols = &.{Symbol.section(first_text_index)},
873 });
874 defer allocator.free(first_object);
875
876 const duplicate_text = [_]u8{0xcc};
877 const duplicate_text_index: u16 = 1;
878 const duplicate_section_symbol: u32 = 1;
879 var duplicate_group_payload: [(1 + 1) * 4]u8 = undefined;
880 const duplicate_group_bytes = groupBytes(&duplicate_group_payload, &.{duplicate_text_index});
881 const duplicate_object = try buildObject(allocator, .{
882 .sections = &.{
883 Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
884 Section.group(".group", duplicate_group_bytes, duplicate_section_symbol),
885 },
886 .symbols = &.{Symbol.section(duplicate_text_index)},
887 });
888 defer allocator.free(duplicate_object);
889
890 var linked = try linkExecutable(allocator, &.{
891 .{ .name = "start.o", .bytes = start_object },
892 .{ .name = "first.o", .bytes = first_object },
893 .{ .name = "duplicate.o", .bytes = duplicate_object },
894 }, .{});
895 defer linked.deinit(allocator);
896
897 try std.testing.expect(metadata.manifestContribution(linked.manifest, "first.o", .section, ".text.inline", first_text_index) != null);
898 try std.testing.expect(metadata.manifestContribution(linked.manifest, "duplicate.o", .section, ".text.inline", duplicate_text_index) == null);
899 const discarded_duplicate = metadata.manifestDiscardedContribution(linked.manifest, "duplicate.o", ".text.inline", duplicate_text_index) orelse return error.MissingSection;
900 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_duplicate.reason);
901 }
902
903 test "ELF linker garbage collects unreferenced alloc sections" {
904 const allocator = std.testing.allocator;
905
906 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
907 const helper_text = [_]u8{0xc3};
908 const dead_text = [_]u8{0xcc};
909 const start_index: u16 = 1;
910 const helper_index: u16 = 2;
911 const dead_index: u16 = 3;
912 const helper_symbol: u32 = 5;
913 const object = try buildObject(allocator, .{
914 .sections = &.{
915 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
916 Section.progbits(".text.helper", &helper_text, std.elf.SHF_EXECINSTR, 16),
917 Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
918 },
919 .symbols = &.{
920 Symbol.section(start_index),
921 Symbol.section(helper_index),
922 Symbol.section(dead_index),
923 Symbol.function("_start", start_index, 0, start_text.len),
924 Symbol.function("helper", helper_index, 0, helper_text.len),
925 Symbol.function("dead", dead_index, 0, dead_text.len),
926 },
927 .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .PLT32, -4)},
928 });
929 defer allocator.free(object);
930
931 const inputs = [_]model.Input{.{ .name = "gc.o", .bytes = object }};
932 var linked = try linkExecutable(allocator, &inputs, .{
933 .incremental_mode = .off,
934 .gc_sections = true,
935 });
936 defer linked.deinit(allocator);
937
938 var manifest_linked = try linkExecutable(allocator, &inputs, .{
939 .gc_sections = true,
940 });
941 defer manifest_linked.deinit(allocator);
942
943 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
944 try std.testing.expectEqual(@as(u64, 17), text_record.size);
945 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.start", start_index) != null);
946 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.helper", helper_index) != null);
947 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "gc.o", .section, ".text.dead", dead_index) == null);
948 const discarded_dead = metadata.manifestDiscardedContribution(manifest_linked.manifest, "gc.o", ".text.dead", dead_index) orelse return error.MissingSection;
949 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
950
951 const text_file_offset: usize = @intCast(text_record.file_offset);
952 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
953 }
954
955 test "ELF linker keeps GNU retain sections during GC" {
956 const allocator = std.testing.allocator;
957
958 const start_text = [_]u8{0xc3};
959 const retained_data = [_]u8{ 0x6d, 0x25, 0xe3, 0x91, 0x4a, 0xbf, 0x70, 0x0d };
960 const dead_data = [_]u8{ 0xd0, 0x5c, 0xaa, 0x11 };
961 const start_index: u16 = 1;
962 const retained_index: u16 = 2;
963 const dead_index: u16 = 3;
964 const object = try buildObject(allocator, .{
965 .sections = &.{
966 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
967 Section.progbits(".rodata.retain", &retained_data, std.elf.SHF_GNU_RETAIN, 8),
968 Section.progbits(".rodata.dead", &dead_data, 0, 4),
969 },
970 .symbols = &.{
971 Symbol.section(start_index),
972 Symbol.section(retained_index),
973 Symbol.section(dead_index),
974 Symbol.function("_start", start_index, 0, start_text.len),
975 Symbol.object("retained_probe", retained_index, 0, retained_data.len),
976 Symbol.object("dead_probe", dead_index, 0, dead_data.len),
977 },
978 });
979 defer allocator.free(object);
980
981 const inputs = [_]model.Input{.{ .name = "retain.o", .bytes = object }};
982 var linked = try linkExecutable(allocator, &inputs, .{
983 .incremental_mode = .off,
984 .gc_sections = true,
985 });
986 defer linked.deinit(allocator);
987
988 var manifest_linked = try linkExecutable(allocator, &inputs, .{
989 .gc_sections = true,
990 });
991 defer manifest_linked.deinit(allocator);
992
993 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".text.start", start_index) != null);
994 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".rodata.retain", retained_index) != null);
995 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "retain.o", .section, ".rodata.dead", dead_index) == null);
996 const discarded_dead = metadata.manifestDiscardedContribution(manifest_linked.manifest, "retain.o", ".rodata.dead", dead_index) orelse return error.MissingSection;
997 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
998 }
999
1000 test "ELF linker keeps init arrays during GC" {
1001 const allocator = std.testing.allocator;
1002
1003 const start_text = [_]u8{0xc3};
1004 const ctor_text = [_]u8{ 0xb8, 0x5d, 0x00, 0x00, 0x00, 0xc3 };
1005 const init_array = @as([8]u8, @splat(0));
1006 const dead_text = [_]u8{0xcc};
1007 const start_index: u16 = 1;
1008 const ctor_index: u16 = 2;
1009 const init_index: u16 = 3;
1010 const dead_index: u16 = 4;
1011 const ctor_symbol: u32 = 6;
1012 const object = try buildObject(allocator, .{
1013 .sections = &.{
1014 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1015 Section.progbits(".text.ctor", &ctor_text, std.elf.SHF_EXECINSTR, 16),
1016 .{
1017 .name = ".init_array",
1018 .section_type = std.elf.SHT_INIT_ARRAY,
1019 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1020 .alignment = 8,
1021 .bytes = &init_array,
1022 },
1023 Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
1024 },
1025 .symbols = &.{
1026 Symbol.section(start_index),
1027 Symbol.section(ctor_index),
1028 Symbol.section(init_index),
1029 Symbol.section(dead_index),
1030 Symbol.function("_start", start_index, 0, start_text.len),
1031 Symbol.function("ctor", ctor_index, 0, ctor_text.len),
1032 Symbol.function("dead", dead_index, 0, dead_text.len),
1033 },
1034 .relocations = &.{Relocation.x86_64(init_index, 0, ctor_symbol, .@"64", 0)},
1035 });
1036 defer allocator.free(object);
1037
1038 var linked = try linkExecutable(allocator, &.{.{ .name = "init.o", .bytes = object }}, .{
1039 .gc_sections = true,
1040 });
1041 defer linked.deinit(allocator);
1042
1043 const init_contribution = metadata.manifestContribution(linked.manifest, "init.o", .section, ".init_array", init_index) orelse return error.MissingSection;
1044 const ctor_contribution = metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.ctor", ctor_index) orelse return error.MissingSection;
1045 try std.testing.expect(metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.start", start_index) != null);
1046 try std.testing.expect(metadata.manifestContribution(linked.manifest, "init.o", .section, ".text.dead", dead_index) == null);
1047 const discarded_dead = metadata.manifestDiscardedContribution(linked.manifest, "init.o", ".text.dead", dead_index) orelse return error.MissingSection;
1048 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
1049
1050 const init_file_offset: usize = @intCast(init_contribution.file_offset);
1051 try std.testing.expectEqual(ctor_contribution.address, std.mem.readInt(u64, linked.bytes[init_file_offset..][0..8], .little));
1052 }
1053
1054 test "ELF linker keeps allocated note sections during GC" {
1055 const allocator = std.testing.allocator;
1056
1057 const start_text = [_]u8{0xc3};
1058 const note_payload = [_]u8{
1059 4, 0, 0, 0,
1060 4, 0, 0, 0,
1061 0x52, 0x44, 0x4c, 0x54,
1062 'T', 'L', 'D', 'R',
1063 0x79, 0x41, 0x6e, 0x23,
1064 };
1065 const dead_text = [_]u8{0xcc};
1066 const start_index: u16 = 1;
1067 const note_index: u16 = 2;
1068 const dead_index: u16 = 3;
1069 const object = try buildObject(allocator, .{
1070 .sections = &.{
1071 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1072 .{
1073 .name = ".note.tldr",
1074 .section_type = std.elf.SHT_NOTE,
1075 .flags = std.elf.SHF_ALLOC,
1076 .alignment = 4,
1077 .bytes = ¬e_payload,
1078 },
1079 Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
1080 },
1081 .symbols = &.{
1082 Symbol.section(start_index),
1083 Symbol.section(note_index),
1084 Symbol.section(dead_index),
1085 Symbol.function("_start", start_index, 0, start_text.len),
1086 Symbol.function("dead", dead_index, 0, dead_text.len),
1087 },
1088 });
1089 defer allocator.free(object);
1090
1091 var linked = try linkExecutable(allocator, &.{.{ .name = "note.o", .bytes = object }}, .{
1092 .gc_sections = true,
1093 });
1094 defer linked.deinit(allocator);
1095
1096 const note_contribution = metadata.manifestContribution(linked.manifest, "note.o", .section, ".note.tldr", note_index) orelse return error.MissingSection;
1097 try std.testing.expect(metadata.manifestContribution(linked.manifest, "note.o", .section, ".text.start", start_index) != null);
1098 try std.testing.expect(metadata.manifestContribution(linked.manifest, "note.o", .section, ".text.dead", dead_index) == null);
1099 const discarded_dead = metadata.manifestDiscardedContribution(linked.manifest, "note.o", ".text.dead", dead_index) orelse return error.MissingSection;
1100 try std.testing.expectEqual(incremental.DiscardReason.discarded, discarded_dead.reason);
1101
1102 const note_file_offset: usize = @intCast(note_contribution.file_offset);
1103 try std.testing.expectEqualSlices(u8, ¬e_payload, linked.bytes[note_file_offset..][0..note_payload.len]);
1104 }
1105
1106 test "ELF linker ignores no-op relocations during GC" {
1107 const allocator = std.testing.allocator;
1108
1109 const text = [_]u8{0xc3};
1110 const text_index: u16 = 1;
1111 const object = try buildObject(allocator, .{
1112 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1113 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1114 .relocations = &.{Relocation.x86_64(text_index, 0, 0, .NONE, 0)},
1115 });
1116 defer allocator.free(object);
1117
1118 var linked = try linkExecutable(allocator, &.{.{ .name = "none-gc.o", .bytes = object }}, .{
1119 .incremental_mode = .off,
1120 .gc_sections = true,
1121 });
1122 defer linked.deinit(allocator);
1123
1124 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1125 try std.testing.expectEqual(@as(u64, text.len), text_record.size);
1126 }
1127
1128 test "ELF linker resolves start and stop section boundary symbols" {
1129 const allocator = std.testing.allocator;
1130
1131 const text = [_]u8{
1132 0x48, 0xb8,
1133 0, 0,
1134 0, 0,
1135 0, 0,
1136 0, 0,
1137 0x48, 0xbb,
1138 0, 0,
1139 0, 0,
1140 0, 0,
1141 0, 0,
1142 0xc3,
1143 };
1144 const caller_text_index: u16 = 1;
1145 const start_symbol: u32 = 3;
1146 const stop_symbol: u32 = 4;
1147 const caller_object = try buildObject(allocator, .{
1148 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1149 .symbols = &.{
1150 Symbol.section(caller_text_index),
1151 Symbol.function("_start", caller_text_index, 0, text.len),
1152 Symbol.undefinedObject("__start_meta"),
1153 Symbol.undefinedObject("__stop_meta"),
1154 },
1155 .relocations = &.{
1156 Relocation.x86_64(caller_text_index, 2, start_symbol, .@"64", 0),
1157 Relocation.x86_64(caller_text_index, 12, stop_symbol, .@"64", 0),
1158 },
1159 });
1160 defer allocator.free(caller_object);
1161
1162 const meta = [_]u8{ 0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55 };
1163 const meta_object = try buildObject(allocator, .{
1164 .sections = &.{Section.progbits("meta", &meta, 0, 1)},
1165 .symbols = &.{Symbol.section(1)},
1166 });
1167 defer allocator.free(meta_object);
1168
1169 var linked = try linkExecutable(
1170 allocator,
1171 &.{
1172 .{ .name = "caller.o", .bytes = caller_object },
1173 .{ .name = "meta.o", .bytes = meta_object },
1174 },
1175 .{
1176 .incremental_mode = .off,
1177 .gc_sections = true,
1178 },
1179 );
1180 defer linked.deinit(allocator);
1181
1182 const text_section = try metadata.linkedSectionByName(linked.bytes, ".text");
1183 const rodata_section = try metadata.linkedSectionByName(linked.bytes, ".rodata");
1184 try std.testing.expectEqual(@as(u64, meta.len), rodata_section.size);
1185 try std.testing.expectEqual(rodata_section.address, readU64(linked.bytes, @intCast(text_section.offset + 2)));
1186 try std.testing.expectEqual(rodata_section.address + meta.len, readU64(linked.bytes, @intCast(text_section.offset + 12)));
1187 }
1188
1189 test "ELF linker resolves runtime array boundary symbols" {
1190 const allocator = std.testing.allocator;
1191
1192 const text = [_]u8{
1193 0x48, 0xb8,
1194 0, 0,
1195 0, 0,
1196 0, 0,
1197 0, 0,
1198 0x48, 0xbb,
1199 0, 0,
1200 0, 0,
1201 0, 0,
1202 0, 0,
1203 0xc3,
1204 };
1205 const caller_text_index: u16 = 1;
1206 const start_symbol: u32 = 3;
1207 const end_symbol: u32 = 4;
1208 const caller_object = try buildObject(allocator, .{
1209 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1210 .symbols = &.{
1211 Symbol.section(caller_text_index),
1212 Symbol.function("_start", caller_text_index, 0, text.len),
1213 Symbol.undefinedObject("__init_array_start"),
1214 Symbol.undefinedObject("__init_array_end"),
1215 },
1216 .relocations = &.{
1217 Relocation.x86_64(caller_text_index, 2, start_symbol, .@"64", 0),
1218 Relocation.x86_64(caller_text_index, 12, end_symbol, .@"64", 0),
1219 },
1220 });
1221 defer allocator.free(caller_object);
1222
1223 const first = @as([8]u8, @splat(0x11));
1224 const second = @as([8]u8, @splat(0x22));
1225 const first_index: u16 = 1;
1226 const second_index: u16 = 2;
1227 const arrays_object = try buildObject(allocator, .{
1228 .sections = &.{
1229 .{
1230 .name = ".init_array",
1231 .section_type = std.elf.SHT_INIT_ARRAY,
1232 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1233 .alignment = 8,
1234 .bytes = &first,
1235 },
1236 .{
1237 .name = ".init_array.100",
1238 .section_type = std.elf.SHT_INIT_ARRAY,
1239 .flags = std.elf.SHF_WRITE | std.elf.SHF_ALLOC,
1240 .alignment = 8,
1241 .bytes = &second,
1242 },
1243 },
1244 .symbols = &.{ Symbol.section(first_index), Symbol.section(second_index) },
1245 });
1246 defer allocator.free(arrays_object);
1247
1248 var linked = try linkExecutable(
1249 allocator,
1250 &.{
1251 .{ .name = "caller.o", .bytes = caller_object },
1252 .{ .name = "arrays.o", .bytes = arrays_object },
1253 },
1254 .{ .gc_sections = true },
1255 );
1256 defer linked.deinit(allocator);
1257
1258 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1259 const first_contribution = metadata.manifestContribution(linked.manifest, "arrays.o", .section, ".init_array", first_index) orelse return error.MissingSection;
1260 const second_contribution = metadata.manifestContribution(linked.manifest, "arrays.o", .section, ".init_array.100", second_index) orelse return error.MissingSection;
1261 const expected_start = @min(first_contribution.address, second_contribution.address);
1262 const expected_end = @max(first_contribution.address + first_contribution.size, second_contribution.address + second_contribution.size);
1263 const text_offset: usize = @intCast(text_record.file_offset);
1264 try std.testing.expectEqual(expected_start, readU64(linked.bytes, text_offset + 2));
1265 try std.testing.expectEqual(expected_end, readU64(linked.bytes, text_offset + 12));
1266 }
1267
1268 test "ELF linker resolves absent runtime array boundaries to equal addresses" {
1269 const allocator = std.testing.allocator;
1270
1271 const text = [_]u8{
1272 0x48, 0xb8,
1273 0, 0,
1274 0, 0,
1275 0, 0,
1276 0, 0,
1277 0x48, 0xbb,
1278 0, 0,
1279 0, 0,
1280 0, 0,
1281 0, 0,
1282 0xc3,
1283 };
1284 const text_index: u16 = 1;
1285 const start_symbol: u32 = 3;
1286 const end_symbol: u32 = 4;
1287 const object = try buildObject(allocator, .{
1288 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1289 .symbols = &.{
1290 Symbol.section(text_index),
1291 Symbol.function("_start", text_index, 0, text.len),
1292 Symbol.undefinedObject("__preinit_array_start"),
1293 Symbol.undefinedObject("__preinit_array_end"),
1294 },
1295 .relocations = &.{
1296 Relocation.x86_64(text_index, 2, start_symbol, .@"64", 0),
1297 Relocation.x86_64(text_index, 12, end_symbol, .@"64", 0),
1298 },
1299 });
1300 defer allocator.free(object);
1301
1302 var linked = try linkExecutable(allocator, &.{.{ .name = "preinit.o", .bytes = object }}, .{});
1303 defer linked.deinit(allocator);
1304
1305 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1306 const text_offset: usize = @intCast(text_record.file_offset);
1307 try std.testing.expectEqual(readU64(linked.bytes, text_offset + 2), readU64(linked.bytes, text_offset + 12));
1308 }
1309
1310 test "ELF linker resolves ELF header start symbol" {
1311 const allocator = std.testing.allocator;
1312
1313 const text = [_]u8{
1314 0x48, 0xb8,
1315 0, 0,
1316 0, 0,
1317 0, 0,
1318 0, 0,
1319 0xc3,
1320 };
1321 const text_index: u16 = 1;
1322 const ehdr_start: u32 = 3;
1323 const object = try buildObject(allocator, .{
1324 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1325 .symbols = &.{
1326 Symbol.section(text_index),
1327 Symbol.function("_start", text_index, 0, text.len),
1328 Symbol.undefinedObject("__ehdr_start"),
1329 },
1330 .relocations = &.{Relocation.x86_64(text_index, 2, ehdr_start, .@"64", 0)},
1331 });
1332 defer allocator.free(object);
1333
1334 var linked = try linkExecutable(allocator, &.{.{ .name = "ehdr.o", .bytes = object }}, .{});
1335 defer linked.deinit(allocator);
1336
1337 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1338 const text_offset: usize = @intCast(text_record.file_offset);
1339 try std.testing.expectEqual(@as(u64, 0x400000), readU64(linked.bytes, text_offset + 2));
1340 }
1341
1342 test "ELF linker records undefined relocation symbol diagnostics" {
1343 const allocator = std.testing.allocator;
1344
1345 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1346 const start_index: u16 = 1;
1347 const missing_symbol: u32 = 3;
1348 const second_missing_symbol: u32 = 4;
1349 const object = try buildObject(allocator, .{
1350 .sections = &.{Section.progbits(".text", &start_text, std.elf.SHF_EXECINSTR, 16)},
1351 .symbols = &.{
1352 Symbol.section(start_index),
1353 Symbol.function("_start", start_index, 0, start_text.len),
1354 Symbol.undefinedFunction("missing_runtime_symbol"),
1355 Symbol.undefinedFunction("second_missing_runtime_symbol"),
1356 },
1357 .relocations = &.{
1358 Relocation.x86_64(1, 1, missing_symbol, .PLT32, -4),
1359 Relocation.x86_64(1, 6, second_missing_symbol, .PLT32, -4),
1360 },
1361 });
1362 defer allocator.free(object);
1363
1364 var diagnostics: model.Diagnostics = .{};
1365 try std.testing.expectError(error.UndefinedSymbol, linkExecutable(
1366 allocator,
1367 &.{.{ .name = "missing.o", .bytes = object }},
1368 .{
1369 .incremental_mode = .off,
1370 .diagnostics = &diagnostics,
1371 },
1372 ));
1373
1374 const undefined_symbol = diagnostics.first_undefined_symbol orelse return error.ExpectedUndefinedSymbolDiagnostic;
1375 try std.testing.expectEqualStrings("missing_runtime_symbol", undefined_symbol.name);
1376 try std.testing.expectEqualStrings("missing.o", undefined_symbol.input_name);
1377 const undefined_symbols = diagnostics.recordedUndefinedSymbols();
1378 try std.testing.expectEqual(@as(usize, 2), undefined_symbols.len);
1379 try std.testing.expectEqualStrings("missing_runtime_symbol", undefined_symbols[0].name);
1380 try std.testing.expectEqualStrings("second_missing_runtime_symbol", undefined_symbols[1].name);
1381 }
1382
1383 test "ELF linker records unsupported relocation diagnostics" {
1384 const allocator = std.testing.allocator;
1385
1386 const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1387 const text_index: u16 = 1;
1388 const helper_symbol: u32 = 3;
1389 const object = try buildObject(allocator, .{
1390 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1391 .symbols = &.{
1392 Symbol.section(text_index),
1393 Symbol.function("_start", text_index, 0, text.len),
1394 Symbol.function("helper", text_index, 0, text.len),
1395 },
1396 .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .GOT64, -4)},
1397 });
1398 defer allocator.free(object);
1399
1400 var diagnostics: model.Diagnostics = .{};
1401 try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
1402 allocator,
1403 &.{.{ .name = "unsupported.o", .bytes = object }},
1404 .{
1405 .incremental_mode = .off,
1406 .diagnostics = &diagnostics,
1407 },
1408 ));
1409
1410 const unsupported = diagnostics.first_unsupported_relocation orelse return error.ExpectedUnsupportedRelocationDiagnostic;
1411 try std.testing.expectEqualStrings("unsupported.o", unsupported.input_name);
1412 try std.testing.expectEqualStrings(".text", unsupported.section_name);
1413 try std.testing.expectEqualStrings("helper", unsupported.symbol_name);
1414 try std.testing.expectEqual(@backingInt(std.elf.R_X86_64.GOT64), unsupported.relocation_type);
1415 }
1416
1417 const CommitWitness = struct {
1418 count: usize = 0,
1419
1420 fn observer(self: *CommitWitness) model.LinkCommitObserver {
1421 return .{ .context = self, .committed = committed };
1422 }
1423
1424 fn committed(context: *anyopaque) void {
1425 const self: *CommitWitness = @ptrCast(@alignCast(context));
1426 self.count += 1;
1427 }
1428 };
1429
1430 fn startObjectAlloc(allocator: Allocator) ![]u8 {
1431 const text = [_]u8{
1432 0xb8, 0x3c, 0x00, 0x00, 0x00,
1433 0x31, 0xff, 0x0f, 0x05,
1434 };
1435 const text_index: u16 = 1;
1436 return try buildObject(allocator, .{
1437 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1438 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1439 });
1440 }
1441
1442 test "ELF linker notifies the commit observer once on success" {
1443 const allocator = std.testing.allocator;
1444 const object = try startObjectAlloc(allocator);
1445 defer allocator.free(object);
1446
1447 var witness: CommitWitness = .{};
1448 var linked = try linkExecutable(
1449 allocator,
1450 &.{.{ .name = "start.o", .bytes = object }},
1451 .{ .commit_observer = witness.observer() },
1452 );
1453 defer linked.deinit(allocator);
1454
1455 try std.testing.expectEqual(@as(usize, 1), witness.count);
1456 }
1457
1458 test "ELF linker withholds commit until resolution failures are impossible" {
1459 const allocator = std.testing.allocator;
1460
1461 const call = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1462 const undefined_text: u16 = 1;
1463 const missing_symbol: u32 = 3;
1464 const undefined_object = try buildObject(allocator, .{
1465 .sections = &.{Section.progbits(".text", &call, std.elf.SHF_EXECINSTR, 16)},
1466 .symbols = &.{
1467 Symbol.section(undefined_text),
1468 Symbol.function("_start", undefined_text, 0, call.len),
1469 Symbol.undefinedFunction("missing"),
1470 },
1471 .relocations = &.{Relocation.x86_64(1, 1, missing_symbol, .PLT32, -4)},
1472 });
1473 defer allocator.free(undefined_object);
1474
1475 var undefined_witness: CommitWitness = .{};
1476 try std.testing.expectError(error.UndefinedSymbol, linkExecutable(
1477 allocator,
1478 &.{.{ .name = "undefined.o", .bytes = undefined_object }},
1479 .{ .commit_observer = undefined_witness.observer() },
1480 ));
1481 try std.testing.expectEqual(@as(usize, 0), undefined_witness.count);
1482
1483 const unsupported_text: u16 = 1;
1484 const helper_symbol: u32 = 3;
1485 const unsupported_object = try buildObject(allocator, .{
1486 .sections = &.{Section.progbits(".text", &call, std.elf.SHF_EXECINSTR, 16)},
1487 .symbols = &.{
1488 Symbol.section(unsupported_text),
1489 Symbol.function("_start", unsupported_text, 0, call.len),
1490 Symbol.function("helper", unsupported_text, 0, call.len),
1491 },
1492 .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .GOT64, -4)},
1493 });
1494 defer allocator.free(unsupported_object);
1495
1496 var unsupported_witness: CommitWitness = .{};
1497 try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
1498 allocator,
1499 &.{.{ .name = "unsupported.o", .bytes = unsupported_object }},
1500 .{ .commit_observer = unsupported_witness.observer() },
1501 ));
1502 try std.testing.expectEqual(@as(usize, 0), unsupported_witness.count);
1503 }
1504
1505 test "ELF linker fails missing entry symbols before commit" {
1506 const allocator = std.testing.allocator;
1507 const object = try startObjectAlloc(allocator);
1508 defer allocator.free(object);
1509
1510 var witness: CommitWitness = .{};
1511 try std.testing.expectError(error.MissingEntrySymbol, linkExecutable(
1512 allocator,
1513 &.{.{ .name = "start.o", .bytes = object }},
1514 .{
1515 .entry_symbol = "absent_entry",
1516 .commit_observer = witness.observer(),
1517 },
1518 ));
1519 try std.testing.expectEqual(@as(usize, 0), witness.count);
1520 }
1521
1522 test "ELF linker applies no-op pc64 and size relocations" {
1523 const allocator = std.testing.allocator;
1524
1525 const text = [_]u8{0xc3};
1526 const payload_bytes = [_]u8{ 'p', 'a', 'y', 'l', 'o', 'a', 'd' };
1527 var data = @as([32]u8, @splat(0xaa));
1528 const text_index: u16 = 1;
1529 const payload_index: u16 = 2;
1530 const data_index: u16 = 3;
1531 const start_symbol: u32 = 4;
1532 const payload_symbol: u32 = 5;
1533 const object = try buildObject(allocator, .{
1534 .sections = &.{
1535 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1536 Section.progbits(".rodata.payload", &payload_bytes, 0, 1),
1537 Section.progbits(".data.relocs", &data, std.elf.SHF_WRITE, 8),
1538 },
1539 .symbols = &.{
1540 Symbol.section(text_index),
1541 Symbol.section(payload_index),
1542 Symbol.section(data_index),
1543 Symbol.function("_start", text_index, 0, text.len),
1544 Symbol.object("payload", payload_index, 0, payload_bytes.len),
1545 },
1546 .relocations = &.{
1547 Relocation.x86_64(data_index, 0, 0, .NONE, 0),
1548 Relocation.x86_64(data_index, 8, payload_symbol, .SIZE64, 5),
1549 Relocation.x86_64(data_index, 16, payload_symbol, .SIZE32, -1),
1550 Relocation.x86_64(data_index, 24, start_symbol, .PC64, 0),
1551 },
1552 });
1553 defer allocator.free(object);
1554
1555 var linked = try linkExecutable(allocator, &.{.{ .name = "standard-relocs.o", .bytes = object }}, .{ .incremental_mode = .off });
1556 defer linked.deinit(allocator);
1557
1558 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1559 const data_record = metadata.manifestSection(linked.manifest, ".data") orelse return error.MissingSection;
1560 const data_offset: usize = @intCast(data_record.file_offset);
1561 try std.testing.expectEqualSlices(u8, data[0..8], linked.bytes[data_offset..][0..8]);
1562 try std.testing.expectEqual(@as(u64, payload_bytes.len + 5), std.mem.readInt(u64, linked.bytes[data_offset + 8 ..][0..8], .little));
1563 try std.testing.expectEqual(@as(u32, payload_bytes.len - 1), std.mem.readInt(u32, linked.bytes[data_offset + 16 ..][0..4], .little));
1564
1565 const pc64 = std.mem.readInt(i64, linked.bytes[data_offset + 24 ..][0..8], .little);
1566 const expected_pc64: i64 = @intCast(@as(i128, text_record.address) - @as(i128, data_record.address + 24));
1567 try std.testing.expectEqual(expected_pc64, pc64);
1568 }
1569
1570 test "ELF linker applies relocations in mergeable constant sections" {
1571 const allocator = std.testing.allocator;
1572
1573 const text = [_]u8{0xc3};
1574 var constants = @as([16]u8, @splat(0));
1575 const text_index: u16 = 1;
1576 const constants_index: u16 = 2;
1577 const start_symbol: u32 = 3;
1578 const object = try buildObject(allocator, .{
1579 .sections = &.{
1580 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1581 Section.mergeable(".rodata.cst8", &constants, 0, 8, 8),
1582 },
1583 .symbols = &.{
1584 Symbol.section(text_index),
1585 Symbol.section(constants_index),
1586 Symbol.function("_start", text_index, 0, text.len),
1587 },
1588 .relocations = &.{Relocation.x86_64(constants_index, 0, start_symbol, .@"64", 0)},
1589 });
1590 defer allocator.free(object);
1591
1592 var linked = try linkExecutable(allocator, &.{.{ .name = "merge-reloc.o", .bytes = object }}, .{ .incremental_mode = .off });
1593 defer linked.deinit(allocator);
1594
1595 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1596 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
1597 const rodata_offset: usize = @intCast(rodata_record.file_offset);
1598 try std.testing.expectEqual(@as(u64, 16), rodata_record.size);
1599 try std.testing.expectEqual(text_record.address, std.mem.readInt(u64, linked.bytes[rodata_offset..][0..8], .little));
1600 try std.testing.expectEqual(@as(u64, 0), std.mem.readInt(u64, linked.bytes[rodata_offset + 8 ..][0..8], .little));
1601 }
1602
1603 test "ELF linker reports direct 32-bit relocation overflow" {
1604 const allocator = std.testing.allocator;
1605
1606 const text = [_]u8{0xc3};
1607 const data = @as([4]u8, @splat(0));
1608 const text_index: u16 = 1;
1609 const data_index: u16 = 2;
1610 const too_large_symbol: u32 = 4;
1611 const object = try buildObject(allocator, .{
1612 .sections = &.{
1613 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
1614 Section.progbits(".data", &data, std.elf.SHF_WRITE, 4),
1615 },
1616 .symbols = &.{
1617 Symbol.section(text_index),
1618 Symbol.section(data_index),
1619 Symbol.function("_start", text_index, 0, text.len),
1620 Symbol.absoluteObject("too_large", @as(u64, std.math.maxInt(u32)) + 1, 0),
1621 },
1622 .relocations = &.{Relocation.x86_64(data_index, 0, too_large_symbol, .@"32", 0)},
1623 });
1624 defer allocator.free(object);
1625
1626 try std.testing.expectError(error.RelocationOverflow, linkExecutable(
1627 allocator,
1628 &.{.{ .name = "overflow.o", .bytes = object }},
1629 .{ .incremental_mode = .off },
1630 ));
1631 }
1632
1633 test "ELF linker rejects relocations that extend past section bytes" {
1634 const allocator = std.testing.allocator;
1635
1636 const text = [_]u8{ 0xe8, 0, 0 };
1637 const text_index: u16 = 1;
1638 const start_symbol: u32 = 2;
1639 const object = try buildObject(allocator, .{
1640 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
1641 .symbols = &.{ Symbol.section(text_index), Symbol.function("_start", text_index, 0, text.len) },
1642 .relocations = &.{Relocation.x86_64(text_index, 1, start_symbol, .PC32, -4)},
1643 });
1644 defer allocator.free(object);
1645
1646 try std.testing.expectError(error.InvalidRange, linkExecutable(
1647 allocator,
1648 &.{.{ .name = "truncated-reloc.o", .bytes = object }},
1649 .{ .incremental_mode = .off },
1650 ));
1651 }
1652
1653 test "ELF linker follows relocation chains across sections during GC" {
1654 const allocator = std.testing.allocator;
1655
1656 const call_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1657 const leaf_text = [_]u8{0xc3};
1658 const start_index: u16 = 1;
1659 const first_index: u16 = 2;
1660 const second_index: u16 = 3;
1661 const first_symbol: u32 = 5;
1662 const second_symbol: u32 = 6;
1663 const object = try buildObject(allocator, .{
1664 .sections = &.{
1665 Section.progbits(".text.start", &call_text, std.elf.SHF_EXECINSTR, 16),
1666 Section.progbits(".text.first", &call_text, std.elf.SHF_EXECINSTR, 16),
1667 Section.progbits(".text.second", &leaf_text, std.elf.SHF_EXECINSTR, 16),
1668 },
1669 .symbols = &.{
1670 Symbol.section(start_index),
1671 Symbol.section(first_index),
1672 Symbol.section(second_index),
1673 Symbol.function("_start", start_index, 0, call_text.len),
1674 Symbol.function("first", first_index, 0, call_text.len),
1675 Symbol.function("second", second_index, 0, leaf_text.len),
1676 },
1677 .relocations = &.{
1678 Relocation.x86_64(start_index, 1, first_symbol, .PLT32, -4),
1679 Relocation.x86_64(first_index, 1, second_symbol, .PLT32, -4),
1680 },
1681 });
1682 defer allocator.free(object);
1683
1684 const inputs = [_]model.Input{.{ .name = "chain.o", .bytes = object }};
1685 var linked = try linkExecutable(allocator, &inputs, .{
1686 .incremental_mode = .off,
1687 .gc_sections = true,
1688 });
1689 defer linked.deinit(allocator);
1690
1691 var manifest_linked = try linkExecutable(allocator, &inputs, .{
1692 .gc_sections = true,
1693 });
1694 defer manifest_linked.deinit(allocator);
1695
1696 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1697 try std.testing.expectEqual(@as(u64, 33), text_record.size);
1698 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.start", start_index) != null);
1699 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.first", first_index) != null);
1700 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "chain.o", .section, ".text.second", second_index) != null);
1701
1702 const text_file_offset: usize = @intCast(text_record.file_offset);
1703 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
1704 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
1705 }
1706
1707 test "ELF linker keeps COMDAT group members live during garbage collection" {
1708 const allocator = std.testing.allocator;
1709
1710 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1711 const caller_text_index: u16 = 1;
1712 const grouped_symbol: u32 = 3;
1713 const caller_object = try buildObject(allocator, .{
1714 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
1715 .symbols = &.{
1716 Symbol.section(caller_text_index),
1717 Symbol.function("_start", caller_text_index, 0, caller_text.len),
1718 Symbol.undefinedFunction("grouped"),
1719 },
1720 .relocations = &.{Relocation.x86_64(1, 1, grouped_symbol, .PLT32, -4)},
1721 });
1722 defer allocator.free(caller_object);
1723
1724 const grouped_text = [_]u8{0xc3};
1725 const grouped_data = [_]u8{ 1, 2, 3, 4 };
1726 const grouped_text_index: u16 = 1;
1727 const grouped_data_index: u16 = 2;
1728 const grouped_definition: u32 = 3;
1729 var comdat_payload: [(2 + 1) * 4]u8 = undefined;
1730 const comdat_bytes = groupBytes(&comdat_payload, &.{ grouped_text_index, grouped_data_index });
1731 const group_object = try buildObject(allocator, .{
1732 .sections = &.{
1733 Section.progbits(".text.grouped", &grouped_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
1734 Section.progbits(".rodata.grouped", &grouped_data, std.elf.SHF_GROUP, 1),
1735 Section.group(".group", comdat_bytes, grouped_definition),
1736 },
1737 .symbols = &.{
1738 Symbol.section(grouped_text_index),
1739 Symbol.section(grouped_data_index),
1740 Symbol.function("grouped", grouped_text_index, 0, grouped_text.len),
1741 },
1742 });
1743 defer allocator.free(group_object);
1744
1745 var linked = try linkExecutable(allocator, &.{
1746 .{ .name = "caller.o", .bytes = caller_object },
1747 .{ .name = "group.o", .bytes = group_object },
1748 }, .{
1749 .gc_sections = true,
1750 });
1751 defer linked.deinit(allocator);
1752
1753 try std.testing.expect(metadata.manifestContribution(linked.manifest, "group.o", .section, ".text.grouped", grouped_text_index) != null);
1754 try std.testing.expect(metadata.manifestContribution(linked.manifest, "group.o", .section, ".rodata.grouped", grouped_data_index) != null);
1755 }
1756
1757 test "ELF linker folds identical code sections when ICF is enabled" {
1758 const allocator = std.testing.allocator;
1759
1760 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1761 const function_text = [_]u8{0xc3};
1762 const start_index: u16 = 1;
1763 const first_index: u16 = 2;
1764 const second_index: u16 = 3;
1765 const first_symbol: u32 = 5;
1766 const second_symbol: u32 = 6;
1767 const object = try buildObject(allocator, .{
1768 .sections = &.{
1769 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1770 Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1771 Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1772 },
1773 .symbols = &.{
1774 Symbol.section(start_index),
1775 Symbol.section(first_index),
1776 Symbol.section(second_index),
1777 Symbol.function("_start", start_index, 0, start_text.len),
1778 Symbol.function("first", first_index, 0, function_text.len),
1779 Symbol.function("second", second_index, 0, function_text.len),
1780 },
1781 .relocations = &.{
1782 Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1783 Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1784 },
1785 });
1786 defer allocator.free(object);
1787
1788 const inputs = [_]model.Input{.{ .name = "icf.o", .bytes = object }};
1789 var retained = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
1790 defer retained.deinit(allocator);
1791 var retained_manifest = try linkExecutable(allocator, &inputs, .{});
1792 defer retained_manifest.deinit(allocator);
1793 const retained_text = metadata.manifestSection(retained.manifest, ".text") orelse return error.MissingSection;
1794 try std.testing.expectEqual(@as(u64, 33), retained_text.size);
1795 try std.testing.expect(metadata.manifestContribution(retained_manifest.manifest, "icf.o", .section, ".text.first", first_index) != null);
1796 try std.testing.expect(metadata.manifestContribution(retained_manifest.manifest, "icf.o", .section, ".text.second", second_index) != null);
1797
1798 var folded = try linkExecutable(allocator, &inputs, .{
1799 .incremental_mode = .off,
1800 .icf = .all,
1801 });
1802 defer folded.deinit(allocator);
1803 var folded_manifest = try linkExecutable(allocator, &inputs, .{
1804 .icf = .all,
1805 });
1806 defer folded_manifest.deinit(allocator);
1807 const folded_text = metadata.manifestSection(folded.manifest, ".text") orelse return error.MissingSection;
1808 try std.testing.expectEqual(@as(u64, 17), folded_text.size);
1809 try std.testing.expect(metadata.manifestContribution(folded_manifest.manifest, "icf.o", .section, ".text.first", first_index) != null);
1810 try std.testing.expect(metadata.manifestContribution(folded_manifest.manifest, "icf.o", .section, ".text.second", second_index) == null);
1811 const folded_second = metadata.manifestDiscardedContribution(folded_manifest.manifest, "icf.o", ".text.second", second_index) orelse return error.MissingSection;
1812 try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1813
1814 const text_file_offset: usize = @intCast(folded_text.file_offset);
1815 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, folded.bytes[text_file_offset + 1 ..][0..4], .little));
1816 try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, folded.bytes[text_file_offset + 6 ..][0..4], .little));
1817 }
1818
1819 test "ELF ICF ignores no-op relocations" {
1820 const allocator = std.testing.allocator;
1821
1822 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1823 const function_text = [_]u8{0xc3};
1824 const start_index: u16 = 1;
1825 const first_index: u16 = 2;
1826 const second_index: u16 = 3;
1827 const first_symbol: u32 = 5;
1828 const second_symbol: u32 = 6;
1829 const object = try buildObject(allocator, .{
1830 .sections = &.{
1831 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1832 Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1833 Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1834 },
1835 .symbols = &.{
1836 Symbol.section(start_index),
1837 Symbol.section(first_index),
1838 Symbol.section(second_index),
1839 Symbol.function("_start", start_index, 0, start_text.len),
1840 Symbol.function("first", first_index, 0, function_text.len),
1841 Symbol.function("second", second_index, 0, function_text.len),
1842 },
1843 .relocations = &.{
1844 Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1845 Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1846 Relocation.x86_64(second_index, 0, 0, .NONE, 0),
1847 },
1848 });
1849 defer allocator.free(object);
1850
1851 const inputs = [_]model.Input{.{ .name = "icf-none.o", .bytes = object }};
1852 var linked = try linkExecutable(allocator, &inputs, .{
1853 .incremental_mode = .off,
1854 .icf = .all,
1855 });
1856 defer linked.deinit(allocator);
1857
1858 var manifest_linked = try linkExecutable(allocator, &inputs, .{
1859 .icf = .all,
1860 });
1861 defer manifest_linked.deinit(allocator);
1862
1863 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1864 try std.testing.expectEqual(@as(u64, 17), text_record.size);
1865 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "icf-none.o", .section, ".text.first", first_index) != null);
1866 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "icf-none.o", .section, ".text.second", second_index) == null);
1867 const folded_second = metadata.manifestDiscardedContribution(manifest_linked.manifest, "icf-none.o", ".text.second", second_index) orelse return error.MissingSection;
1868 try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1869 }
1870
1871 test "ELF linker folds relocated code sections with equivalent targets" {
1872 const allocator = std.testing.allocator;
1873
1874 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1875 const start_index: u16 = 1;
1876 const first_symbol: u32 = 3;
1877 const second_symbol: u32 = 4;
1878 const caller_object = try buildObject(allocator, .{
1879 .sections = &.{Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16)},
1880 .symbols = &.{
1881 Symbol.section(start_index),
1882 Symbol.function("_start", start_index, 0, start_text.len),
1883 Symbol.undefinedFunction("first"),
1884 Symbol.undefinedFunction("second"),
1885 },
1886 .relocations = &.{
1887 Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
1888 Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
1889 },
1890 });
1891 defer allocator.free(caller_object);
1892
1893 const function_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1894
1895 const first_index: u16 = 1;
1896 const first_target_symbol: u32 = 3;
1897 const first_object = try buildObject(allocator, .{
1898 .sections = &.{Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16)},
1899 .symbols = &.{
1900 Symbol.section(first_index),
1901 Symbol.function("first", first_index, 0, function_text.len),
1902 Symbol.undefinedFunction("target"),
1903 },
1904 .relocations = &.{Relocation.x86_64(1, 1, first_target_symbol, .PLT32, -4)},
1905 });
1906 defer allocator.free(first_object);
1907
1908 const second_index: u16 = 1;
1909 const second_target_symbol: u32 = 3;
1910 const second_object = try buildObject(allocator, .{
1911 .sections = &.{Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16)},
1912 .symbols = &.{
1913 Symbol.section(second_index),
1914 Symbol.function("second", second_index, 0, function_text.len),
1915 Symbol.undefinedFunction("target"),
1916 },
1917 .relocations = &.{Relocation.x86_64(1, 1, second_target_symbol, .PLT32, -4)},
1918 });
1919 defer allocator.free(second_object);
1920
1921 const target_text = [_]u8{0xc3};
1922 const target_index: u16 = 1;
1923 const target_object = try buildObject(allocator, .{
1924 .sections = &.{Section.progbits(".text.target", &target_text, std.elf.SHF_EXECINSTR, 16)},
1925 .symbols = &.{ Symbol.section(target_index), Symbol.function("target", target_index, 0, target_text.len) },
1926 });
1927 defer allocator.free(target_object);
1928
1929 const inputs = [_]model.Input{
1930 .{ .name = "caller.o", .bytes = caller_object },
1931 .{ .name = "first.o", .bytes = first_object },
1932 .{ .name = "second.o", .bytes = second_object },
1933 .{ .name = "target.o", .bytes = target_object },
1934 };
1935
1936 var linked = try linkExecutable(allocator, &inputs, .{
1937 .incremental_mode = .off,
1938 .icf = .all,
1939 });
1940 defer linked.deinit(allocator);
1941
1942 var manifest_linked = try linkExecutable(allocator, &inputs, .{
1943 .icf = .all,
1944 });
1945 defer manifest_linked.deinit(allocator);
1946
1947 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
1948 try std.testing.expectEqual(@as(u64, 33), text.size);
1949 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "first.o", .section, ".text.first", first_index) != null);
1950 try std.testing.expect(metadata.manifestContribution(manifest_linked.manifest, "second.o", .section, ".text.second", second_index) == null);
1951 const folded_second = metadata.manifestDiscardedContribution(manifest_linked.manifest, "second.o", ".text.second", second_index) orelse return error.MissingSection;
1952 try std.testing.expectEqual(incremental.DiscardReason.identical_code_folded, folded_second.reason);
1953
1954 const text_file_offset: usize = @intCast(text.file_offset);
1955 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
1956 try std.testing.expectEqual(@as(i32, 6), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
1957 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
1958 }
1959
1960 test "ELF linker keeps relocated code sections with different targets distinct in ICF" {
1961 const allocator = std.testing.allocator;
1962
1963 const start_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3 };
1964 const function_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
1965 const target_text = [_]u8{0xc3};
1966 const start_index: u16 = 1;
1967 const first_index: u16 = 2;
1968 const second_index: u16 = 3;
1969 const first_target_index: u16 = 4;
1970 const second_target_index: u16 = 5;
1971 const first_symbol: u32 = 7;
1972 const second_symbol: u32 = 8;
1973 const first_target_symbol: u32 = 9;
1974 const second_target_symbol: u32 = 10;
1975 const object = try buildObject(allocator, .{
1976 .sections = &.{
1977 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
1978 Section.progbits(".text.first", &function_text, std.elf.SHF_EXECINSTR, 16),
1979 Section.progbits(".text.second", &function_text, std.elf.SHF_EXECINSTR, 16),
1980 Section.progbits(".text.first_target", &target_text, std.elf.SHF_EXECINSTR, 16),
1981 Section.progbits(".text.second_target", &target_text, std.elf.SHF_EXECINSTR, 16),
1982 },
1983 .symbols = &.{
1984 Symbol.section(start_index),
1985 Symbol.section(first_index),
1986 Symbol.section(second_index),
1987 Symbol.section(first_target_index),
1988 Symbol.section(second_target_index),
1989 Symbol.function("_start", start_index, 0, start_text.len),
1990 Symbol.function("first", first_index, 0, function_text.len),
1991 Symbol.function("second", second_index, 0, function_text.len),
1992 Symbol.function("first_target", first_target_index, 0, target_text.len),
1993 Symbol.function("second_target", second_target_index, 0, target_text.len),
1994 },
1995 .relocations = &.{
1996 Relocation.x86_64(start_index, 1, first_symbol, .PLT32, -4),
1997 Relocation.x86_64(start_index, 6, second_symbol, .PLT32, -4),
1998 Relocation.x86_64(first_index, 1, first_target_symbol, .PLT32, -4),
1999 Relocation.x86_64(second_index, 1, second_target_symbol, .PLT32, -4),
2000 },
2001 });
2002 defer allocator.free(object);
2003
2004 var linked = try linkExecutable(allocator, &.{.{ .name = "distinct-targets.o", .bytes = object }}, .{
2005 .icf = .all,
2006 });
2007 defer linked.deinit(allocator);
2008
2009 try std.testing.expect(metadata.manifestContribution(linked.manifest, "distinct-targets.o", .section, ".text.first", first_index) != null);
2010 try std.testing.expect(metadata.manifestContribution(linked.manifest, "distinct-targets.o", .section, ".text.second", second_index) != null);
2011 }
2012
2013 test "ELF linker applies absolute relocations to read-only data" {
2014 const allocator = std.testing.allocator;
2015 const text = [_]u8{
2016 0x48, 0xb8,
2017 0, 0,
2018 0, 0,
2019 0, 0,
2020 0, 0,
2021 0xc3,
2022 };
2023 const rodata = [_]u8{ 'o', 'k', 0 };
2024 const text_index: u16 = 1;
2025 const rodata_index: u16 = 2;
2026 const message_symbol: u32 = 4;
2027 const object = try buildObject(allocator, .{
2028 .sections = &.{
2029 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2030 Section.progbits(".rodata", &rodata, 0, 1),
2031 },
2032 .symbols = &.{
2033 Symbol.section(text_index),
2034 Symbol.section(rodata_index),
2035 Symbol.function("_start", text_index, 0, text.len),
2036 Symbol.object("message", rodata_index, 0, rodata.len),
2037 },
2038 .relocations = &.{Relocation.x86_64(1, 2, message_symbol, .@"64", 0)},
2039 });
2040 defer allocator.free(object);
2041
2042 var linked = try linkExecutable(allocator, &.{.{ .name = "abs.o", .bytes = object }}, .{ .incremental_mode = .off });
2043 defer linked.deinit(allocator);
2044
2045 const text_file_offset: usize = @intCast(linked.manifest.sections[0].file_offset);
2046 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2047 const rodata_address = rodata_record.address;
2048 try std.testing.expectEqual(rodata_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2049 const rodata_file_offset: usize = @intCast(rodata_record.file_offset);
2050 try std.testing.expectEqualSlices(u8, &rodata, linked.bytes[rodata_file_offset..][0..rodata.len]);
2051 }
2052
2053 test "ELF linker merges fixed-size mergeable constants" {
2054 const allocator = std.testing.allocator;
2055
2056 const text = [_]u8{
2057 0x48, 0xb8,
2058 0, 0,
2059 0, 0,
2060 0, 0,
2061 0, 0,
2062 0x48, 0xbb,
2063 0, 0,
2064 0, 0,
2065 0, 0,
2066 0, 0,
2067 0xc3,
2068 };
2069 const text_index: u16 = 1;
2070 const duplicated_symbol: u32 = 3;
2071 const section_addend_symbol: u32 = 4;
2072 const caller_object = try buildObject(allocator, .{
2073 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2074 .symbols = &.{
2075 Symbol.section(text_index),
2076 Symbol.function("_start", text_index, 0, text.len),
2077 Symbol.undefinedObject("duplicated_constant"),
2078 Symbol.undefinedObject("section_addend_constant"),
2079 },
2080 .relocations = &.{
2081 Relocation.x86_64(1, 2, duplicated_symbol, .@"64", 0),
2082 Relocation.x86_64(1, 12, section_addend_symbol, .@"64", 0),
2083 },
2084 });
2085 defer allocator.free(caller_object);
2086
2087 const constant = [_]u8{ 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe };
2088 const first_section: u16 = 1;
2089 const first_object = try buildObject(allocator, .{
2090 .sections = &.{Section.mergeable(".rodata.cst8", &constant, 0, 8, 8)},
2091 .symbols = &.{
2092 Symbol.section(first_section),
2093 Symbol.object("first_constant", first_section, 0, constant.len),
2094 },
2095 });
2096 defer allocator.free(first_object);
2097
2098 const duplicate_section: u16 = 1;
2099 const duplicate_object = try buildObject(allocator, .{
2100 .sections = &.{Section.mergeable(".rodata.cst8", &constant, 0, 8, 8)},
2101 .symbols = &.{
2102 Symbol.section(duplicate_section),
2103 Symbol.object("duplicated_constant", duplicate_section, 0, constant.len),
2104 },
2105 });
2106 defer allocator.free(duplicate_object);
2107
2108 const pair = constant ++ constant;
2109 const pair_section: u16 = 1;
2110 const pair_section_symbol: u32 = 1;
2111 const probe_text = [_]u8{
2112 0x48, 0xb8,
2113 0, 0,
2114 0, 0,
2115 0, 0,
2116 0, 0,
2117 0xc3,
2118 };
2119 const probe_text_index: u16 = 2;
2120 const section_addend_object = try buildObject(allocator, .{
2121 .sections = &.{
2122 Section.mergeable(".rodata.cst8", &pair, 0, 8, 8),
2123 Section.progbits(".text.probe", &probe_text, std.elf.SHF_EXECINSTR, 16),
2124 },
2125 .symbols = &.{
2126 Symbol.section(pair_section),
2127 Symbol.section(probe_text_index),
2128 Symbol.object("section_addend_constant", pair_section, 8, constant.len),
2129 Symbol.function("section_probe", probe_text_index, 0, probe_text.len),
2130 },
2131 .relocations = &.{Relocation.x86_64(probe_text_index, 2, pair_section_symbol, .@"64", 8)},
2132 });
2133 defer allocator.free(section_addend_object);
2134
2135 var linked = try linkExecutable(allocator, &.{
2136 .{ .name = "caller.o", .bytes = caller_object },
2137 .{ .name = "first.o", .bytes = first_object },
2138 .{ .name = "duplicate.o", .bytes = duplicate_object },
2139 .{ .name = "section-addend.o", .bytes = section_addend_object },
2140 }, .{ .incremental_mode = .off });
2141 defer linked.deinit(allocator);
2142
2143 const rodata = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2144 try std.testing.expectEqual(@as(u64, constant.len), rodata.size);
2145 try std.testing.expectEqualSlices(u8, &constant, linked.bytes[@as(usize, @intCast(rodata.file_offset))..][0..constant.len]);
2146
2147 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2148 const text_file_offset: usize = @intCast(text_record.file_offset);
2149 try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2150 try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[text_file_offset + 12 ..][0..8], .little));
2151
2152 const probe_file_offset = @as(usize, @intCast(text_record.file_offset)) + alignForward(text.len, 16);
2153 try std.testing.expectEqual(rodata.address, std.mem.readInt(u64, linked.bytes[probe_file_offset + 2 ..][0..8], .little));
2154 }
2155
2156 test "ELF linker preserves external mergeable string starts" {
2157 const allocator = std.testing.allocator;
2158
2159 const text = [_]u8{
2160 0x48, 0xb8,
2161 0, 0,
2162 0, 0,
2163 0, 0,
2164 0, 0,
2165 0x48, 0xbb,
2166 0, 0,
2167 0, 0,
2168 0, 0,
2169 0, 0,
2170 0xc3,
2171 };
2172 const text_index: u16 = 1;
2173 const suffix_symbol: u32 = 3;
2174 const duplicate_symbol: u32 = 4;
2175 const caller_object = try buildObject(allocator, .{
2176 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2177 .symbols = &.{
2178 Symbol.section(text_index),
2179 Symbol.function("_start", text_index, 0, text.len),
2180 Symbol.undefinedObject("suffix_string"),
2181 Symbol.undefinedObject("duplicate_suffix"),
2182 },
2183 .relocations = &.{
2184 Relocation.x86_64(1, 2, suffix_symbol, .@"64", 0),
2185 Relocation.x86_64(1, 12, duplicate_symbol, .@"64", 0),
2186 },
2187 });
2188 defer allocator.free(caller_object);
2189
2190 const prefix = "prefix-";
2191 const suffix = "tail";
2192 const long_string = prefix ++ suffix ++ "\x00";
2193 const long_section: u16 = 1;
2194 const long_object = try buildObject(allocator, .{
2195 .sections = &.{Section.strings(".rodata.str1.1", long_string, 1)},
2196 .symbols = &.{
2197 Symbol.section(long_section),
2198 Symbol.object("long_string", long_section, 0, long_string.len),
2199 },
2200 });
2201 defer allocator.free(long_object);
2202
2203 const suffix_string = suffix ++ "\x00";
2204 const suffix_section: u16 = 1;
2205 const suffix_object = try buildObject(allocator, .{
2206 .sections = &.{Section.strings(".rodata.str1.1", suffix_string, 1)},
2207 .symbols = &.{
2208 Symbol.section(suffix_section),
2209 Symbol.object("suffix_string", suffix_section, 0, suffix_string.len),
2210 },
2211 });
2212 defer allocator.free(suffix_object);
2213
2214 const duplicate_section: u16 = 1;
2215 const duplicate_object = try buildObject(allocator, .{
2216 .sections = &.{Section.strings(".rodata.str1.1", suffix_string, 1)},
2217 .symbols = &.{
2218 Symbol.section(duplicate_section),
2219 Symbol.object("duplicate_suffix", duplicate_section, 0, suffix_string.len),
2220 },
2221 });
2222 defer allocator.free(duplicate_object);
2223
2224 const second_prefix = "second-";
2225 const second_long = second_prefix ++ suffix ++ "\x00";
2226 const pair = second_long ++ suffix_string;
2227 const second_suffix_offset = second_long.len;
2228 const pair_section: u16 = 1;
2229 const pair_section_symbol: u32 = 1;
2230 const probe_text = [_]u8{
2231 0x48, 0xb8,
2232 0, 0,
2233 0, 0,
2234 0, 0,
2235 0, 0,
2236 0xc3,
2237 };
2238 const probe_text_index: u16 = 2;
2239 const section_addend_object = try buildObject(allocator, .{
2240 .sections = &.{
2241 Section.strings(".rodata.str1.1", pair, 1),
2242 Section.progbits(".text.probe", &probe_text, std.elf.SHF_EXECINSTR, 16),
2243 },
2244 .symbols = &.{
2245 Symbol.section(pair_section),
2246 Symbol.section(probe_text_index),
2247 Symbol.function("section_probe", probe_text_index, 0, probe_text.len),
2248 },
2249 .relocations = &.{
2250 Relocation.x86_64(probe_text_index, 2, pair_section_symbol, .@"64", second_suffix_offset),
2251 },
2252 });
2253 defer allocator.free(section_addend_object);
2254
2255 var linked = try linkExecutable(allocator, &.{
2256 .{ .name = "caller.o", .bytes = caller_object },
2257 .{ .name = "long.o", .bytes = long_object },
2258 .{ .name = "suffix.o", .bytes = suffix_object },
2259 .{ .name = "duplicate.o", .bytes = duplicate_object },
2260 .{ .name = "section-addend.o", .bytes = section_addend_object },
2261 }, .{ .incremental_mode = .off });
2262 defer linked.deinit(allocator);
2263
2264 const rodata = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2265 const expected_rodata = long_string ++ second_long ++ suffix_string;
2266 try std.testing.expectEqual(@as(u64, expected_rodata.len), rodata.size);
2267 try std.testing.expectEqualSlices(u8, expected_rodata, linked.bytes[@as(usize, @intCast(rodata.file_offset))..][0..expected_rodata.len]);
2268
2269 const expected_suffix_address = rodata.address + long_string.len + second_long.len;
2270 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2271 const text_file_offset: usize = @intCast(text_record.file_offset);
2272 try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 2 ..][0..8], .little));
2273 try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[text_file_offset + 12 ..][0..8], .little));
2274
2275 const probe_file_offset = @as(usize, @intCast(text_record.file_offset)) + alignForward(text.len, 16);
2276 try std.testing.expectEqual(expected_suffix_address, std.mem.readInt(u64, linked.bytes[probe_file_offset + 2 ..][0..8], .little));
2277 }
2278
2279 test "ELF linker relaxes x86_64 GOTPCRELX data address loads" {
2280 const allocator = std.testing.allocator;
2281 const text = [_]u8{
2282 0x48, 0x8b, 0x05,
2283 0, 0, 0,
2284 0, 0xc3,
2285 };
2286 const rodata = [_]u8{ 'o', 'k', 0 };
2287 const text_index: u16 = 1;
2288 const rodata_index: u16 = 2;
2289 const message_symbol: u32 = 4;
2290 const object = try buildObject(allocator, .{
2291 .sections = &.{
2292 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2293 Section.progbits(".rodata", &rodata, 0, 1),
2294 },
2295 .symbols = &.{
2296 Symbol.section(text_index),
2297 Symbol.section(rodata_index),
2298 Symbol.function("_start", text_index, 0, text.len),
2299 Symbol.object("message", rodata_index, 0, rodata.len),
2300 },
2301 .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4)},
2302 });
2303 defer allocator.free(object);
2304
2305 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx.o", .bytes = object }}, .{ .incremental_mode = .off });
2306 defer linked.deinit(allocator);
2307
2308 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2309 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2310 const text_file_offset: usize = @intCast(text_record.file_offset);
2311 try std.testing.expectEqual(@as(u8, 0x8d), linked.bytes[text_file_offset + 1]);
2312
2313 const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little);
2314 const next_instruction = text_record.address + 7;
2315 const loaded_address = @as(i128, next_instruction) + displacement;
2316 try std.testing.expectEqual(@as(i128, rodata_record.address), loaded_address);
2317 }
2318
2319 test "ELF linker relaxes x86_64 REX_GOTPCRELX address ALU operations" {
2320 const allocator = std.testing.allocator;
2321 const text = [_]u8{
2322 0x48, 0x3b, 0x35,
2323 0, 0, 0,
2324 0, 0x4c, 0x3b,
2325 0x0d, 0, 0,
2326 0, 0, 0x48,
2327 0x2b, 0x0d, 0,
2328 0, 0, 0,
2329 0xc3,
2330 };
2331 const rodata = [_]u8{ 'o', 'k', 0 };
2332 const text_index: u16 = 1;
2333 const rodata_index: u16 = 2;
2334 const message_symbol: u32 = 4;
2335 const object = try buildObject(allocator, .{
2336 .sections = &.{
2337 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2338 Section.progbits(".rodata", &rodata, 0, 1),
2339 },
2340 .symbols = &.{
2341 Symbol.section(text_index),
2342 Symbol.section(rodata_index),
2343 Symbol.function("_start", text_index, 0, text.len),
2344 Symbol.object("message", rodata_index, 0, rodata.len),
2345 },
2346 .relocations = &.{
2347 Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4),
2348 Relocation.x86_64(1, 10, message_symbol, .REX_GOTPCRELX, -4),
2349 Relocation.x86_64(1, 17, message_symbol, .REX_GOTPCRELX, -4),
2350 },
2351 });
2352 defer allocator.free(object);
2353
2354 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-alu.o", .bytes = object }}, .{ .incremental_mode = .off });
2355 defer linked.deinit(allocator);
2356
2357 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2358 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2359 const text_file_offset: usize = @intCast(text_record.file_offset);
2360 try std.testing.expectEqual(@as(u8, 0x48), linked.bytes[text_file_offset]);
2361 try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 1]);
2362 try std.testing.expectEqual(@as(u8, 0xfe), linked.bytes[text_file_offset + 2]);
2363 try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little));
2364 try std.testing.expectEqual(@as(u8, 0x49), linked.bytes[text_file_offset + 7]);
2365 try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 8]);
2366 try std.testing.expectEqual(@as(u8, 0xf9), linked.bytes[text_file_offset + 9]);
2367 try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 10 ..][0..4], .little));
2368 try std.testing.expectEqual(@as(u8, 0x48), linked.bytes[text_file_offset + 14]);
2369 try std.testing.expectEqual(@as(u8, 0x81), linked.bytes[text_file_offset + 15]);
2370 try std.testing.expectEqual(@as(u8, 0xe9), linked.bytes[text_file_offset + 16]);
2371 try std.testing.expectEqual(@as(i32, @intCast(rodata_record.address)), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
2372 }
2373
2374 test "ELF linker relaxes x86_64 GOTPCRELX indirect calls" {
2375 const allocator = std.testing.allocator;
2376 const text = [_]u8{
2377 0xff, 0x15,
2378 0, 0,
2379 0, 0,
2380 0xf4, 0xc3,
2381 };
2382 const text_index: u16 = 1;
2383 const callee_symbol: u32 = 3;
2384 const object = try buildObject(allocator, .{
2385 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2386 .symbols = &.{
2387 Symbol.section(text_index),
2388 Symbol.function("_start", text_index, 0, 7),
2389 Symbol.function("callee", text_index, 7, 1),
2390 },
2391 .relocations = &.{Relocation.x86_64(1, 2, callee_symbol, .GOTPCRELX, -4)},
2392 });
2393 defer allocator.free(object);
2394
2395 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-call.o", .bytes = object }}, .{ .incremental_mode = .off });
2396 defer linked.deinit(allocator);
2397
2398 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2399 const text_file_offset: usize = @intCast(text_record.file_offset);
2400 try std.testing.expectEqual(@as(u8, 0x67), linked.bytes[text_file_offset]);
2401 try std.testing.expectEqual(@as(u8, 0xe8), linked.bytes[text_file_offset + 1]);
2402
2403 const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 2 ..][0..4], .little);
2404 const next_instruction = text_record.address + 6;
2405 const loaded_address = @as(i128, next_instruction) + displacement;
2406 try std.testing.expectEqual(@as(i128, text_record.address + 7), loaded_address);
2407 }
2408
2409 test "ELF linker relaxes x86_64 GOTPCRELX indirect jumps" {
2410 const allocator = std.testing.allocator;
2411 const text = [_]u8{
2412 0xff, 0x25,
2413 0, 0,
2414 0, 0,
2415 0xf4, 0xc3,
2416 };
2417 const text_index: u16 = 1;
2418 const callee_symbol: u32 = 3;
2419 const object = try buildObject(allocator, .{
2420 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2421 .symbols = &.{
2422 Symbol.section(text_index),
2423 Symbol.function("_start", text_index, 0, 7),
2424 Symbol.function("callee", text_index, 7, 1),
2425 },
2426 .relocations = &.{Relocation.x86_64(1, 2, callee_symbol, .GOTPCRELX, -4)},
2427 });
2428 defer allocator.free(object);
2429
2430 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrelx-jump.o", .bytes = object }}, .{ .incremental_mode = .off });
2431 defer linked.deinit(allocator);
2432
2433 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2434 const text_file_offset: usize = @intCast(text_record.file_offset);
2435 try std.testing.expectEqual(@as(u8, 0x67), linked.bytes[text_file_offset]);
2436 try std.testing.expectEqual(@as(u8, 0xe9), linked.bytes[text_file_offset + 1]);
2437
2438 const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 2 ..][0..4], .little);
2439 const next_instruction = text_record.address + 6;
2440 const loaded_address = @as(i128, next_instruction) + displacement;
2441 try std.testing.expectEqual(@as(i128, text_record.address + 7), loaded_address);
2442 }
2443
2444 test "ELF linker relaxes legacy x86_64 GOTPCREL data address loads" {
2445 const allocator = std.testing.allocator;
2446 const text = [_]u8{
2447 0x48, 0x8b, 0x05,
2448 0, 0, 0,
2449 0, 0xc3,
2450 };
2451 const rodata = [_]u8{ 'o', 'k', 0 };
2452 const text_index: u16 = 1;
2453 const rodata_index: u16 = 2;
2454 const message_symbol: u32 = 4;
2455 const object = try buildObject(allocator, .{
2456 .sections = &.{
2457 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2458 Section.progbits(".rodata", &rodata, 0, 1),
2459 },
2460 .symbols = &.{
2461 Symbol.section(text_index),
2462 Symbol.section(rodata_index),
2463 Symbol.function("_start", text_index, 0, text.len),
2464 Symbol.object("message", rodata_index, 0, rodata.len),
2465 },
2466 .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .GOTPCREL, -4)},
2467 });
2468 defer allocator.free(object);
2469
2470 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel.o", .bytes = object }}, .{ .incremental_mode = .off });
2471 defer linked.deinit(allocator);
2472
2473 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2474 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2475 const text_file_offset: usize = @intCast(text_record.file_offset);
2476 try std.testing.expectEqual(@as(u8, 0x8d), linked.bytes[text_file_offset + 1]);
2477
2478 const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 3 ..][0..4], .little);
2479 const next_instruction = text_record.address + 7;
2480 const loaded_address = @as(i128, next_instruction) + displacement;
2481 try std.testing.expectEqual(@as(i128, rodata_record.address), loaded_address);
2482 }
2483
2484 test "ELF linker emits x86_64 GOTPCREL entries for memory operands" {
2485 const allocator = std.testing.allocator;
2486 const text = [_]u8{
2487 0xf3, 0x0f, 0x7e, 0x0d,
2488 0, 0, 0, 0,
2489 0xc3,
2490 };
2491 const rodata = [_]u8{ 'o', 'k', 0 };
2492 const text_index: u16 = 1;
2493 const rodata_index: u16 = 2;
2494 const message_symbol: u32 = 4;
2495 const object = try buildObject(allocator, .{
2496 .sections = &.{
2497 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2498 Section.progbits(".rodata", &rodata, 0, 1),
2499 },
2500 .symbols = &.{
2501 Symbol.section(text_index),
2502 Symbol.section(rodata_index),
2503 Symbol.function("_start", text_index, 0, text.len),
2504 Symbol.object("message", rodata_index, 0, rodata.len),
2505 },
2506 .relocations = &.{Relocation.x86_64(1, 4, message_symbol, .GOTPCREL, -4)},
2507 });
2508 defer allocator.free(object);
2509
2510 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel-memory.o", .bytes = object }}, .{ .incremental_mode = .off });
2511 defer linked.deinit(allocator);
2512
2513 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2514 const rodata_record = metadata.manifestSection(linked.manifest, ".rodata") orelse return error.MissingSection;
2515 const got_record = metadata.manifestSection(linked.manifest, ".got") orelse return error.MissingSection;
2516 const text_file_offset: usize = @intCast(text_record.file_offset);
2517 try std.testing.expectEqual(@as(u64, 8), got_record.size);
2518 try std.testing.expectEqual(rodata_record.address, readU64(linked.bytes, @intCast(got_record.file_offset)));
2519 try std.testing.expectEqualSlices(u8, text[0..4], linked.bytes[text_file_offset..][0..4]);
2520
2521 const displacement = std.mem.readInt(i32, linked.bytes[text_file_offset + 4 ..][0..4], .little);
2522 const next_instruction = text_record.address + 8;
2523 const got_address = @as(i128, next_instruction) + displacement;
2524 try std.testing.expectEqual(@as(i128, got_record.address), got_address);
2525 }
2526
2527 test "ELF linker relaxes x86_64 GOTPCREL weak undefined null checks" {
2528 const allocator = std.testing.allocator;
2529 const text = [_]u8{
2530 0x48, 0x83, 0x3d,
2531 0, 0, 0,
2532 0, 0, 0x75,
2533 0x01, 0xcc, 0xc3,
2534 };
2535 const text_index: u16 = 1;
2536 const weak_symbol: u32 = 3;
2537 const object = try buildObject(allocator, .{
2538 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2539 .symbols = &.{
2540 Symbol.section(text_index),
2541 Symbol.function("_start", text_index, 0, text.len),
2542 Symbol.weakUndefinedFunction("weak_tls_init"),
2543 },
2544 .relocations = &.{Relocation.x86_64(text_index, 3, weak_symbol, .GOTPCREL, -5)},
2545 });
2546 defer allocator.free(object);
2547
2548 var linked = try linkExecutable(allocator, &.{.{ .name = "gotpcrel-weak-null.o", .bytes = object }}, .{ .incremental_mode = .off });
2549 defer linked.deinit(allocator);
2550
2551 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2552 const text_file_offset: usize = @intCast(text_record.file_offset);
2553 const expected = [_]u8{ 0x48, 0x39, 0xe4, 0x0f, 0x1f, 0x44, 0x00, 0x00 };
2554 try std.testing.expectEqualSlices(u8, &expected, linked.bytes[text_file_offset..][0..expected.len]);
2555 try std.testing.expectEqual(@as(u8, 0x75), linked.bytes[text_file_offset + 8]);
2556 }
2557
2558 test "ELF linker rejects malformed GOTPCRELX relaxation instructions" {
2559 const allocator = std.testing.allocator;
2560 const text = [_]u8{
2561 0x48, 0x8b, 0x45,
2562 0, 0, 0,
2563 0, 0xc3,
2564 };
2565 const rodata = [_]u8{ 'o', 'k', 0 };
2566 const text_index: u16 = 1;
2567 const rodata_index: u16 = 2;
2568 const message_symbol: u32 = 4;
2569 const object = try buildObject(allocator, .{
2570 .sections = &.{
2571 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2572 Section.progbits(".rodata", &rodata, 0, 1),
2573 },
2574 .symbols = &.{
2575 Symbol.section(text_index),
2576 Symbol.section(rodata_index),
2577 Symbol.function("_start", text_index, 0, text.len),
2578 Symbol.object("message", rodata_index, 0, rodata.len),
2579 },
2580 .relocations = &.{Relocation.x86_64(1, 3, message_symbol, .REX_GOTPCRELX, -4)},
2581 });
2582 defer allocator.free(object);
2583
2584 var diagnostics: model.Diagnostics = .{};
2585 try std.testing.expectError(error.UnsupportedRelocation, linkExecutable(
2586 allocator,
2587 &.{.{ .name = "bad-gotpcrelx.o", .bytes = object }},
2588 .{
2589 .incremental_mode = .off,
2590 .diagnostics = &diagnostics,
2591 },
2592 ));
2593
2594 const unsupported = diagnostics.first_unsupported_relocation orelse return error.ExpectedUnsupportedRelocationDiagnostic;
2595 try std.testing.expectEqual(@backingInt(std.elf.R_X86_64.REX_GOTPCRELX), unsupported.relocation_type);
2596 }
2597
2598 test "ELF linker places x86_64 unwind sections in read-only output" {
2599 const allocator = std.testing.allocator;
2600 const text = [_]u8{0xc3};
2601 const unwind = [_]u8{ 0x14, 0, 0, 0, 0, 0, 0, 0 };
2602 const text_index: u16 = 1;
2603 const unwind_index: u16 = 2;
2604 const object = try buildObject(allocator, .{
2605 .sections = &.{
2606 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2607 .{
2608 .name = ".eh_frame",
2609 .section_type = std.elf.SHT_X86_64_UNWIND,
2610 .flags = std.elf.SHF_ALLOC,
2611 .alignment = 8,
2612 .bytes = &unwind,
2613 },
2614 },
2615 .symbols = &.{
2616 Symbol.section(text_index),
2617 Symbol.section(unwind_index),
2618 Symbol.function("_start", text_index, 0, text.len),
2619 },
2620 });
2621 defer allocator.free(object);
2622
2623 var linked = try linkExecutable(allocator, &.{.{ .name = "unwind.o", .bytes = object }}, .{ .incremental_mode = .off });
2624 defer linked.deinit(allocator);
2625
2626 const eh_frame = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2627 const payload_offset: usize = @intCast(eh_frame.file_offset);
2628 try std.testing.expectEqualSlices(u8, &unwind, linked.bytes[payload_offset..][0..unwind.len]);
2629 }
2630
2631 test "ELF linker prunes eh_frame FDEs for discarded COMDAT sections" {
2632 const allocator = std.testing.allocator;
2633
2634 const retained_text = [_]u8{0xc3};
2635 const retained_text_index: u16 = 1;
2636 const retained_symbol: u32 = 2;
2637 var retained_group_payload: [(1 + 1) * 4]u8 = undefined;
2638 const retained_group_bytes = groupBytes(&retained_group_payload, &.{retained_text_index});
2639 const retained_object = try buildObject(allocator, .{
2640 .sections = &.{
2641 Section.progbits(".text.inline", &retained_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
2642 Section.group(".group", retained_group_bytes, retained_symbol),
2643 },
2644 .symbols = &.{
2645 Symbol.section(retained_text_index),
2646 Symbol.function("inline_fn", retained_text_index, 0, retained_text.len),
2647 },
2648 });
2649 defer allocator.free(retained_object);
2650
2651 const start_text = [_]u8{0xc3};
2652 const duplicate_text = [_]u8{0xcc};
2653 var unwind = @as([0x40]u8, @splat(0));
2654 std.mem.writeInt(u32, unwind[0x00..][0..4], 0x14, .little);
2655 std.mem.writeInt(u32, unwind[0x04..][0..4], 0, .little);
2656 std.mem.writeInt(u32, unwind[0x18..][0..4], 0x10, .little);
2657 std.mem.writeInt(u32, unwind[0x1c..][0..4], 0x1c, .little);
2658 std.mem.writeInt(u32, unwind[0x24..][0..4], 1, .little);
2659 std.mem.writeInt(u32, unwind[0x2c..][0..4], 0x10, .little);
2660 std.mem.writeInt(u32, unwind[0x30..][0..4], 0x30, .little);
2661 std.mem.writeInt(u32, unwind[0x38..][0..4], 1, .little);
2662
2663 const start_text_index: u16 = 1;
2664 const duplicate_text_index: u16 = 2;
2665 const eh_frame_index: u16 = 3;
2666 const duplicate_section_symbol: u32 = 2;
2667 const start_symbol: u32 = 4;
2668 const duplicate_symbol: u32 = 5;
2669 var frame_group_payload: [(1 + 1) * 4]u8 = undefined;
2670 const frame_group_bytes = groupBytes(&frame_group_payload, &.{duplicate_text_index});
2671 const frame_object = try buildObject(allocator, .{
2672 .sections = &.{
2673 Section.progbits(".text.start", &start_text, std.elf.SHF_EXECINSTR, 16),
2674 Section.progbits(".text.inline", &duplicate_text, std.elf.SHF_EXECINSTR | std.elf.SHF_GROUP, 16),
2675 .{
2676 .name = ".eh_frame",
2677 .section_type = std.elf.SHT_X86_64_UNWIND,
2678 .flags = std.elf.SHF_ALLOC,
2679 .alignment = 8,
2680 .bytes = &unwind,
2681 },
2682 Section.group(".group", frame_group_bytes, duplicate_symbol),
2683 },
2684 .symbols = &.{
2685 Symbol.section(start_text_index),
2686 Symbol.section(duplicate_text_index),
2687 Symbol.section(eh_frame_index),
2688 Symbol.function("_start", start_text_index, 0, start_text.len),
2689 Symbol.function("inline_fn", duplicate_text_index, 0, duplicate_text.len),
2690 },
2691 .relocations = &.{
2692 Relocation.x86_64(eh_frame_index, 0x20, duplicate_section_symbol, .PC32, 0),
2693 Relocation.x86_64(eh_frame_index, 0x34, start_symbol, .PC32, 0),
2694 },
2695 });
2696 defer allocator.free(frame_object);
2697
2698 var linked = try linkExecutable(allocator, &.{
2699 .{ .name = "retained.o", .bytes = retained_object },
2700 .{ .name = "frame.o", .bytes = frame_object },
2701 }, .{ .incremental_mode = .off });
2702 defer linked.deinit(allocator);
2703
2704 const eh_frame_record = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2705 try std.testing.expectEqual(@as(u64, 0x2c), eh_frame_record.size);
2706 const payload_offset: usize = @intCast(eh_frame_record.file_offset);
2707 try std.testing.expectEqual(@as(u32, 0x14), std.mem.readInt(u32, linked.bytes[payload_offset..][0..4], .little));
2708 try std.testing.expectEqual(@as(u32, 0x10), std.mem.readInt(u32, linked.bytes[payload_offset + 0x18 ..][0..4], .little));
2709 try std.testing.expectEqual(@as(u32, 0x1c), std.mem.readInt(u32, linked.bytes[payload_offset + 0x1c ..][0..4], .little));
2710 try std.testing.expect(std.mem.readInt(i32, linked.bytes[payload_offset + 0x20 ..][0..4], .little) != 0);
2711 }
2712
2713 test "ELF linker emits eh_frame_hdr for retained FDEs" {
2714 const allocator = std.testing.allocator;
2715
2716 const text = [_]u8{0xc3};
2717 var unwind = @as([0x2c]u8, @splat(0));
2718 std.mem.writeInt(u32, unwind[0x00..][0..4], 0x14, .little);
2719 std.mem.writeInt(u32, unwind[0x04..][0..4], 0, .little);
2720 std.mem.writeInt(u32, unwind[0x18..][0..4], 0x10, .little);
2721 std.mem.writeInt(u32, unwind[0x1c..][0..4], 0x1c, .little);
2722 std.mem.writeInt(u32, unwind[0x24..][0..4], 1, .little);
2723
2724 const text_index: u16 = 1;
2725 const eh_frame_index: u16 = 2;
2726 const start_symbol: u32 = 3;
2727 const object = try buildObject(allocator, .{
2728 .sections = &.{
2729 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2730 .{
2731 .name = ".eh_frame",
2732 .section_type = std.elf.SHT_X86_64_UNWIND,
2733 .flags = std.elf.SHF_ALLOC,
2734 .alignment = 8,
2735 .bytes = &unwind,
2736 },
2737 },
2738 .symbols = &.{
2739 Symbol.section(text_index),
2740 Symbol.section(eh_frame_index),
2741 Symbol.function("_start", text_index, 0, text.len),
2742 },
2743 .relocations = &.{Relocation.x86_64(eh_frame_index, 0x20, start_symbol, .PC32, 0)},
2744 });
2745 defer allocator.free(object);
2746
2747 var linked = try linkExecutable(allocator, &.{.{ .name = "eh-frame-hdr.o", .bytes = object }}, .{ .incremental_mode = .off, .eh_frame_header = true });
2748 defer linked.deinit(allocator);
2749
2750 const header = try metadata.linkedSectionByName(linked.bytes, ".eh_frame_hdr");
2751 const rodata = metadata.manifestSection(linked.manifest, ".eh_frame") orelse return error.MissingSection;
2752 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2753 const segment = try metadata.programHeaderByType(linked.bytes, std.elf.PT_GNU_EH_FRAME);
2754
2755 try std.testing.expectEqual(std.elf.SHT_PROGBITS, header.section_type);
2756 try std.testing.expectEqual(@as(u64, std.elf.SHF_ALLOC), header.flags);
2757 try std.testing.expectEqual(@as(u64, eh_frame_hdr_fixed_size + eh_frame_hdr_entry_size), header.size);
2758 try std.testing.expectEqual(header.address, segment.virtual_address);
2759 try std.testing.expectEqual(header.offset, segment.file_offset);
2760 try std.testing.expectEqual(header.size, segment.file_size);
2761
2762 const offset: usize = @intCast(header.offset);
2763 try std.testing.expectEqual(@as(u8, 1), linked.bytes[offset + 0]);
2764 try std.testing.expectEqual(@as(u8, dwarf_eh_pe_pcrel | dwarf_eh_pe_sdata4), linked.bytes[offset + 1]);
2765 try std.testing.expectEqual(@as(u8, dwarf_eh_pe_udata4), linked.bytes[offset + 2]);
2766 try std.testing.expectEqual(@as(u8, dwarf_eh_pe_datarel | dwarf_eh_pe_sdata4), linked.bytes[offset + 3]);
2767 try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(rodata.address)) - @as(i128, @intCast(header.address + 4)))), std.mem.readInt(i32, linked.bytes[offset + 4 ..][0..4], .little));
2768 try std.testing.expectEqual(@as(u32, 1), std.mem.readInt(u32, linked.bytes[offset + 8 ..][0..4], .little));
2769 try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(text_record.address)) - @as(i128, @intCast(header.address)))), std.mem.readInt(i32, linked.bytes[offset + 12 ..][0..4], .little));
2770 try std.testing.expectEqual(@as(i32, @intCast(@as(i128, @intCast(rodata.address + 0x18)) - @as(i128, @intCast(header.address)))), std.mem.readInt(i32, linked.bytes[offset + 16 ..][0..4], .little));
2771 }
2772
2773 test "ELF linker preserves non-allocated DWARF sections in executable output" {
2774 const allocator = std.testing.allocator;
2775
2776 const text = [_]u8{0xc3};
2777 const debug = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
2778 const text_index: u16 = 1;
2779 const debug_index: u16 = 2;
2780 const object = try buildObject(allocator, .{
2781 .sections = &.{
2782 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2783 Section.nonAlloc(".debug_info", &debug, std.elf.SHT_PROGBITS, 1),
2784 },
2785 .symbols = &.{
2786 Symbol.section(text_index),
2787 Symbol.section(debug_index),
2788 Symbol.function("_start", text_index, 0, text.len),
2789 },
2790 });
2791 defer allocator.free(object);
2792
2793 var linked = try linkExecutable(allocator, &.{.{ .name = "debug.o", .bytes = object }}, .{});
2794 defer linked.deinit(allocator);
2795
2796 try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".text", text_index) != null);
2797 const debug_contribution = metadata.manifestContribution(linked.manifest, "debug.o", .section, ".debug_info", debug_index) orelse return error.MissingSection;
2798 try std.testing.expectEqual(@as(u64, debug.len), debug_contribution.file_size);
2799 try std.testing.expectEqual(@as(usize, 2), linked.manifest.sections.len);
2800
2801 const debug_plan = linked.manifest.planContributionReplacement(&.{
2802 .{ .input_name = "debug.o", .input_index = 0, .kind = .section, .name = ".debug_info", .ordinal = debug_index, .size = debug.len, .alignment = 1 },
2803 });
2804 try std.testing.expectEqual(incremental.PatchDecision.in_place, debug_plan.decision);
2805
2806 const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2807 try std.testing.expectEqual(@as(u64, 0), debug_section.address);
2808 try std.testing.expect(debug_section.size >= debug.len);
2809 try std.testing.expectEqualSlices(u8, &debug, linked.bytes[@intCast(debug_section.offset)..][0..debug.len]);
2810 }
2811
2812 test "ELF linker strips non-allocated DWARF sections when requested" {
2813 const allocator = std.testing.allocator;
2814
2815 const text = [_]u8{0xc3};
2816 const debug = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 };
2817 const text_index: u16 = 1;
2818 const debug_index: u16 = 2;
2819 const object = try buildObject(allocator, .{
2820 .sections = &.{
2821 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2822 Section.nonAlloc(".debug_info", &debug, std.elf.SHT_PROGBITS, 1),
2823 },
2824 .symbols = &.{
2825 Symbol.section(text_index),
2826 Symbol.section(debug_index),
2827 Symbol.function("_start", text_index, 0, text.len),
2828 },
2829 });
2830 defer allocator.free(object);
2831
2832 var linked = try linkExecutable(allocator, &.{.{ .name = "debug.o", .bytes = object }}, .{ .strip_debug = true });
2833 defer linked.deinit(allocator);
2834
2835 try std.testing.expect(linked.manifest.strip_debug);
2836 try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".text", text_index) != null);
2837 try std.testing.expect(metadata.manifestContribution(linked.manifest, "debug.o", .section, ".debug_info", debug_index) == null);
2838 try std.testing.expectEqual(@as(usize, 1), linked.manifest.sections.len);
2839 try std.testing.expectError(error.MissingSection, metadata.linkedSectionByName(linked.bytes, ".debug_info"));
2840 }
2841
2842 test "ELF linker applies relocations in non-allocated DWARF sections" {
2843 const allocator = std.testing.allocator;
2844
2845 const text = [_]u8{0xc3};
2846 const debug_info = @as([16]u8, @splat(0));
2847 const debug_abbrev = [_]u8{ 1, 2, 3, 4 };
2848 const text_index: u16 = 1;
2849 const debug_info_index: u16 = 2;
2850 const debug_abbrev_index: u16 = 3;
2851 const debug_abbrev_symbol: u32 = 3;
2852 const start_symbol: u32 = 4;
2853 const object = try buildObject(allocator, .{
2854 .sections = &.{
2855 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2856 Section.nonAlloc(".debug_info", &debug_info, std.elf.SHT_PROGBITS, 1),
2857 Section.nonAlloc(".debug_abbrev", &debug_abbrev, std.elf.SHT_PROGBITS, 1),
2858 },
2859 .symbols = &.{
2860 Symbol.section(text_index),
2861 Symbol.section(debug_info_index),
2862 Symbol.section(debug_abbrev_index),
2863 Symbol.function("_start", text_index, 0, text.len),
2864 },
2865 .relocations = &.{
2866 Relocation.x86_64(debug_info_index, 0, start_symbol, .@"64", 0),
2867 Relocation.x86_64(debug_info_index, 8, debug_abbrev_symbol, .@"64", 2),
2868 },
2869 });
2870 defer allocator.free(object);
2871
2872 var linked = try linkExecutable(allocator, &.{.{ .name = "debug-reloc.o", .bytes = object }}, .{ .incremental_mode = .off });
2873 defer linked.deinit(allocator);
2874
2875 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2876 const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2877 const debug_offset: usize = @intCast(debug_section.offset);
2878 try std.testing.expectEqual(text_record.address, readU64(linked.bytes, debug_offset));
2879 try std.testing.expectEqual(@as(u64, 2), readU64(linked.bytes, debug_offset + 8));
2880 }
2881
2882 test "ELF linker leaves DWARF relocations to GC-discarded sections unresolved" {
2883 const allocator = std.testing.allocator;
2884
2885 const live_text = [_]u8{0xc3};
2886 const dead_text = [_]u8{0xc3};
2887 const debug_info = @as([8]u8, @splat(0));
2888 const live_index: u16 = 1;
2889 const dead_index: u16 = 2;
2890 const debug_index: u16 = 3;
2891 const dead_symbol: u32 = 5;
2892 const object = try buildObject(allocator, .{
2893 .sections = &.{
2894 Section.progbits(".text.live", &live_text, std.elf.SHF_EXECINSTR, 16),
2895 Section.progbits(".text.dead", &dead_text, std.elf.SHF_EXECINSTR, 16),
2896 Section.nonAlloc(".debug_info", &debug_info, std.elf.SHT_PROGBITS, 1),
2897 },
2898 .symbols = &.{
2899 Symbol.section(live_index),
2900 Symbol.section(dead_index),
2901 Symbol.section(debug_index),
2902 Symbol.function("_start", live_index, 0, live_text.len),
2903 Symbol.function("dead", dead_index, 0, dead_text.len),
2904 },
2905 .relocations = &.{Relocation.x86_64(debug_index, 0, dead_symbol, .@"64", 0)},
2906 });
2907 defer allocator.free(object);
2908
2909 var linked = try linkExecutable(allocator, &.{.{ .name = "debug-gc.o", .bytes = object }}, .{ .gc_sections = true, .incremental_mode = .off });
2910 defer linked.deinit(allocator);
2911
2912 const debug_section = try metadata.linkedSectionByName(linked.bytes, ".debug_info");
2913 try std.testing.expectEqual(@as(u64, 0), readU64(linked.bytes, @intCast(debug_section.offset)));
2914 }
2915
2916 test "ELF linker keeps SHT_NOBITS out of file payload" {
2917 const allocator = std.testing.allocator;
2918 const text = [_]u8{
2919 0x48, 0xb8,
2920 0, 0,
2921 0, 0,
2922 0, 0,
2923 0, 0,
2924 0xc3,
2925 };
2926 const text_index: u16 = 1;
2927 const bss_index: u16 = 2;
2928 const scratch_symbol: u32 = 4;
2929 const object = try buildObject(allocator, .{
2930 .sections = &.{
2931 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
2932 Section.nobits(".bss", 32, std.elf.SHF_WRITE, 16),
2933 },
2934 .symbols = &.{
2935 Symbol.section(text_index),
2936 Symbol.section(bss_index),
2937 Symbol.function("_start", text_index, 0, text.len),
2938 Symbol.object("scratch", bss_index, 0, 32),
2939 },
2940 .relocations = &.{Relocation.x86_64(1, 2, scratch_symbol, .@"64", 0)},
2941 });
2942 defer allocator.free(object);
2943
2944 var linked = try linkExecutable(allocator, &.{.{ .name = "bss.o", .bytes = object }}, .{ .incremental_mode = .off });
2945 defer linked.deinit(allocator);
2946
2947 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
2948 const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
2949 try std.testing.expectEqual(@as(u64, 32), bss_record.size);
2950 try std.testing.expectEqual(@as(u64, 32), bss_record.reserved_size);
2951 try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
2952 try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
2953
2954 const bss_header = try metadata.linkedSectionByName(linked.bytes, ".bss");
2955 try std.testing.expectEqual(std.elf.SHT_NOBITS, bss_header.section_type);
2956 try std.testing.expectEqual(bss_record.file_offset, bss_header.offset);
2957 try std.testing.expectEqual(@as(u64, 32), bss_header.size);
2958
2959 const bss_load = try metadata.loadProgramHeaderByAddress(linked.bytes, bss_record.address);
2960 try std.testing.expectEqual(std.elf.PF_R | std.elf.PF_W, bss_load.flags);
2961 try std.testing.expectEqual(bss_record.file_offset, bss_load.file_offset);
2962 try std.testing.expectEqual(bss_record.address, bss_load.virtual_address);
2963 try std.testing.expectEqual(@as(u64, 0), bss_load.file_size);
2964 try std.testing.expectEqual(@as(u64, 32), bss_load.memory_size);
2965 }
2966
2967 test "ELF linker resolves symbols on empty allocated sections" {
2968 const allocator = std.testing.allocator;
2969
2970 const text = [_]u8{
2971 0x48, 0xb8,
2972 0, 0,
2973 0, 0,
2974 0, 0,
2975 0, 0,
2976 0xc3,
2977 };
2978 const caller_text_index: u16 = 1;
2979 const marker_ref: u32 = 3;
2980 const caller_object = try buildObject(allocator, .{
2981 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
2982 .symbols = &.{
2983 Symbol.section(caller_text_index),
2984 Symbol.function("_start", caller_text_index, 0, text.len),
2985 Symbol.undefinedObject("__TMC_END__"),
2986 },
2987 .relocations = &.{Relocation.x86_64(1, 2, marker_ref, .@"64", 0)},
2988 });
2989 defer allocator.free(caller_object);
2990
2991 const data = [_]u8{ 1, 2, 3 };
2992 const marker_index: u16 = 2;
2993 const marker_object = try buildObject(allocator, .{
2994 .sections = &.{
2995 Section.progbits(".data.payload", &data, std.elf.SHF_WRITE, 1),
2996 Section.progbits(".tm_clone_table", &.{}, std.elf.SHF_WRITE, 8),
2997 },
2998 .symbols = &.{
2999 Symbol.section(1),
3000 Symbol.section(marker_index),
3001 Symbol.object("__TMC_END__", marker_index, 0, 0),
3002 },
3003 });
3004 defer allocator.free(marker_object);
3005
3006 var linked = try linkExecutable(
3007 allocator,
3008 &.{
3009 .{ .name = "caller.o", .bytes = caller_object },
3010 .{ .name = "marker.o", .bytes = marker_object },
3011 },
3012 .{},
3013 );
3014 defer linked.deinit(allocator);
3015
3016 const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
3017 const marker_contribution = metadata.manifestContribution(linked.manifest, "marker.o", .section, ".tm_clone_table", marker_index) orelse return error.MissingSection;
3018 const loaded = readU64(linked.bytes, @intCast(caller_contribution.file_offset + 2));
3019 try std.testing.expectEqual(@as(u64, 0), marker_contribution.size);
3020 try std.testing.expectEqual(@as(u64, 8), marker_contribution.alignment);
3021 try std.testing.expectEqual(@as(u64, 0), marker_contribution.address % 8);
3022 try std.testing.expectEqual(marker_contribution.address, loaded);
3023 }
3024
3025 test "ELF linker emits static TLS sections and local-exec relocations" {
3026 const allocator = std.testing.allocator;
3027 const text = [_]u8{
3028 0x64, 0x8b, 0x04, 0x25,
3029 0, 0, 0, 0,
3030 0x64, 0x8b, 0x14, 0x25,
3031 0, 0, 0, 0,
3032 0xc3,
3033 };
3034 const tdata = [_]u8{ 7, 0, 0, 0 };
3035 const text_index: u16 = 1;
3036 const tdata_index: u16 = 2;
3037 const tbss_index: u16 = 3;
3038 const x_symbol: u32 = 5;
3039 const y_symbol: u32 = 6;
3040 const object = try buildObject(allocator, .{
3041 .sections = &.{
3042 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3043 Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3044 Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3045 },
3046 .symbols = &.{
3047 Symbol.section(text_index),
3048 Symbol.section(tdata_index),
3049 Symbol.section(tbss_index),
3050 Symbol.function("_start", text_index, 0, text.len),
3051 Symbol.tlsObject("x", tdata_index, 0, 4),
3052 Symbol.tlsObject("y", tbss_index, 0, 4),
3053 },
3054 .relocations = &.{
3055 Relocation.x86_64(text_index, 4, x_symbol, .TPOFF32, 0),
3056 Relocation.x86_64(text_index, 12, y_symbol, .TPOFF32, 0),
3057 },
3058 });
3059 defer allocator.free(object);
3060
3061 var linked = try linkExecutable(allocator, &.{.{ .name = "tls.o", .bytes = object }}, .{ .incremental_mode = .off });
3062 defer linked.deinit(allocator);
3063
3064 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3065 const text_offset: usize = @intCast(text_record.file_offset);
3066 try std.testing.expectEqual(@as(i32, -8), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 4))));
3067 try std.testing.expectEqual(@as(i32, -4), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 12))));
3068
3069 const tdata_header = try metadata.linkedSectionByName(linked.bytes, ".tdata");
3070 const tbss_header = try metadata.linkedSectionByName(linked.bytes, ".tbss");
3071 try std.testing.expect((tdata_header.flags & std.elf.SHF_TLS) != 0);
3072 try std.testing.expect((tbss_header.flags & std.elf.SHF_TLS) != 0);
3073
3074 var saw_tls_header = false;
3075 const phnum = readU16(linked.bytes, 56);
3076 for (0..phnum) |index| {
3077 const offset = ehdr_size + index * phdr_size;
3078 if (readU32(linked.bytes, offset) != std.elf.PT_TLS) continue;
3079 saw_tls_header = true;
3080 try std.testing.expectEqual(@as(u64, 4), readU64(linked.bytes, offset + 32));
3081 try std.testing.expectEqual(@as(u64, 8), readU64(linked.bytes, offset + 40));
3082 }
3083 try std.testing.expect(saw_tls_header);
3084 }
3085
3086 test "ELF linker relaxes x86-64 initial-exec TLS for static executable" {
3087 const allocator = std.testing.allocator;
3088 const text = [_]u8{
3089 0x48, 0x8b, 0x05,
3090 0, 0, 0,
3091 0, 0x4c, 0x8b,
3092 0x05, 0, 0,
3093 0, 0, 0xc3,
3094 };
3095 const tdata = [_]u8{ 7, 0, 0, 0 };
3096 const text_index: u16 = 1;
3097 const tdata_index: u16 = 2;
3098 const x_symbol: u32 = 5;
3099 const object = try buildObject(allocator, .{
3100 .sections = &.{
3101 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3102 Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3103 Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3104 },
3105 .symbols = &.{
3106 Symbol.section(text_index),
3107 Symbol.section(tdata_index),
3108 Symbol.section(3),
3109 Symbol.function("_start", text_index, 0, text.len),
3110 Symbol.tlsObject("x", tdata_index, 0, 4),
3111 },
3112 .relocations = &.{
3113 Relocation.x86_64(text_index, 3, x_symbol, .GOTTPOFF, -4),
3114 Relocation.x86_64(text_index, 10, x_symbol, .GOTTPOFF, -4),
3115 },
3116 });
3117 defer allocator.free(object);
3118
3119 var linked = try linkExecutable(allocator, &.{.{ .name = "tls-initial-exec.o", .bytes = object }}, .{ .incremental_mode = .off });
3120 defer linked.deinit(allocator);
3121
3122 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3123 const text_offset: usize = @intCast(text_record.file_offset);
3124 const rax_expected = [_]u8{ 0x48, 0xc7, 0xc0, 0xf8, 0xff, 0xff, 0xff };
3125 const r8_expected = [_]u8{ 0x49, 0xc7, 0xc0, 0xf8, 0xff, 0xff, 0xff };
3126 try std.testing.expectEqualSlices(u8, &rax_expected, linked.bytes[text_offset..][0..rax_expected.len]);
3127 try std.testing.expectEqualSlices(u8, &r8_expected, linked.bytes[text_offset + 7 ..][0..r8_expected.len]);
3128 try std.testing.expect(metadata.manifestSection(linked.manifest, ".got") == null);
3129 }
3130
3131 test "ELF linker relaxes x86-64 TLS dynamic models for static executable" {
3132 const allocator = std.testing.allocator;
3133 const text = [_]u8{
3134 0x66, 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00,
3135 0x66, 0x66, 0x48, 0xe8, 0x00, 0x00, 0x00, 0x00,
3136 0x48, 0x8d, 0x3d, 0x00, 0x00, 0x00, 0x00, 0xe8,
3137 0x00, 0x00, 0x00, 0x00, 0x8b, 0x80, 0x00, 0x00,
3138 0x00, 0x00, 0xc3,
3139 };
3140 const tdata = [_]u8{ 7, 0, 0, 0 };
3141 const text_index: u16 = 1;
3142 const tdata_index: u16 = 2;
3143 const x_symbol: u32 = 4;
3144 const tls_get_addr: u32 = 5;
3145 const object = try buildObject(allocator, .{
3146 .sections = &.{
3147 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3148 Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3149 },
3150 .symbols = &.{
3151 Symbol.section(text_index),
3152 Symbol.section(tdata_index),
3153 Symbol.function("_start", text_index, 0, text.len),
3154 Symbol.tlsObject("x", tdata_index, 0, 4),
3155 Symbol.undefinedFunction("__tls_get_addr"),
3156 },
3157 .relocations = &.{
3158 Relocation.x86_64(text_index, 4, x_symbol, .TLSGD, -4),
3159 Relocation.x86_64(text_index, 12, tls_get_addr, .PLT32, -4),
3160 Relocation.x86_64(text_index, 19, x_symbol, .TLSLD, -4),
3161 Relocation.x86_64(text_index, 24, tls_get_addr, .PLT32, -4),
3162 Relocation.x86_64(text_index, 30, x_symbol, .DTPOFF32, 0),
3163 },
3164 });
3165 defer allocator.free(object);
3166
3167 var linked = try linkExecutable(allocator, &.{.{ .name = "tls-dynamic.o", .bytes = object }}, .{ .incremental_mode = .off });
3168 defer linked.deinit(allocator);
3169
3170 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3171 const text_offset: usize = @intCast(text_record.file_offset);
3172 const gd_expected = [_]u8{
3173 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00,
3174 0x00, 0x48, 0x8d, 0x80, 0xfc, 0xff, 0xff, 0xff,
3175 };
3176 const ld_expected = [_]u8{
3177 0x66, 0x66, 0x66, 0x64, 0x48, 0x8b, 0x04, 0x25,
3178 0x00, 0x00, 0x00, 0x00,
3179 };
3180 try std.testing.expectEqualSlices(u8, &gd_expected, linked.bytes[text_offset..][0..gd_expected.len]);
3181 try std.testing.expectEqualSlices(u8, &ld_expected, linked.bytes[text_offset + 16 ..][0..ld_expected.len]);
3182 try std.testing.expectEqual(@as(i32, -4), @as(i32, @bitCast(readU32(linked.bytes, text_offset + 30))));
3183 }
3184
3185 test "ELF incremental manifest distinguishes TLS file payloads from TBSS memory" {
3186 const allocator = std.testing.allocator;
3187 const text = [_]u8{0xc3};
3188 const tdata = [_]u8{ 7, 0, 0, 0 };
3189 const text_index: u16 = 1;
3190 const tdata_index: u16 = 2;
3191 const tbss_index: u16 = 3;
3192 const object = try buildObject(allocator, .{
3193 .sections = &.{
3194 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
3195 Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3196 Section.nobits(".tbss", 4, std.elf.SHF_WRITE | std.elf.SHF_TLS, 4),
3197 },
3198 .symbols = &.{
3199 Symbol.section(text_index),
3200 Symbol.section(tdata_index),
3201 Symbol.section(tbss_index),
3202 Symbol.function("_start", text_index, 0, text.len),
3203 Symbol.tlsObject("x", tdata_index, 0, 4),
3204 Symbol.tlsObject("y", tbss_index, 0, 4),
3205 },
3206 });
3207 defer allocator.free(object);
3208
3209 var linked = try linkExecutable(allocator, &.{.{ .name = "tls.o", .bytes = object }}, .{});
3210 defer linked.deinit(allocator);
3211
3212 const tdata_contribution = metadata.manifestContribution(linked.manifest, "tls.o", .section, ".tdata", tdata_index) orelse return error.MissingSection;
3213 const tbss_contribution = metadata.manifestContribution(linked.manifest, "tls.o", .section, ".tbss", tbss_index) orelse return error.MissingSection;
3214 try std.testing.expectEqual(@as(u64, tdata.len), tdata_contribution.file_size);
3215 try std.testing.expectEqual(@as(u64, 0), tbss_contribution.file_size);
3216
3217 const tdata_plan = linked.manifest.planContributionReplacement(&.{
3218 .{ .input_name = "tls.o", .input_index = 0, .kind = .section, .name = ".tdata", .ordinal = tdata_index, .size = tdata.len, .alignment = 4 },
3219 });
3220 try std.testing.expectEqual(incremental.PatchDecision.in_place, tdata_plan.decision);
3221
3222 const tbss_plan = linked.manifest.planContributionReplacement(&.{
3223 .{ .input_name = "tls.o", .input_index = 0, .kind = .section, .name = ".tbss", .ordinal = tbss_index, .size = 4, .alignment = 4 },
3224 });
3225 try std.testing.expectEqual(incremental.PatchDecision.in_place, tbss_plan.decision);
3226 }
3227
3228 test "ELF linker allocates common symbols in bss" {
3229 const allocator = std.testing.allocator;
3230 const text = [_]u8{
3231 0x48, 0xb8,
3232 0, 0,
3233 0, 0,
3234 0, 0,
3235 0, 0,
3236 0xc3,
3237 };
3238 const text_index: u16 = 1;
3239 const scratch_symbol: u32 = 3;
3240 const object = try buildObject(allocator, .{
3241 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3242 .symbols = &.{
3243 Symbol.section(text_index),
3244 Symbol.function("_start", text_index, 0, text.len),
3245 Symbol.commonObject("scratch", 16, 24),
3246 },
3247 .relocations = &.{Relocation.x86_64(1, 2, scratch_symbol, .@"64", 0)},
3248 });
3249 defer allocator.free(object);
3250
3251 const inputs = [_]model.Input{.{ .name = "common.o", .bytes = object }};
3252 var linked = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off });
3253 defer linked.deinit(allocator);
3254 var manifest_linked = try linkExecutable(allocator, &inputs, .{});
3255 defer manifest_linked.deinit(allocator);
3256
3257 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3258 const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
3259 try std.testing.expectEqual(@as(u64, 24), bss_record.size);
3260 try std.testing.expectEqual(@as(u64, 32), bss_record.reserved_size);
3261 try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
3262 try std.testing.expectEqual(@as(u64, 0), bss_record.address % 16);
3263 try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
3264
3265 const manifest_bss_record = metadata.manifestSection(manifest_linked.manifest, ".bss") orelse return error.MissingSection;
3266 const common_contribution = metadata.manifestContribution(manifest_linked.manifest, "common.o", .common_symbol, "scratch", scratch_symbol) orelse return error.MissingSection;
3267 try std.testing.expectEqualStrings(".bss", manifest_linked.manifest.string(common_contribution.output_section_name_id));
3268 try std.testing.expectEqual(manifest_bss_record.address, common_contribution.address);
3269 try std.testing.expectEqual(@as(u64, 24), common_contribution.size);
3270 try std.testing.expectEqual(@as(u64, 0), common_contribution.file_size);
3271 try std.testing.expect(common_contribution.reserved_size > common_contribution.size);
3272 try std.testing.expectEqual(@as(u64, 16), common_contribution.alignment);
3273
3274 const common_plan = manifest_linked.manifest.planContributionReplacement(&.{
3275 .{ .input_name = "common.o", .input_index = 0, .kind = .common_symbol, .name = "scratch", .ordinal = scratch_symbol, .size = 24, .alignment = 16 },
3276 });
3277 try std.testing.expectEqual(incremental.PatchDecision.in_place, common_plan.decision);
3278
3279 const bss_header = try metadata.linkedSectionByName(linked.bytes, ".bss");
3280 try std.testing.expectEqual(std.elf.SHT_NOBITS, bss_header.section_type);
3281
3282 const bss_load = try metadata.loadProgramHeaderByAddress(linked.bytes, bss_record.address);
3283 try std.testing.expectEqual(@as(u64, 0), bss_load.file_size);
3284 try std.testing.expectEqual(bss_record.reserved_size, bss_load.memory_size);
3285 }
3286
3287 test "ELF linker coalesces common symbols by size" {
3288 const allocator = std.testing.allocator;
3289 const text = [_]u8{
3290 0x48, 0xb8,
3291 0, 0,
3292 0, 0,
3293 0, 0,
3294 0, 0,
3295 0xc3,
3296 };
3297 const text_index: u16 = 1;
3298 const caller_scratch: u32 = 3;
3299 const caller_object = try buildObject(allocator, .{
3300 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3301 .symbols = &.{
3302 Symbol.section(text_index),
3303 Symbol.function("_start", text_index, 0, text.len),
3304 Symbol.commonObject("scratch", 8, 8),
3305 },
3306 .relocations = &.{Relocation.x86_64(1, 2, caller_scratch, .@"64", 0)},
3307 });
3308 defer allocator.free(caller_object);
3309
3310 const storage_object = try buildObject(allocator, .{
3311 .sections = &.{},
3312 .symbols = &.{Symbol.commonObject("scratch", 16, 32)},
3313 });
3314 defer allocator.free(storage_object);
3315
3316 var linked = try linkExecutable(allocator, &.{
3317 .{ .name = "caller.o", .bytes = caller_object },
3318 .{ .name = "storage.o", .bytes = storage_object },
3319 }, .{ .incremental_mode = .off });
3320 defer linked.deinit(allocator);
3321
3322 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3323 const bss_record = metadata.manifestSection(linked.manifest, ".bss") orelse return error.MissingSection;
3324 try std.testing.expectEqual(@as(u64, 32), bss_record.size);
3325 try std.testing.expectEqual(@as(u64, 16), bss_record.alignment);
3326 try std.testing.expectEqual(bss_record.address, std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little));
3327 }
3328
3329 test "ELF linker lets strong definitions override common symbols" {
3330 const allocator = std.testing.allocator;
3331
3332 const text = [_]u8{
3333 0x48, 0xb8,
3334 0, 0,
3335 0, 0,
3336 0, 0,
3337 0, 0,
3338 0xc3,
3339 };
3340 const text_index: u16 = 1;
3341 const slot_ref: u32 = 3;
3342 const caller_object = try buildObject(allocator, .{
3343 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3344 .symbols = &.{
3345 Symbol.section(text_index),
3346 Symbol.function("_start", text_index, 0, text.len),
3347 Symbol.undefinedObject("shared_slot"),
3348 },
3349 .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3350 });
3351 defer allocator.free(caller_object);
3352
3353 const common_symbol: u32 = 1;
3354 const common_object = try buildObject(allocator, .{
3355 .sections = &.{},
3356 .symbols = &.{Symbol.commonObject("shared_slot", 32, 128)},
3357 });
3358 defer allocator.free(common_object);
3359
3360 const data = [_]u8{ 1, 2, 3, 4 };
3361 const data_index: u16 = 1;
3362 const strong_object = try buildObject(allocator, .{
3363 .sections = &.{Section.progbits(".data.shared", &data, std.elf.SHF_WRITE, 4)},
3364 .symbols = &.{ Symbol.section(data_index), Symbol.object("shared_slot", data_index, 0, data.len) },
3365 });
3366 defer allocator.free(strong_object);
3367
3368 var linked = try linkExecutable(allocator, &.{
3369 .{ .name = "caller.o", .bytes = caller_object },
3370 .{ .name = "common.o", .bytes = common_object },
3371 .{ .name = "strong.o", .bytes = strong_object },
3372 }, .{});
3373 defer linked.deinit(allocator);
3374
3375 const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", text_index) orelse return error.MissingSection;
3376 const strong_contribution = metadata.manifestContribution(linked.manifest, "strong.o", .section, ".data.shared", data_index) orelse return error.MissingSection;
3377 try std.testing.expect(metadata.manifestContribution(linked.manifest, "common.o", .common_symbol, "shared_slot", common_symbol) == null);
3378 try std.testing.expect(metadata.manifestSection(linked.manifest, ".bss") == null);
3379
3380 const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
3381 try std.testing.expectEqual(strong_contribution.address, loaded);
3382 }
3383
3384 test "ELF linker lazily extracts archive members and rescans the same archive" {
3385 const allocator = std.testing.allocator;
3386
3387 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3388 const caller_text_index: u16 = 1;
3389 const callee_symbol: u32 = 3;
3390 const caller_object = try buildObject(allocator, .{
3391 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3392 .symbols = &.{
3393 Symbol.section(caller_text_index),
3394 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3395 Symbol.undefinedFunction("callee"),
3396 },
3397 .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3398 });
3399 defer allocator.free(caller_object);
3400
3401 const unused_text = [_]u8{0xc3};
3402 const unused_text_index: u16 = 1;
3403 const unused_object = try buildObject(allocator, .{
3404 .sections = &.{Section.progbits(".text", &unused_text, std.elf.SHF_EXECINSTR, 16)},
3405 .symbols = &.{
3406 Symbol.section(unused_text_index),
3407 Symbol.function("_start", unused_text_index, 0, unused_text.len),
3408 },
3409 });
3410 defer allocator.free(unused_object);
3411
3412 const helper_text = [_]u8{0xc3};
3413 const helper_text_index: u16 = 1;
3414 const helper_object = try buildObject(allocator, .{
3415 .sections = &.{Section.progbits(".text", &helper_text, std.elf.SHF_EXECINSTR, 16)},
3416 .symbols = &.{
3417 Symbol.section(helper_text_index),
3418 Symbol.function("helper", helper_text_index, 0, helper_text.len),
3419 },
3420 });
3421 defer allocator.free(helper_object);
3422
3423 const callee_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3424 const callee_text_index: u16 = 1;
3425 const helper_symbol: u32 = 3;
3426 const callee_object = try buildObject(allocator, .{
3427 .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
3428 .symbols = &.{
3429 Symbol.section(callee_text_index),
3430 Symbol.function("callee", callee_text_index, 0, callee_text.len),
3431 Symbol.undefinedFunction("helper"),
3432 },
3433 .relocations = &.{Relocation.x86_64(1, 1, helper_symbol, .PLT32, -4)},
3434 });
3435 defer allocator.free(callee_object);
3436
3437 const archive_bytes = try archive.build(allocator, &.{
3438 .{ .name = "unused.o", .bytes = unused_object },
3439 .{ .name = "helper.o", .bytes = helper_object },
3440 .{ .name = "callee.o", .bytes = callee_object },
3441 });
3442 defer allocator.free(archive_bytes);
3443
3444 var linked = try linkExecutable(allocator, &.{
3445 .{ .name = "caller.o", .bytes = caller_object },
3446 .{ .name = "libcall.a", .bytes = archive_bytes },
3447 }, .{ .incremental_mode = .off });
3448 defer linked.deinit(allocator);
3449
3450 try std.testing.expectEqual(@as(usize, 2), linked.manifest.inputs.len);
3451 try std.testing.expectEqualStrings("libcall.a", linked.manifest.string(linked.manifest.inputs[1].name_id));
3452 try std.testing.expectEqual(@as(u64, archive_bytes.len), linked.manifest.inputs[1].size);
3453
3454 const text = linked.manifest.sections[0];
3455 try std.testing.expectEqual(@as(u64, 33), text.size);
3456 const text_file_offset: usize = @intCast(text.file_offset);
3457 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3458 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
3459 }
3460
3461 test "ELF linker extracts archive members that define GNU unique symbols" {
3462 const allocator = std.testing.allocator;
3463
3464 const caller_text = [_]u8{
3465 0x48, 0xb8,
3466 0, 0,
3467 0, 0,
3468 0, 0,
3469 0, 0,
3470 0xc3,
3471 };
3472 const caller_text_index: u16 = 1;
3473 const slot_ref: u32 = 3;
3474 const caller_object = try buildObject(allocator, .{
3475 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3476 .symbols = &.{
3477 Symbol.section(caller_text_index),
3478 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3479 Symbol.undefinedObject("unique_slot"),
3480 },
3481 .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3482 });
3483 defer allocator.free(caller_object);
3484
3485 const data = [_]u8{ 9, 8, 7, 6 };
3486 const data_index: u16 = 1;
3487 const unique_object = try buildObject(allocator, .{
3488 .sections = &.{Section.progbits(".data.unique", &data, std.elf.SHF_WRITE, 4)},
3489 .symbols = &.{ Symbol.section(data_index), Symbol.gnuUniqueObject("unique_slot", data_index, 0, data.len) },
3490 });
3491 defer allocator.free(unique_object);
3492
3493 const archive_bytes = try archive.build(allocator, &.{
3494 .{ .name = "unique.o", .bytes = unique_object, .symbols = &.{"unique_slot"} },
3495 });
3496 defer allocator.free(archive_bytes);
3497
3498 var linked = try linkExecutable(allocator, &.{
3499 .{ .name = "caller.o", .bytes = caller_object },
3500 .{ .name = "libunique.a", .bytes = archive_bytes },
3501 }, .{});
3502 defer linked.deinit(allocator);
3503
3504 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3505 const unique_contribution = metadata.manifestContribution(linked.manifest, "unique.o", .section, ".data.unique", data_index) orelse return error.MissingSection;
3506 const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little);
3507 try std.testing.expectEqual(unique_contribution.address, loaded);
3508 }
3509
3510 test "ELF linker coalesces duplicate GNU unique definitions" {
3511 const allocator = std.testing.allocator;
3512
3513 const caller_text = [_]u8{
3514 0x48, 0xb8,
3515 0, 0,
3516 0, 0,
3517 0, 0,
3518 0, 0,
3519 0xc3,
3520 };
3521 const caller_text_index: u16 = 1;
3522 const slot_ref: u32 = 3;
3523 const caller_object = try buildObject(allocator, .{
3524 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3525 .symbols = &.{
3526 Symbol.section(caller_text_index),
3527 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3528 Symbol.undefinedObject("unique_slot"),
3529 },
3530 .relocations = &.{Relocation.x86_64(1, 2, slot_ref, .@"64", 0)},
3531 });
3532 defer allocator.free(caller_object);
3533
3534 const first_data = [_]u8{ 1, 0, 0, 0 };
3535 const first_data_index: u16 = 1;
3536 const first_object = try buildObject(allocator, .{
3537 .sections = &.{Section.progbits(".data.first_unique", &first_data, std.elf.SHF_WRITE, 4)},
3538 .symbols = &.{
3539 Symbol.section(first_data_index),
3540 Symbol.gnuUniqueObject("unique_slot", first_data_index, 0, first_data.len),
3541 },
3542 });
3543 defer allocator.free(first_object);
3544
3545 const second_data = [_]u8{ 2, 0, 0, 0 };
3546 const second_data_index: u16 = 1;
3547 const second_object = try buildObject(allocator, .{
3548 .sections = &.{Section.progbits(".data.second_unique", &second_data, std.elf.SHF_WRITE, 4)},
3549 .symbols = &.{
3550 Symbol.section(second_data_index),
3551 Symbol.gnuUniqueObject("unique_slot", second_data_index, 0, second_data.len),
3552 },
3553 });
3554 defer allocator.free(second_object);
3555
3556 var linked = try linkExecutable(allocator, &.{
3557 .{ .name = "caller.o", .bytes = caller_object },
3558 .{ .name = "first.o", .bytes = first_object },
3559 .{ .name = "second.o", .bytes = second_object },
3560 }, .{});
3561 defer linked.deinit(allocator);
3562
3563 const text_record = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3564 const first_contribution = metadata.manifestContribution(linked.manifest, "first.o", .section, ".data.first_unique", first_data_index) orelse return error.MissingSection;
3565 _ = metadata.manifestContribution(linked.manifest, "second.o", .section, ".data.second_unique", second_data_index) orelse return error.MissingSection;
3566 const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(text_record.file_offset)) + 2 ..][0..8], .little);
3567 try std.testing.expectEqual(first_contribution.address, loaded);
3568 }
3569
3570 test "ELF linker skips stale archive candidates after a symbol is resolved" {
3571 const allocator = std.testing.allocator;
3572
3573 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3574 const caller_text_index: u16 = 1;
3575 const callee_symbol: u32 = 3;
3576 const caller_object = try buildObject(allocator, .{
3577 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3578 .symbols = &.{
3579 Symbol.section(caller_text_index),
3580 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3581 Symbol.undefinedFunction("callee"),
3582 },
3583 .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3584 });
3585 defer allocator.free(caller_object);
3586
3587 const first_text = [_]u8{0xc3};
3588 const first_text_index: u16 = 1;
3589 const first_object = try buildObject(allocator, .{
3590 .sections = &.{Section.progbits(".text", &first_text, std.elf.SHF_EXECINSTR, 16)},
3591 .symbols = &.{
3592 Symbol.section(first_text_index),
3593 Symbol.function("callee", first_text_index, 0, first_text.len),
3594 },
3595 });
3596 defer allocator.free(first_object);
3597
3598 const duplicate_text = [_]u8{ 0x90, 0x90, 0xc3 };
3599 const duplicate_text_index: u16 = 1;
3600 const duplicate_object = try buildObject(allocator, .{
3601 .sections = &.{Section.progbits(".text", &duplicate_text, std.elf.SHF_EXECINSTR, 16)},
3602 .symbols = &.{
3603 Symbol.section(duplicate_text_index),
3604 Symbol.function("callee", duplicate_text_index, 0, duplicate_text.len),
3605 },
3606 });
3607 defer allocator.free(duplicate_object);
3608
3609 const archive_bytes = try archive.build(allocator, &.{
3610 .{ .name = "first.o", .bytes = first_object },
3611 .{ .name = "duplicate.o", .bytes = duplicate_object },
3612 });
3613 defer allocator.free(archive_bytes);
3614
3615 var linked = try linkExecutable(allocator, &.{
3616 .{ .name = "caller.o", .bytes = caller_object },
3617 .{ .name = "libcallee.a", .bytes = archive_bytes },
3618 }, .{ .incremental_mode = .off });
3619 defer linked.deinit(allocator);
3620
3621 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3622 const expected_size = alignForward(caller_text.len, 16) + first_text.len;
3623 try std.testing.expectEqual(@as(u64, expected_size), text.size);
3624 }
3625
3626 test "ELF linker uses archive symbol indexes without parsing unreferenced members" {
3627 const allocator = std.testing.allocator;
3628
3629 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3630 const caller_text_index: u16 = 1;
3631 const needed_symbol: u32 = 3;
3632 const caller_object = try buildObject(allocator, .{
3633 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3634 .symbols = &.{
3635 Symbol.section(caller_text_index),
3636 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3637 Symbol.undefinedFunction("needed"),
3638 },
3639 .relocations = &.{Relocation.x86_64(1, 1, needed_symbol, .PLT32, -4)},
3640 });
3641 defer allocator.free(caller_object);
3642
3643 const needed_text = [_]u8{0xc3};
3644 const needed_text_index: u16 = 1;
3645 const needed_object = try buildObject(allocator, .{
3646 .sections = &.{Section.progbits(".text", &needed_text, std.elf.SHF_EXECINSTR, 16)},
3647 .symbols = &.{
3648 Symbol.section(needed_text_index),
3649 Symbol.function("needed", needed_text_index, 0, needed_text.len),
3650 },
3651 });
3652 defer allocator.free(needed_object);
3653
3654 const unused_coff = coffHeaderBytes();
3655 const archive_bytes = try archive.build(allocator, &.{
3656 .{ .name = "unused.obj", .bytes = &unused_coff, .symbols = &.{"unused"} },
3657 .{ .name = "needed.o", .bytes = needed_object, .symbols = &.{"needed"} },
3658 });
3659 defer allocator.free(archive_bytes);
3660
3661 var linked = try linkExecutable(allocator, &.{
3662 .{ .name = "caller.o", .bytes = caller_object },
3663 .{ .name = "libneeded.a", .bytes = archive_bytes },
3664 }, .{ .incremental_mode = .off });
3665 defer linked.deinit(allocator);
3666
3667 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3668 const expected_size = alignForward(caller_text.len, 16) + needed_text.len;
3669 try std.testing.expectEqual(@as(u64, expected_size), text.size);
3670 }
3671
3672 test "ELF linker diagnoses extracted archive members with mismatched formats" {
3673 const allocator = std.testing.allocator;
3674
3675 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3676 const caller_text_index: u16 = 1;
3677 const needed_symbol: u32 = 3;
3678 const caller_object = try buildObject(allocator, .{
3679 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3680 .symbols = &.{
3681 Symbol.section(caller_text_index),
3682 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3683 Symbol.undefinedFunction("needed"),
3684 },
3685 .relocations = &.{Relocation.x86_64(1, 1, needed_symbol, .PLT32, -4)},
3686 });
3687 defer allocator.free(caller_object);
3688
3689 const needed_coff = coffHeaderBytes();
3690 const archive_bytes = try archive.build(allocator, &.{
3691 .{ .name = "needed.obj", .bytes = &needed_coff, .symbols = &.{"needed"} },
3692 });
3693 defer allocator.free(archive_bytes);
3694
3695 var diagnostics: model.Diagnostics = .{};
3696 try std.testing.expectError(
3697 error.UnsupportedFormat,
3698 linkExecutable(allocator, &.{
3699 .{ .name = "caller.o", .bytes = caller_object },
3700 .{ .name = "libmixed.a", .bytes = archive_bytes },
3701 }, .{ .diagnostics = &diagnostics, .incremental_mode = .off }),
3702 );
3703
3704 const unsupported = diagnostics.first_unsupported_input_format orelse return error.ExpectedUnsupportedInputFormatDiagnostic;
3705 try std.testing.expectEqual(model.ObjectFormat.coff, unsupported.input_format);
3706 try std.testing.expectEqual(model.ObjectFormat.elf, unsupported.target_format);
3707 try std.testing.expectEqualStrings("libmixed.a", unsupported.input_name);
3708 try std.testing.expectEqualStrings("needed.obj", unsupported.member_name orelse return error.ExpectedUnsupportedInputFormatDiagnostic);
3709
3710 const failure = diagnostics.linkFailure(error.UnsupportedFormat);
3711 const reported = switch (failure) {
3712 .unsupported_input_format => |reported| reported,
3713 else => return error.ExpectedUnsupportedInputFormatDiagnostic,
3714 };
3715 try std.testing.expectEqual(model.ObjectFormat.coff, reported.input_format);
3716 try std.testing.expectEqual(model.ObjectFormat.elf, reported.target_format);
3717 try std.testing.expectEqualStrings("libmixed.a", reported.input_name);
3718 try std.testing.expectEqualStrings("needed.obj", reported.member_name orelse return error.ExpectedUnsupportedInputFormatDiagnostic);
3719 }
3720
3721 test "ELF linker extracts indexed archive fanout members" {
3722 const allocator = std.testing.allocator;
3723
3724 const caller_text = [_]u8{
3725 0xe8, 0, 0, 0, 0,
3726 0xe8, 0, 0, 0, 0,
3727 0xc3,
3728 };
3729 const caller_text_index: u16 = 1;
3730 const first_symbol: u32 = 3;
3731 const second_symbol: u32 = 4;
3732 const caller_object = try buildObject(allocator, .{
3733 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3734 .symbols = &.{
3735 Symbol.section(caller_text_index),
3736 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3737 Symbol.undefinedFunction("first"),
3738 Symbol.undefinedFunction("second"),
3739 },
3740 .relocations = &.{
3741 Relocation.x86_64(1, 1, first_symbol, .PLT32, -4),
3742 Relocation.x86_64(1, 6, second_symbol, .PLT32, -4),
3743 },
3744 });
3745 defer allocator.free(caller_object);
3746
3747 const function_text = [_]u8{0xc3};
3748 const first_text_index: u16 = 1;
3749 const first_object = try buildObject(allocator, .{
3750 .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3751 .symbols = &.{
3752 Symbol.section(first_text_index),
3753 Symbol.function("first", first_text_index, 0, function_text.len),
3754 },
3755 });
3756 defer allocator.free(first_object);
3757
3758 const second_text_index: u16 = 1;
3759 const second_object = try buildObject(allocator, .{
3760 .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3761 .symbols = &.{
3762 Symbol.section(second_text_index),
3763 Symbol.function("second", second_text_index, 0, function_text.len),
3764 },
3765 });
3766 defer allocator.free(second_object);
3767
3768 const malformed_elf = std.elf.MAGIC ++ "not enough header bytes";
3769 const archive_bytes = try archive.build(allocator, &.{
3770 .{ .name = "unused.o", .bytes = malformed_elf, .symbols = &.{"unused"} },
3771 .{ .name = "first.o", .bytes = first_object, .symbols = &.{"first"} },
3772 .{ .name = "second.o", .bytes = second_object, .symbols = &.{"second"} },
3773 });
3774 defer allocator.free(archive_bytes);
3775
3776 var linked = try linkExecutable(allocator, &.{
3777 .{ .name = "caller.o", .bytes = caller_object },
3778 .{ .name = "libfanout.a", .bytes = archive_bytes },
3779 }, .{ .incremental_mode = .off });
3780 defer linked.deinit(allocator);
3781
3782 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3783 try std.testing.expectEqual(@as(u64, 33), text.size);
3784 const text_file_offset: usize = @intCast(text.file_offset);
3785 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3786 try std.testing.expectEqual(@as(i32, 22), std.mem.readInt(i32, linked.bytes[text_file_offset + 6 ..][0..4], .little));
3787 }
3788
3789 test "ELF linker preserves wide indexed archive fanout members" {
3790 const allocator = std.testing.allocator;
3791 const function_names = [_][]const u8{
3792 "fanout_0",
3793 "fanout_1",
3794 "fanout_2",
3795 "fanout_3",
3796 "fanout_4",
3797 "fanout_5",
3798 "fanout_6",
3799 "fanout_7",
3800 };
3801 const member_names = [_][]const u8{
3802 "fanout_0.o",
3803 "fanout_1.o",
3804 "fanout_2.o",
3805 "fanout_3.o",
3806 "fanout_4.o",
3807 "fanout_5.o",
3808 "fanout_6.o",
3809 "fanout_7.o",
3810 };
3811 const function_count = function_names.len;
3812
3813 var caller_text: [function_count * 5 + 1]u8 = undefined;
3814 for (0..function_count) |index| {
3815 const call_offset = index * 5;
3816 caller_text[call_offset..][0..5].* = .{ 0xe8, 0, 0, 0, 0 };
3817 }
3818 caller_text[function_count * 5] = 0xc3;
3819 const caller_text_index: u16 = 1;
3820 var caller_symbols: [function_count + 2]Symbol = undefined;
3821 caller_symbols[0] = Symbol.section(caller_text_index);
3822 caller_symbols[1] = Symbol.function("_start", caller_text_index, 0, caller_text.len);
3823 var caller_relocations: [function_count]Relocation = undefined;
3824 for (function_names, 0..) |name, index| {
3825 const symbol: u32 = @intCast(index + 3);
3826 caller_symbols[index + 2] = Symbol.undefinedFunction(name);
3827 const offset = index * 5 + 1;
3828 caller_relocations[index] = Relocation.x86_64(caller_text_index, offset, symbol, .PLT32, -4);
3829 }
3830 const caller_object = try buildObject(allocator, .{
3831 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3832 .symbols = &caller_symbols,
3833 .relocations = &caller_relocations,
3834 });
3835 defer allocator.free(caller_object);
3836
3837 var member_bytes: [function_count][]u8 = undefined;
3838 var member_symbol_storage: [function_count][1][]const u8 = undefined;
3839 var members: [function_count]archive.BuildMember = undefined;
3840 const function_text = [_]u8{0xc3};
3841 for (function_names, 0..) |name, index| {
3842 const text_index: u16 = 1;
3843 member_bytes[index] = try buildObject(allocator, .{
3844 .sections = &.{Section.progbits(".text", &function_text, std.elf.SHF_EXECINSTR, 16)},
3845 .symbols = &.{ Symbol.section(text_index), Symbol.function(name, text_index, 0, function_text.len) },
3846 });
3847 member_symbol_storage[index][0] = name;
3848 members[index] = .{
3849 .name = member_names[index],
3850 .bytes = member_bytes[index],
3851 .symbols = member_symbol_storage[index][0..],
3852 };
3853 }
3854 defer for (member_bytes) |bytes| allocator.free(bytes);
3855
3856 const archive_bytes = try archive.build(allocator, &members);
3857 defer allocator.free(archive_bytes);
3858
3859 var linked = try linkExecutable(allocator, &.{
3860 .{ .name = "caller.o", .bytes = caller_object },
3861 .{ .name = "libfanout.a", .bytes = archive_bytes },
3862 }, .{ .incremental_mode = .off, .max_link_jobs = 2 });
3863 defer linked.deinit(allocator);
3864
3865 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3866 const expected_size = alignForward(caller_text.len, 16) + (function_count - 1) * 16 + function_text.len;
3867 try std.testing.expectEqual(@as(u64, expected_size), text.size);
3868 }
3869
3870 test "ELF linker resolves archive members referenced by later objects" {
3871 const allocator = std.testing.allocator;
3872
3873 const callee_text = [_]u8{0xc3};
3874 const callee_text_index: u16 = 1;
3875 const callee_object = try buildObject(allocator, .{
3876 .sections = &.{Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16)},
3877 .symbols = &.{
3878 Symbol.section(callee_text_index),
3879 Symbol.function("callee", callee_text_index, 0, callee_text.len),
3880 },
3881 });
3882 defer allocator.free(callee_object);
3883
3884 const archive_bytes = try archive.build(allocator, &.{.{ .name = "callee.o", .bytes = callee_object }});
3885 defer allocator.free(archive_bytes);
3886
3887 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3888 const caller_text_index: u16 = 1;
3889 const callee_symbol: u32 = 3;
3890 const caller_object = try buildObject(allocator, .{
3891 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3892 .symbols = &.{
3893 Symbol.section(caller_text_index),
3894 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3895 Symbol.undefinedFunction("callee"),
3896 },
3897 .relocations = &.{Relocation.x86_64(1, 1, callee_symbol, .PLT32, -4)},
3898 });
3899 defer allocator.free(caller_object);
3900
3901 var linked = try linkExecutable(allocator, &.{
3902 .{ .name = "libcallee.a", .bytes = archive_bytes },
3903 .{ .name = "caller.o", .bytes = caller_object },
3904 }, .{ .incremental_mode = .off });
3905 defer linked.deinit(allocator);
3906
3907 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3908 const text_file_offset: usize = @intCast(text.file_offset);
3909 try std.testing.expectEqual(@as(u64, 17), text.size);
3910 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3911 }
3912
3913 test "ELF linker carries archive unresolved symbols across later archives" {
3914 const allocator = std.testing.allocator;
3915
3916 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3917 const caller_text_index: u16 = 1;
3918 const first_symbol: u32 = 3;
3919 const caller_object = try buildObject(allocator, .{
3920 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
3921 .symbols = &.{
3922 Symbol.section(caller_text_index),
3923 Symbol.function("_start", caller_text_index, 0, caller_text.len),
3924 Symbol.undefinedFunction("first"),
3925 },
3926 .relocations = &.{Relocation.x86_64(1, 1, first_symbol, .PLT32, -4)},
3927 });
3928 defer allocator.free(caller_object);
3929
3930 const first_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3931 const first_text_index: u16 = 1;
3932 const second_symbol: u32 = 3;
3933 const first_object = try buildObject(allocator, .{
3934 .sections = &.{Section.progbits(".text", &first_text, std.elf.SHF_EXECINSTR, 16)},
3935 .symbols = &.{
3936 Symbol.section(first_text_index),
3937 Symbol.function("first", first_text_index, 0, first_text.len),
3938 Symbol.undefinedFunction("second"),
3939 },
3940 .relocations = &.{Relocation.x86_64(1, 1, second_symbol, .PLT32, -4)},
3941 });
3942 defer allocator.free(first_object);
3943
3944 const second_text = [_]u8{0xc3};
3945 const second_text_index: u16 = 1;
3946 const second_object = try buildObject(allocator, .{
3947 .sections = &.{Section.progbits(".text", &second_text, std.elf.SHF_EXECINSTR, 16)},
3948 .symbols = &.{
3949 Symbol.section(second_text_index),
3950 Symbol.function("second", second_text_index, 0, second_text.len),
3951 },
3952 });
3953 defer allocator.free(second_object);
3954
3955 const first_archive = try archive.build(allocator, &.{.{ .name = "first.o", .bytes = first_object, .symbols = &.{"first"} }});
3956 defer allocator.free(first_archive);
3957 const second_archive = try archive.build(allocator, &.{.{ .name = "second.o", .bytes = second_object, .symbols = &.{"second"} }});
3958 defer allocator.free(second_archive);
3959
3960 var linked = try linkExecutable(allocator, &.{
3961 .{ .name = "caller.o", .bytes = caller_object },
3962 .{ .name = "libfirst.a", .bytes = first_archive },
3963 .{ .name = "libsecond.a", .bytes = second_archive },
3964 }, .{ .incremental_mode = .off });
3965 defer linked.deinit(allocator);
3966
3967 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
3968 const text_file_offset: usize = @intCast(text.file_offset);
3969 try std.testing.expectEqual(@as(u64, 33), text.size);
3970 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 1 ..][0..4], .little));
3971 try std.testing.expectEqual(@as(i32, 11), std.mem.readInt(i32, linked.bytes[text_file_offset + 17 ..][0..4], .little));
3972 }
3973
3974 test "ELF linker rejects malformed archives" {
3975 try std.testing.expectError(
3976 error.InvalidArchive,
3977 linkExecutable(std.testing.allocator, &.{.{ .name = "bad.a", .bytes = archive.magic ++ "short" }}, .{}),
3978 );
3979 }
3980
3981 test "ELF linker rejects unresolved globals" {
3982 const allocator = std.testing.allocator;
3983 const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
3984 const text_index: u16 = 1;
3985 const missing: u32 = 3;
3986 const object = try buildObject(allocator, .{
3987 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
3988 .symbols = &.{
3989 Symbol.section(text_index),
3990 Symbol.function("_start", text_index, 0, text.len),
3991 Symbol.undefinedFunction("missing"),
3992 },
3993 .relocations = &.{Relocation.x86_64(1, 1, missing, .PLT32, -4)},
3994 });
3995 defer allocator.free(object);
3996
3997 try std.testing.expectError(error.UndefinedSymbol, linkExecutable(allocator, &.{.{ .name = "bad.o", .bytes = object }}, .{}));
3998 }
3999
4000 test "ELF linker permits unresolved weak symbols" {
4001 const allocator = std.testing.allocator;
4002 const text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
4003 const text_index: u16 = 1;
4004 const weak_missing: u32 = 3;
4005 const object = try buildObject(allocator, .{
4006 .sections = &.{Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
4007 .symbols = &.{
4008 Symbol.section(text_index),
4009 Symbol.function("_start", text_index, 0, text.len),
4010 Symbol.weakUndefinedFunction("weak_missing"),
4011 },
4012 .relocations = &.{Relocation.x86_64(1, 1, weak_missing, .PLT32, -4)},
4013 });
4014 defer allocator.free(object);
4015
4016 var linked = try linkExecutable(allocator, &.{.{ .name = "weak.o", .bytes = object }}, .{});
4017 defer linked.deinit(allocator);
4018 try std.testing.expect(linked.bytes.len != 0);
4019 }
4020
4021 test "ELF linker lets strong definitions override weak definitions" {
4022 const allocator = std.testing.allocator;
4023
4024 const caller_text = [_]u8{
4025 0x48, 0xb8,
4026 0, 0,
4027 0, 0,
4028 0, 0,
4029 0, 0,
4030 0xc3,
4031 };
4032 const caller_text_index: u16 = 1;
4033 const hook_ref: u32 = 3;
4034 const caller_object = try buildObject(allocator, .{
4035 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4036 .symbols = &.{
4037 Symbol.section(caller_text_index),
4038 Symbol.function("_start", caller_text_index, 0, caller_text.len),
4039 Symbol.undefinedFunction("weak_hook"),
4040 },
4041 .relocations = &.{Relocation.x86_64(1, 2, hook_ref, .@"64", 0)},
4042 });
4043 defer allocator.free(caller_object);
4044
4045 const weak_text = [_]u8{0xc3};
4046 const weak_index: u16 = 1;
4047 const weak_object = try buildObject(allocator, .{
4048 .sections = &.{Section.progbits(".text.weak", &weak_text, std.elf.SHF_EXECINSTR, 16)},
4049 .symbols = &.{ Symbol.section(weak_index), Symbol.weakFunction("weak_hook", weak_index, 0, weak_text.len) },
4050 });
4051 defer allocator.free(weak_object);
4052
4053 const strong_text = [_]u8{ 0x90, 0xc3 };
4054 const strong_index: u16 = 1;
4055 const strong_object = try buildObject(allocator, .{
4056 .sections = &.{Section.progbits(".text.strong", &strong_text, std.elf.SHF_EXECINSTR, 16)},
4057 .symbols = &.{
4058 Symbol.section(strong_index),
4059 Symbol.function("weak_hook", strong_index, 0, strong_text.len),
4060 },
4061 });
4062 defer allocator.free(strong_object);
4063
4064 var linked = try linkExecutable(allocator, &.{
4065 .{ .name = "caller.o", .bytes = caller_object },
4066 .{ .name = "weak.o", .bytes = weak_object },
4067 .{ .name = "strong.o", .bytes = strong_object },
4068 }, .{});
4069 defer linked.deinit(allocator);
4070
4071 const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
4072 const strong_contribution = metadata.manifestContribution(linked.manifest, "strong.o", .section, ".text.strong", strong_index) orelse return error.MissingSection;
4073 try std.testing.expect(metadata.manifestContribution(linked.manifest, "weak.o", .section, ".text.weak", weak_index) != null);
4074
4075 const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
4076 try std.testing.expectEqual(strong_contribution.address, loaded);
4077 }
4078
4079 test "ELF linker selects first weak definition when no strong definition exists" {
4080 const allocator = std.testing.allocator;
4081
4082 const caller_text = [_]u8{
4083 0x48, 0xb8,
4084 0, 0,
4085 0, 0,
4086 0, 0,
4087 0, 0,
4088 0xc3,
4089 };
4090 const caller_text_index: u16 = 1;
4091 const hook_ref: u32 = 3;
4092 const caller_object = try buildObject(allocator, .{
4093 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4094 .symbols = &.{
4095 Symbol.section(caller_text_index),
4096 Symbol.function("_start", caller_text_index, 0, caller_text.len),
4097 Symbol.undefinedFunction("weak_only_hook"),
4098 },
4099 .relocations = &.{Relocation.x86_64(1, 2, hook_ref, .@"64", 0)},
4100 });
4101 defer allocator.free(caller_object);
4102
4103 const first_text = [_]u8{0xc3};
4104 const first_index: u16 = 1;
4105 const first_object = try buildObject(allocator, .{
4106 .sections = &.{Section.progbits(".text.first_weak", &first_text, std.elf.SHF_EXECINSTR, 16)},
4107 .symbols = &.{
4108 Symbol.section(first_index),
4109 Symbol.weakFunction("weak_only_hook", first_index, 0, first_text.len),
4110 },
4111 });
4112 defer allocator.free(first_object);
4113
4114 const second_text = [_]u8{ 0x90, 0xc3 };
4115 const second_index: u16 = 1;
4116 const second_object = try buildObject(allocator, .{
4117 .sections = &.{Section.progbits(".text.second_weak", &second_text, std.elf.SHF_EXECINSTR, 16)},
4118 .symbols = &.{
4119 Symbol.section(second_index),
4120 Symbol.weakFunction("weak_only_hook", second_index, 0, second_text.len),
4121 },
4122 });
4123 defer allocator.free(second_object);
4124
4125 var linked = try linkExecutable(allocator, &.{
4126 .{ .name = "caller.o", .bytes = caller_object },
4127 .{ .name = "first.o", .bytes = first_object },
4128 .{ .name = "second.o", .bytes = second_object },
4129 }, .{});
4130 defer linked.deinit(allocator);
4131
4132 const caller_contribution = metadata.manifestContribution(linked.manifest, "caller.o", .section, ".text", caller_text_index) orelse return error.MissingSection;
4133 const first_contribution = metadata.manifestContribution(linked.manifest, "first.o", .section, ".text.first_weak", first_index) orelse return error.MissingSection;
4134 try std.testing.expect(metadata.manifestContribution(linked.manifest, "second.o", .section, ".text.second_weak", second_index) != null);
4135
4136 const loaded = std.mem.readInt(u64, linked.bytes[@as(usize, @intCast(caller_contribution.file_offset)) + 2 ..][0..8], .little);
4137 try std.testing.expectEqual(first_contribution.address, loaded);
4138 }
4139
4140 test "ELF linker parallel unresolved relocation scan matches serial output" {
4141 const allocator = std.testing.allocator;
4142 const relocation_count = format.parallel_relocation_threshold + 1024;
4143
4144 const text = [_]u8{0xc3};
4145 const text_index: u16 = 1;
4146 const data = try allocator.alloc(u8, relocation_count * 8);
4147 defer allocator.free(data);
4148 @memset(data, 0);
4149 const data_index: u16 = 2;
4150 const target_ref: u32 = 4;
4151 const relocations = try allocator.alloc(Relocation, relocation_count);
4152 defer allocator.free(relocations);
4153 for (relocations, 0..) |*relocation, index| {
4154 relocation.* = Relocation.x86_64(data_index, index * 8, target_ref, .@"64", 0);
4155 }
4156 const caller_object = try buildObject(allocator, .{
4157 .sections = &.{
4158 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4159 Section.progbits(".data.refs", data, std.elf.SHF_WRITE, 8),
4160 },
4161 .symbols = &.{
4162 Symbol.section(text_index),
4163 Symbol.section(data_index),
4164 Symbol.function("_start", text_index, 0, text.len),
4165 Symbol.undefinedObject("target_value"),
4166 },
4167 .relocations = relocations,
4168 });
4169 defer allocator.free(caller_object);
4170
4171 const target_data = [_]u8{ 1, 2, 3, 4 };
4172 const target_index: u16 = 1;
4173 const target_object = try buildObject(allocator, .{
4174 .sections = &.{Section.progbits(".rodata.target", &target_data, 0, 4)},
4175 .symbols = &.{
4176 Symbol.section(target_index),
4177 Symbol.object("target_value", target_index, 0, target_data.len),
4178 },
4179 });
4180 defer allocator.free(target_object);
4181
4182 const inputs = [_]model.Input{
4183 .{ .name = "caller.o", .bytes = caller_object },
4184 .{ .name = "target.o", .bytes = target_object },
4185 };
4186
4187 var serial = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4188 defer serial.deinit(allocator);
4189 var concurrent = try linkExecutable(allocator, &inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4190 defer concurrent.deinit(allocator);
4191
4192 try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4193 }
4194
4195 test "ELF linker parallel global symbols and executable entries match serial output" {
4196 const allocator = std.testing.allocator;
4197 const object_count = 128;
4198 const definitions_per_object = symbol_table.parallel_global_symbol_threshold / object_count + 8;
4199
4200 const inputs = try allocator.alloc(model.Input, object_count);
4201 defer allocator.free(inputs);
4202 var built_count: usize = 0;
4203 defer {
4204 for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4205 }
4206
4207 for (inputs, 0..) |*input, object_index| {
4208 const text = [_]u8{0xc3};
4209 const section_name = try std.fmt.allocPrint(allocator, ".text.{d}", .{object_index});
4210 defer allocator.free(section_name);
4211 const text_index: u16 = 1;
4212
4213 var names: [definitions_per_object][]u8 = undefined;
4214 var named: usize = 0;
4215 defer for (names[0..named]) |name| allocator.free(name);
4216
4217 var symbols: [definitions_per_object + 2]Symbol = undefined;
4218 var symbol_count: usize = 0;
4219 symbols[symbol_count] = Symbol.section(text_index);
4220 symbol_count += 1;
4221 if (object_index == 0) {
4222 symbols[symbol_count] = Symbol.function("_start", text_index, 0, text.len);
4223 symbol_count += 1;
4224 }
4225
4226 for (0..definitions_per_object) |symbol_index| {
4227 names[symbol_index] = try std.fmt.allocPrint(allocator, "global_{d}_{d}", .{ object_index, symbol_index });
4228 named += 1;
4229 symbols[symbol_count] = Symbol.object(names[symbol_index], text_index, 0, text.len);
4230 symbol_count += 1;
4231 }
4232
4233 input.* = .{
4234 .name = "globals.o",
4235 .bytes = try buildObject(allocator, .{
4236 .sections = &.{Section.progbits(section_name, &text, std.elf.SHF_EXECINSTR, 16)},
4237 .symbols = symbols[0..symbol_count],
4238 }),
4239 };
4240 built_count += 1;
4241 }
4242
4243 var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4244 defer serial.deinit(allocator);
4245 var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4246 defer concurrent.deinit(allocator);
4247
4248 try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4249 }
4250
4251 test "ELF linker parallel output payload copy matches serial output" {
4252 const allocator = std.testing.allocator;
4253 const object_count = 64;
4254 const payload_size = payload.parallel_object_copy_threshold / object_count + 4096;
4255
4256 const payload_bytes = try allocator.alloc(u8, payload_size);
4257 defer allocator.free(payload_bytes);
4258 for (payload_bytes, 0..) |*byte, index| byte.* = @truncate(index *% 251);
4259
4260 const inputs = try allocator.alloc(model.Input, object_count);
4261 defer allocator.free(inputs);
4262 var built_count: usize = 0;
4263 defer {
4264 for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4265 }
4266
4267 for (inputs, 0..) |*input, object_index| {
4268 const text = [_]u8{0xc3};
4269 const section_name = try std.fmt.allocPrint(allocator, ".rodata.copy.{d}", .{object_index});
4270 defer allocator.free(section_name);
4271 const symbol_name = try std.fmt.allocPrint(allocator, "copy_payload_{d}", .{object_index});
4272 defer allocator.free(symbol_name);
4273
4274 input.* = .{
4275 .name = "copy.o",
4276 .bytes = if (object_index == 0) try buildObject(allocator, .{
4277 .sections = &.{
4278 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4279 Section.progbits(section_name, payload_bytes, 0, 16),
4280 },
4281 .symbols = &.{
4282 Symbol.section(1),
4283 Symbol.section(2),
4284 Symbol.function("_start", 1, 0, text.len),
4285 Symbol.object(symbol_name, 2, 0, payload_bytes.len),
4286 },
4287 }) else try buildObject(allocator, .{
4288 .sections = &.{Section.progbits(section_name, payload_bytes, 0, 16)},
4289 .symbols = &.{ Symbol.section(1), Symbol.object(symbol_name, 1, 0, payload_bytes.len) },
4290 }),
4291 };
4292 built_count += 1;
4293 }
4294
4295 var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4296 defer serial.deinit(allocator);
4297 var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4298 defer concurrent.deinit(allocator);
4299
4300 try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4301 }
4302
4303 test "ELF linker keeps load segment pages disjoint with tls" {
4304 const allocator = std.testing.allocator;
4305 const text = [_]u8{0xc3};
4306 const tdata = [_]u8{ 7, 0, 0, 0, 0, 0, 0, 0 };
4307 const data = [_]u8{ 1, 2, 3, 4 };
4308 const text_index: u16 = 1;
4309 const tdata_index: u16 = 2;
4310 const object = try buildObject(allocator, .{
4311 .sections = &.{
4312 Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16),
4313 Section.progbits(".tdata", &tdata, std.elf.SHF_WRITE | std.elf.SHF_TLS, 8),
4314 Section.nobits(".tbss", 64, std.elf.SHF_WRITE | std.elf.SHF_TLS, 8),
4315 Section.progbits(".data", &data, std.elf.SHF_WRITE, 4),
4316 Section.nobits(".bss", 128, std.elf.SHF_WRITE, 8),
4317 },
4318 .symbols = &.{
4319 Symbol.section(text_index),
4320 Symbol.section(tdata_index),
4321 Symbol.section(3),
4322 Symbol.section(4),
4323 Symbol.section(5),
4324 Symbol.function("_start", text_index, 0, text.len),
4325 Symbol.tlsObject("x", tdata_index, 0, 8),
4326 },
4327 });
4328 defer allocator.free(object);
4329
4330 var linked = try linkExecutable(allocator, &.{.{ .name = "tls-pages.o", .bytes = object }}, .{ .incremental_mode = .off });
4331 defer linked.deinit(allocator);
4332
4333 const page_size: u64 = 0x1000;
4334 const phnum = readU16(linked.bytes, 56);
4335 var previous_page_end: u64 = 0;
4336 var tls_vaddr: u64 = 0;
4337 var tls_offset: u64 = 0;
4338 var tls_filesz: u64 = 0;
4339 var load_count: usize = 0;
4340 for (0..phnum) |index| {
4341 const offset = ehdr_size + index * phdr_size;
4342 const header_type = readU32(linked.bytes, offset);
4343 if (header_type == std.elf.PT_TLS) {
4344 tls_offset = readU64(linked.bytes, offset + 8);
4345 tls_vaddr = readU64(linked.bytes, offset + 16);
4346 tls_filesz = readU64(linked.bytes, offset + 32);
4347 continue;
4348 }
4349 if (header_type != std.elf.PT_LOAD) continue;
4350 load_count += 1;
4351 const load_offset = readU64(linked.bytes, offset + 8);
4352 const load_vaddr = readU64(linked.bytes, offset + 16);
4353 const load_filesz = readU64(linked.bytes, offset + 32);
4354 const load_memsz = readU64(linked.bytes, offset + 40);
4355 try std.testing.expect(load_memsz >= load_filesz);
4356 const start_page = load_vaddr / page_size;
4357 try std.testing.expect(start_page >= previous_page_end);
4358 previous_page_end = (load_vaddr + load_memsz + page_size - 1) / page_size;
4359 _ = load_offset;
4360 }
4361 try std.testing.expect(load_count >= 3);
4362 try std.testing.expect(tls_filesz != 0);
4363
4364 var template_backed = false;
4365 for (0..phnum) |index| {
4366 const offset = ehdr_size + index * phdr_size;
4367 if (readU32(linked.bytes, offset) != std.elf.PT_LOAD) continue;
4368 const load_offset = readU64(linked.bytes, offset + 8);
4369 const load_vaddr = readU64(linked.bytes, offset + 16);
4370 const load_filesz = readU64(linked.bytes, offset + 32);
4371 if (tls_vaddr < load_vaddr or tls_vaddr + tls_filesz > load_vaddr + load_filesz) continue;
4372 if (tls_vaddr - tls_offset != load_vaddr - load_offset) continue;
4373 template_backed = true;
4374 }
4375 try std.testing.expect(template_backed);
4376 }
4377
4378 test "ELF linker synthesizes iplt machinery for ifunc references" {
4379 const allocator = std.testing.allocator;
4380
4381 const resolver_text = [_]u8{ 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4382 const resolver_index: u16 = 1;
4383 const provider_object = try buildObject(allocator, .{
4384 .sections = &.{Section.progbits(".text.resolver", &resolver_text, std.elf.SHF_EXECINSTR, 16)},
4385 .symbols = &.{
4386 Symbol.section(resolver_index),
4387 Symbol.ifunc("fast_copy", resolver_index, 0, resolver_text.len),
4388 },
4389 });
4390 defer allocator.free(provider_object);
4391
4392 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4393 const caller_index: u16 = 1;
4394 const copy_symbol: u32 = 3;
4395 const bounds_symbol: u32 = 4;
4396 const caller_object = try buildObject(allocator, .{
4397 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4398 .symbols = &.{
4399 Symbol.section(caller_index),
4400 Symbol.function("_start", caller_index, 0, caller_text.len),
4401 Symbol.undefinedFunction("fast_copy"),
4402 Symbol.undefinedObject("__rela_iplt_start"),
4403 },
4404 .relocations = &.{
4405 Relocation.x86_64(caller_index, 1, copy_symbol, .PLT32, -4),
4406 Relocation.x86_64(caller_index, 8, bounds_symbol, .PC32, -4),
4407 },
4408 });
4409 defer allocator.free(caller_object);
4410
4411 const inputs = [_]model.Input{
4412 .{ .name = "caller.o", .bytes = caller_object },
4413 .{ .name = "provider.o", .bytes = provider_object },
4414 };
4415 var linked = try linkExecutable(allocator, &inputs, .{});
4416 defer linked.deinit(allocator);
4417
4418 const iplt = metadata.manifestSection(linked.manifest, ".iplt") orelse return error.MissingSection;
4419 const igot = metadata.manifestSection(linked.manifest, ".igot") orelse return error.MissingSection;
4420 const rela = metadata.manifestSection(linked.manifest, ".rela.iplt") orelse return error.MissingSection;
4421 const resolver = metadata.manifestContribution(linked.manifest, "provider.o", .section, ".text.resolver", 1) orelse return error.MissingSection;
4422
4423 try std.testing.expectEqual(@as(u64, 16), iplt.size);
4424 try std.testing.expectEqual(@as(u64, 8), igot.size);
4425 try std.testing.expectEqual(@as(u64, 24), rela.size);
4426
4427 const stub_offset: usize = @intCast(iplt.file_offset);
4428 try std.testing.expectEqual(@as(u8, 0xff), linked.bytes[stub_offset]);
4429 try std.testing.expectEqual(@as(u8, 0x25), linked.bytes[stub_offset + 1]);
4430 const stub_disp = std.mem.readInt(i32, linked.bytes[stub_offset + 2 ..][0..4], .little);
4431 try std.testing.expectEqual(@as(i128, @intCast(igot.address)), @as(i128, @intCast(iplt.address + 6)) + stub_disp);
4432
4433 const slot_offset: usize = @intCast(igot.file_offset);
4434 try std.testing.expectEqual(resolver.address, std.mem.readInt(u64, linked.bytes[slot_offset..][0..8], .little));
4435
4436 const rela_offset: usize = @intCast(rela.file_offset);
4437 try std.testing.expectEqual(igot.address, std.mem.readInt(u64, linked.bytes[rela_offset..][0..8], .little));
4438 try std.testing.expectEqual(@as(u64, 37), std.mem.readInt(u64, linked.bytes[rela_offset + 8 ..][0..8], .little));
4439 try std.testing.expectEqual(resolver.address, std.mem.readInt(u64, linked.bytes[rela_offset + 16 ..][0..8], .little));
4440
4441 const text = metadata.manifestSection(linked.manifest, ".text") orelse return error.MissingSection;
4442 const text_offset: usize = @intCast(text.file_offset);
4443 const call_disp = std.mem.readInt(i32, linked.bytes[text_offset + 1 ..][0..4], .little);
4444 try std.testing.expectEqual(@as(i128, @intCast(iplt.address)), @as(i128, @intCast(text.address + 5)) + call_disp);
4445 const bounds_disp = std.mem.readInt(i32, linked.bytes[text_offset + 8 ..][0..4], .little);
4446 try std.testing.expectEqual(@as(i128, @intCast(rela.address)), @as(i128, @intCast(text.address + 12)) + bounds_disp);
4447 }
4448
4449 test "ELF linker parallel ifunc marks match serial output" {
4450 const allocator = std.testing.allocator;
4451 const filler_count = 128;
4452 const symbols_per_filler = ifunc.parallel_mark_symbol_threshold / filler_count + 8;
4453
4454 const inputs = try allocator.alloc(model.Input, filler_count + 2);
4455 defer allocator.free(inputs);
4456 var built_count: usize = 0;
4457 defer {
4458 for (inputs[0..built_count]) |input| allocator.free(input.bytes);
4459 }
4460
4461 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
4462 const caller_index: u16 = 1;
4463 const copy_symbol: u32 = 3;
4464 inputs[built_count] = .{ .name = "caller.o", .bytes = try buildObject(allocator, .{
4465 .sections = &.{Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16)},
4466 .symbols = &.{
4467 Symbol.section(caller_index),
4468 Symbol.function("_start", caller_index, 0, caller_text.len),
4469 Symbol.undefinedFunction("fast_copy"),
4470 },
4471 .relocations = &.{Relocation.x86_64(caller_index, 1, copy_symbol, .PLT32, -4)},
4472 }) };
4473 built_count += 1;
4474
4475 const resolver_text = [_]u8{ 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3 };
4476 const resolver_index: u16 = 1;
4477 inputs[built_count] = .{ .name = "provider.o", .bytes = try buildObject(allocator, .{
4478 .sections = &.{Section.progbits(".text.resolver", &resolver_text, std.elf.SHF_EXECINSTR, 16)},
4479 .symbols = &.{
4480 Symbol.section(resolver_index),
4481 Symbol.ifunc("fast_copy", resolver_index, 0, resolver_text.len),
4482 },
4483 }) };
4484 built_count += 1;
4485
4486 for (0..filler_count) |object_index| {
4487 const text = [_]u8{0xc3};
4488 const text_index: u16 = 1;
4489
4490 var names: [symbols_per_filler][]u8 = undefined;
4491 var named: usize = 0;
4492 defer for (names[0..named]) |name| allocator.free(name);
4493
4494 var symbols: [symbols_per_filler + 1]Symbol = undefined;
4495 symbols[0] = Symbol.section(text_index);
4496 for (0..symbols_per_filler) |symbol_index| {
4497 const pattern = "ifunc_mark_fill_{d}_{d}";
4498 names[symbol_index] = try std.fmt.allocPrint(allocator, pattern, .{ object_index, symbol_index });
4499 named += 1;
4500 symbols[symbol_index + 1] = Symbol.function(names[symbol_index], text_index, 0, text.len);
4501 }
4502
4503 inputs[built_count] = .{ .name = "fill.o", .bytes = try buildObject(allocator, .{
4504 .sections = &.{Section.progbits(".text.fill", &text, std.elf.SHF_EXECINSTR, 16)},
4505 .symbols = &symbols,
4506 }) };
4507 built_count += 1;
4508 }
4509
4510 var serial = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 1 });
4511 defer serial.deinit(allocator);
4512 var concurrent = try linkExecutable(allocator, inputs, .{ .incremental_mode = .off, .max_link_jobs = 8 });
4513 defer concurrent.deinit(allocator);
4514
4515 try std.testing.expectEqualSlices(u8, serial.bytes, concurrent.bytes);
4516 }
4517
4518 test "elf namespace" {
4519 std.testing.refAllDecls(elf);
4520 std.testing.refAllDecls(link);
4521 std.testing.refAllDecls(metadata);
4522 std.testing.refAllDecls(address_suite);
4523 std.testing.refAllDecls(archive_suite);
4524 std.testing.refAllDecls(ehframe_suite);
4525 std.testing.refAllDecls(group_suite);
4526 std.testing.refAllDecls(icf_suite);
4527 std.testing.refAllDecls(image_suite);
4528 std.testing.refAllDecls(layout_suite);
4529 std.testing.refAllDecls(liveness_suite);
4530 std.testing.refAllDecls(merge_suite);
4531 std.testing.refAllDecls(relocation_suite);
4532 }