1/// Address allocated for this Atom.
2value: i64 = 0,
3
4/// Name of this Atom.
5name_offset: u32 = 0,
6
7/// Index into linker's input file table.
8file_index: File.Index = 0,
9
10/// Size of this atom
11size: u64 = 0,
12
13/// Alignment of this atom as a power of two.
14alignment: Alignment = .@"1",
15
16/// Index of the input section.
17input_section_index: u32 = 0,
18
19/// Index of the output section.
20output_section_index: u32 = 0,
21
22/// Index of the input section containing this atom's relocs.
23relocs_section_index: u32 = 0,
24
25/// Index of this atom in the linker's atoms table.
26atom_index: Index = 0,
27
28/// Points to the previous and next neighbors.
29prev_atom_ref: Elf.Ref = .{},
30next_atom_ref: Elf.Ref = .{},
31
32/// Specifies whether this atom is alive or has been garbage collected.
33alive: bool = true,
34
35/// Specifies if the atom has been visited during garbage collection.
36visited: bool = false,
37
38extra_index: u32 = 0,
39
40pub const Alignment = @import("../../InternPool.zig").Alignment;
41
42pub fn name(self: Atom, elf_file: *Elf) [:0]const u8 {
43 const file_ptr = self.file(elf_file).?;
44 return switch (file_ptr) {
45 inline else => |x| x.getString(self.name_offset),
46 };
47}
48
49pub fn address(self: Atom, elf_file: *Elf) i64 {
50 const shdr = elf_file.sections.items(.shdr)[self.output_section_index];
51 return @as(i64, @intCast(shdr.sh_addr)) + self.value;
52}
53
54pub fn offset(self: Atom, elf_file: *Elf) u64 {
55 const shdr = elf_file.sections.items(.shdr)[self.output_section_index];
56 return shdr.sh_offset + @as(u64, @intCast(self.value));
57}
58
59pub fn ref(self: Atom) Elf.Ref {
60 return .{ .index = self.atom_index, .file = self.file_index };
61}
62
63pub fn prevAtom(self: Atom, elf_file: *Elf) ?*Atom {
64 return elf_file.atom(self.prev_atom_ref);
65}
66
67pub fn nextAtom(self: Atom, elf_file: *Elf) ?*Atom {
68 return elf_file.atom(self.next_atom_ref);
69}
70
71pub fn debugTombstoneValue(self: Atom, target: Symbol, elf_file: *Elf) ?u64 {
72 if (target.mergeSubsection(elf_file)) |msub| {
73 if (msub.alive) return null;
74 }
75 if (target.atom(elf_file)) |atom_ptr| {
76 if (atom_ptr.alive) return null;
77 }
78 const atom_name = self.name(elf_file);
79 if (!mem.startsWith(u8, atom_name, ".debug")) return null;
80 return if (mem.eql(u8, atom_name, ".debug_loc") or mem.eql(u8, atom_name, ".debug_ranges")) 1 else 0;
81}
82
83pub fn file(self: Atom, elf_file: *Elf) ?File {
84 return elf_file.file(self.file_index);
85}
86
87pub fn thunk(self: Atom, elf_file: *Elf) *Thunk {
88 const extras = self.extra(elf_file);
89 return elf_file.thunk(extras.thunk);
90}
91
92pub fn inputShdr(self: Atom, elf_file: *Elf) elf.Elf64_Shdr {
93 return switch (self.file(elf_file).?) {
94 .object => |x| x.shdrs.items[self.input_section_index],
95 .zig_object => |x| x.inputShdr(self.atom_index, elf_file),
96 else => unreachable,
97 };
98}
99
100pub fn relocsShndx(self: Atom) ?u32 {
101 if (self.relocs_section_index == 0) return null;
102 return self.relocs_section_index;
103}
104
105pub fn priority(atom: Atom, elf_file: *Elf) u64 {
106 const index = atom.file(elf_file).?.index();
107 return priorityLookup(index, atom.input_section_index);
108}
109
110pub fn priorityLookup(file_index: File.Index, input_section_index: u32) u64 {
111 return (@as(u64, @intCast(file_index)) << 32) | @as(u64, @intCast(input_section_index));
112}
113
114/// Returns how much room there is to grow in virtual address space.
115/// File offset relocation happens transparently, so it is not included in
116/// this calculation.
117pub fn capacity(self: Atom, elf_file: *Elf) u64 {
118 const next_addr = if (self.nextAtom(elf_file)) |next_atom|
119 next_atom.address(elf_file)
120 else
121 std.math.maxInt(u32);
122 return @intCast(next_addr - self.address(elf_file));
123}
124
125pub fn fileCapacity(self: Atom, elf_file: *Elf) u64 {
126 const self_off = self.offset(elf_file);
127 const next_off = if (self.nextAtom(elf_file)) |next_atom|
128 next_atom.offset(elf_file)
129 else
130 self_off + elf_file.allocatedSize(self_off);
131 return @intCast(next_off - self_off);
132}
133
134pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
135 // No need to keep a free list node for the last block.
136 const next = self.nextAtom(elf_file) orelse return false;
137 const cap: u64 = @intCast(next.value - self.value);
138 const ideal_cap = Elf.padToIdeal(self.size);
139 if (cap <= ideal_cap) return false;
140 const surplus = cap - ideal_cap;
141 return surplus >= Elf.min_text_capacity;
142}
143
144pub fn free(self: *Atom, elf_file: *Elf) void {
145 log.debug("freeAtom atom({f}) ({s})", .{ self.ref(), self.name(elf_file) });
146
147 const comp = elf_file.base.comp;
148 const gpa = comp.gpa;
149 const shndx = self.output_section_index;
150 const slice = elf_file.sections.slice();
151 const free_list = &slice.items(.free_list)[shndx];
152 const last_atom_ref = &slice.items(.last_atom)[shndx];
153 var already_have_free_list_node = false;
154 {
155 var i: usize = 0;
156 // TODO turn free_list into a hash map
157 while (i < free_list.items.len) {
158 if (free_list.items[i].eql(self.ref())) {
159 _ = free_list.swapRemove(i);
160 continue;
161 }
162 if (self.prevAtom(elf_file)) |prev_atom| {
163 if (free_list.items[i].eql(prev_atom.ref())) {
164 already_have_free_list_node = true;
165 }
166 }
167 i += 1;
168 }
169 }
170
171 if (elf_file.atom(last_atom_ref.*)) |last_atom| {
172 if (last_atom.ref().eql(self.ref())) {
173 if (self.prevAtom(elf_file)) |prev_atom| {
174 // TODO shrink the section size here
175 last_atom_ref.* = prev_atom.ref();
176 } else {
177 last_atom_ref.* = .{};
178 }
179 }
180 }
181
182 if (self.prevAtom(elf_file)) |prev_atom| {
183 prev_atom.next_atom_ref = self.next_atom_ref;
184 if (!already_have_free_list_node and prev_atom.*.freeListEligible(elf_file)) {
185 // The free list is heuristics, it doesn't have to be perfect, so we can
186 // ignore the OOM here.
187 free_list.append(gpa, prev_atom.ref()) catch {};
188 }
189 } else {
190 self.prev_atom_ref = .{};
191 }
192
193 if (self.nextAtom(elf_file)) |next_atom| {
194 next_atom.prev_atom_ref = self.prev_atom_ref;
195 } else {
196 self.next_atom_ref = .{};
197 }
198
199 switch (self.file(elf_file).?) {
200 .zig_object => |zo| {
201 // TODO create relocs free list
202 self.freeRelocs(zo);
203 // TODO figure out how to free input section mappind in ZigModule
204 // const zig_object = elf_file.zigObjectPtr().?
205 // assert(zig_object.atoms.swapRemove(self.atom_index));
206 },
207 else => {},
208 }
209 self.* = .{};
210}
211
212pub fn relocs(self: Atom, elf_file: *Elf) []const elf.Elf64_Rela {
213 const shndx = self.relocsShndx() orelse return &[0]elf.Elf64_Rela{};
214 switch (self.file(elf_file).?) {
215 .zig_object => |x| return x.relocs.items[shndx].items,
216 .object => |x| {
217 const extras = self.extra(elf_file);
218 return x.relocs.items[extras.rel_index..][0..extras.rel_count];
219 },
220 else => unreachable,
221 }
222}
223
224pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.array_list.Managed(elf.Elf64_Rela)) !void {
225 relocs_log.debug("0x{x}: {s}", .{ self.address(elf_file), self.name(elf_file) });
226
227 const cpu_arch = elf_file.getTarget().cpu.arch;
228 const file_ptr = self.file(elf_file).?;
229 for (self.relocs(elf_file)) |rel| {
230 const target_ref = file_ptr.resolveSymbol(rel.r_sym(), elf_file);
231 const target = elf_file.symbol(target_ref).?;
232 const r_type = rel.r_type();
233 const r_offset: u64 = @intCast(self.value + @as(i64, @intCast(rel.r_offset)));
234 var r_addend = rel.r_addend;
235 var r_sym: u32 = 0;
236 switch (target.type(elf_file)) {
237 elf.STT_SECTION => {
238 r_addend += @intCast(target.address(.{}, elf_file));
239 r_sym = target.outputShndx(elf_file) orelse 0;
240 },
241 else => {
242 r_sym = target.outputSymtabIndex(elf_file) orelse 0;
243 },
244 }
245
246 relocs_log.debug(" {f}: [{x} => {d}({s})] + {x}", .{
247 relocation.fmtRelocType(rel.r_type(), cpu_arch),
248 r_offset,
249 r_sym,
250 target.name(elf_file),
251 r_addend,
252 });
253
254 out_relocs.appendAssumeCapacity(.{
255 .r_offset = r_offset,
256 .r_addend = r_addend,
257 .r_info = (@as(u64, @intCast(r_sym)) << 32) | r_type,
258 });
259 }
260}
261
262pub fn fdes(atom: Atom, object: *Object) []Fde {
263 const extras = object.atomExtra(atom.extra_index);
264 return object.fdes.items[extras.fde_start..][0..extras.fde_count];
265}
266
267pub fn markFdesDead(self: Atom, object: *Object) void {
268 for (self.fdes(object)) |*fde| fde.alive = false;
269}
270
271pub fn addReloc(self: Atom, alloc: Allocator, reloc: elf.Elf64_Rela, zo: *ZigObject) !void {
272 const rels = &zo.relocs.items[self.relocs_section_index];
273 try rels.ensureUnusedCapacity(alloc, 1);
274 self.addRelocAssumeCapacity(reloc, zo);
275}
276
277pub fn addRelocAssumeCapacity(self: Atom, reloc: elf.Elf64_Rela, zo: *ZigObject) void {
278 const rels = &zo.relocs.items[self.relocs_section_index];
279 rels.appendAssumeCapacity(reloc);
280}
281
282pub fn freeRelocs(self: Atom, zo: *ZigObject) void {
283 zo.relocs.items[self.relocs_section_index].clearRetainingCapacity();
284}
285
286pub fn scanRelocsRequiresCode(self: Atom, elf_file: *Elf) bool {
287 const cpu_arch = elf_file.getTarget().cpu.arch;
288 for (self.relocs(elf_file)) |rel| {
289 switch (cpu_arch) {
290 .x86_64 => {
291 const r_type: elf.R_X86_64 = @fromBackingInt(@intCast(rel.r_type()));
292 if (r_type == .GOTTPOFF) return true;
293 },
294 else => {},
295 }
296 }
297 return false;
298}
299
300pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype) RelocError!void {
301 const cpu_arch = elf_file.getTarget().cpu.arch;
302 const file_ptr = self.file(elf_file).?;
303 const rels = self.relocs(elf_file);
304
305 var has_reloc_errors = false;
306 var it = RelocsIterator{ .relocs = rels };
307 while (it.next()) |rel| {
308 const r_kind = relocation.decode(rel.r_type(), cpu_arch);
309 if (r_kind == .none) continue;
310
311 const symbol_ref = file_ptr.resolveSymbol(rel.r_sym(), elf_file);
312 const symbol = elf_file.symbol(symbol_ref) orelse {
313 const sym_name = switch (file_ptr) {
314 .zig_object => |x| x.symbol(rel.r_sym()).name(elf_file),
315 inline else => |x| x.symbols.items[rel.r_sym()].name(elf_file),
316 };
317 // Violation of One Definition Rule for COMDATs.
318 // TODO convert into an error
319 log.debug("{f}: {s}: {s} refers to a discarded COMDAT section", .{
320 file_ptr.fmtPath(),
321 self.name(elf_file),
322 sym_name,
323 });
324 continue;
325 };
326
327 const is_synthetic_symbol = switch (file_ptr) {
328 .zig_object => false, // TODO: implement this once we support merge sections in ZigObject
329 .object => |x| rel.r_sym() >= x.symtab.items.len,
330 else => unreachable,
331 };
332
333 // Report an undefined symbol.
334 if (!is_synthetic_symbol and (try self.reportUndefined(elf_file, symbol, rel, undefs)))
335 continue;
336
337 if (symbol.isIFunc(elf_file)) {
338 symbol.flags.needs_got = true;
339 symbol.flags.needs_plt = true;
340 }
341
342 // While traversing relocations, mark symbols that require special handling such as
343 // pointer indirection via GOT, or a stub trampoline via PLT.
344 switch (cpu_arch) {
345 .x86_64 => x86_64.scanReloc(self, elf_file, rel, symbol, code, &it) catch |err| switch (err) {
346 error.RelocFailure => has_reloc_errors = true,
347 else => |e| return e,
348 },
349 .aarch64, .aarch64_be => aarch64.scanReloc(self, elf_file, rel, symbol, code, &it) catch |err| switch (err) {
350 error.RelocFailure => has_reloc_errors = true,
351 else => |e| return e,
352 },
353 .riscv64, .riscv64be => riscv.scanReloc(self, elf_file, rel, symbol, code, &it) catch |err| switch (err) {
354 error.RelocFailure => has_reloc_errors = true,
355 else => |e| return e,
356 },
357 else => return error.UnsupportedCpuArch,
358 }
359 }
360 if (has_reloc_errors) return error.RelocFailure;
361}
362
363fn scanReloc(
364 self: Atom,
365 symbol: *Symbol,
366 rel: elf.Elf64_Rela,
367 action: RelocAction,
368 elf_file: *Elf,
369) RelocError!void {
370 const is_writeable = self.inputShdr(elf_file).sh_flags & elf.SHF_WRITE != 0;
371 const num_dynrelocs = switch (self.file(elf_file).?) {
372 .linker_defined => unreachable,
373 .shared_object => unreachable,
374 inline else => |x| &x.num_dynrelocs,
375 };
376
377 switch (action) {
378 .none => {},
379
380 .@"error" => if (symbol.isAbs(elf_file))
381 try self.reportNoPicError(symbol, rel, elf_file)
382 else
383 try self.reportPicError(symbol, rel, elf_file),
384
385 .copyrel => {
386 if (elf_file.z_nocopyreloc) {
387 if (symbol.isAbs(elf_file))
388 try self.reportNoPicError(symbol, rel, elf_file)
389 else
390 try self.reportPicError(symbol, rel, elf_file);
391 }
392 symbol.flags.needs_copy_rel = true;
393 },
394
395 .dyn_copyrel => {
396 if (is_writeable or elf_file.z_nocopyreloc) {
397 if (!is_writeable) {
398 if (elf_file.z_notext) {
399 elf_file.has_text_reloc = true;
400 } else {
401 try self.reportTextRelocError(symbol, rel, elf_file);
402 }
403 }
404 num_dynrelocs.* += 1;
405 } else {
406 symbol.flags.needs_copy_rel = true;
407 }
408 },
409
410 .plt => {
411 symbol.flags.needs_plt = true;
412 },
413
414 .cplt => {
415 symbol.flags.needs_plt = true;
416 symbol.flags.is_canonical = true;
417 },
418
419 .dyn_cplt => {
420 if (is_writeable) {
421 num_dynrelocs.* += 1;
422 } else {
423 symbol.flags.needs_plt = true;
424 symbol.flags.is_canonical = true;
425 }
426 },
427
428 .dynrel, .baserel, .ifunc => {
429 if (!is_writeable) {
430 if (elf_file.z_notext) {
431 elf_file.has_text_reloc = true;
432 } else {
433 try self.reportTextRelocError(symbol, rel, elf_file);
434 }
435 }
436 num_dynrelocs.* += 1;
437
438 if (action == .ifunc) elf_file.num_ifunc_dynrelocs += 1;
439 },
440 }
441}
442
443const RelocAction = enum {
444 none,
445 @"error",
446 copyrel,
447 dyn_copyrel,
448 plt,
449 dyn_cplt,
450 cplt,
451 dynrel,
452 baserel,
453 ifunc,
454};
455
456fn pcRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction {
457 // zig fmt: off
458 const table: [3][4]RelocAction = .{
459 // Abs Local Import data Import func
460 .{ .@"error", .none, .@"error", .plt }, // Shared object
461 .{ .@"error", .none, .copyrel, .plt }, // PIE
462 .{ .none, .none, .copyrel, .cplt }, // Non-PIE
463 };
464 // zig fmt: on
465 const output = outputType(elf_file);
466 const data = dataType(symbol, elf_file);
467 return table[output][data];
468}
469
470fn absRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction {
471 // zig fmt: off
472 const table: [3][4]RelocAction = .{
473 // Abs Local Import data Import func
474 .{ .none, .@"error", .@"error", .@"error" }, // Shared object
475 .{ .none, .@"error", .@"error", .@"error" }, // PIE
476 .{ .none, .none, .copyrel, .cplt }, // Non-PIE
477 };
478 // zig fmt: on
479 const output = outputType(elf_file);
480 const data = dataType(symbol, elf_file);
481 return table[output][data];
482}
483
484fn dynAbsRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction {
485 if (symbol.isIFunc(elf_file)) return .ifunc;
486 // zig fmt: off
487 const table: [3][4]RelocAction = .{
488 // Abs Local Import data Import func
489 .{ .none, .baserel, .dynrel, .dynrel }, // Shared object
490 .{ .none, .baserel, .dynrel, .dynrel }, // PIE
491 .{ .none, .none, .dyn_copyrel, .dyn_cplt }, // Non-PIE
492 };
493 // zig fmt: on
494 const output = outputType(elf_file);
495 const data = dataType(symbol, elf_file);
496 return table[output][data];
497}
498
499fn outputType(elf_file: *Elf) u2 {
500 assert(!elf_file.base.isRelocatable());
501 const config = &elf_file.base.comp.config;
502 return switch (config.output_mode) {
503 .Obj => unreachable,
504 .Lib => 0,
505 .Exe => switch (elf_file.getTarget().os.tag) {
506 .haiku => 0,
507 else => if (config.pie) 1 else 2,
508 },
509 };
510}
511
512fn dataType(symbol: *const Symbol, elf_file: *Elf) u2 {
513 if (symbol.isAbs(elf_file)) return 0;
514 if (!symbol.flags.import) return 1;
515 if (symbol.type(elf_file) != elf.STT_FUNC) return 2;
516 return 3;
517}
518
519fn reportUnhandledRelocError(self: Atom, rel: elf.Elf64_Rela, elf_file: *Elf) RelocError!void {
520 const diags = &elf_file.base.comp.link_diags;
521 var err = try diags.addErrorWithNotes(1);
522 try err.addMsg("fatal linker error: unhandled relocation type {f} at offset 0x{x}", .{
523 relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch),
524 rel.r_offset,
525 });
526 err.addNote("in {f}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
527 return error.RelocFailure;
528}
529
530fn reportTextRelocError(
531 self: Atom,
532 symbol: *const Symbol,
533 rel: elf.Elf64_Rela,
534 elf_file: *Elf,
535) RelocError!void {
536 const diags = &elf_file.base.comp.link_diags;
537 var err = try diags.addErrorWithNotes(1);
538 try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{
539 rel.r_offset,
540 symbol.name(elf_file),
541 });
542 err.addNote("in {f}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
543 return error.RelocFailure;
544}
545
546fn reportPicError(
547 self: Atom,
548 symbol: *const Symbol,
549 rel: elf.Elf64_Rela,
550 elf_file: *Elf,
551) RelocError!void {
552 const diags = &elf_file.base.comp.link_diags;
553 var err = try diags.addErrorWithNotes(2);
554 try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{
555 rel.r_offset,
556 symbol.name(elf_file),
557 });
558 err.addNote("in {f}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
559 err.addNote("recompile with -fPIC", .{});
560 return error.RelocFailure;
561}
562
563fn reportNoPicError(
564 self: Atom,
565 symbol: *const Symbol,
566 rel: elf.Elf64_Rela,
567 elf_file: *Elf,
568) RelocError!void {
569 const diags = &elf_file.base.comp.link_diags;
570 var err = try diags.addErrorWithNotes(2);
571 try err.addMsg("relocation at offset 0x{x} against symbol '{s}' cannot be used", .{
572 rel.r_offset,
573 symbol.name(elf_file),
574 });
575 err.addNote("in {f}:{s}", .{ self.file(elf_file).?.fmtPath(), self.name(elf_file) });
576 err.addNote("recompile with -fno-PIC", .{});
577 return error.RelocFailure;
578}
579
580// This function will report any undefined non-weak symbols that are not imports.
581fn reportUndefined(
582 self: Atom,
583 elf_file: *Elf,
584 sym: *const Symbol,
585 rel: elf.Elf64_Rela,
586 undefs: anytype,
587) !bool {
588 const comp = elf_file.base.comp;
589 const gpa = comp.gpa;
590 const file_ptr = self.file(elf_file).?;
591 const rel_esym = switch (file_ptr) {
592 .zig_object => |x| x.symbol(rel.r_sym()).elfSym(elf_file),
593 .shared_object => |so| so.parsed.symtab[rel.r_sym()],
594 inline else => |x| x.symtab.items[rel.r_sym()],
595 };
596 const esym = sym.elfSym(elf_file);
597 if (rel_esym.st_shndx == elf.SHN_UNDEF and
598 rel_esym.st_bind() == elf.STB_GLOBAL and
599 sym.esym_index > 0 and
600 !sym.flags.import and
601 esym.st_shndx == elf.SHN_UNDEF)
602 {
603 const idx = switch (file_ptr) {
604 .zig_object => |x| x.symbols_resolver.items[rel.r_sym() & ZigObject.symbol_mask],
605 .object => |x| x.symbols_resolver.items[rel.r_sym() - x.first_global.?],
606 inline else => |x| x.symbols_resolver.items[rel.r_sym()],
607 };
608 const gop = try undefs.getOrPut(gpa, idx);
609 if (!gop.found_existing) {
610 gop.value_ptr.* = std.array_list.Managed(Elf.Ref).init(gpa);
611 }
612 try gop.value_ptr.append(.{ .index = self.atom_index, .file = self.file_index });
613 return true;
614 }
615
616 return false;
617}
618
619pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) RelocError!void {
620 relocs_log.debug("0x{x}: {s}", .{ self.address(elf_file), self.name(elf_file) });
621
622 const cpu_arch = elf_file.getTarget().cpu.arch;
623 const file_ptr = self.file(elf_file).?;
624
625 const rels = self.relocs(elf_file);
626 var it = RelocsIterator{ .relocs = rels };
627 var has_reloc_errors = false;
628 while (it.next()) |rel| {
629 const r_kind = relocation.decode(rel.r_type(), cpu_arch);
630 if (r_kind == .none) continue;
631
632 const target_ref = file_ptr.resolveSymbol(rel.r_sym(), elf_file);
633 const target = elf_file.symbol(target_ref).?;
634 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
635
636 // We will use equation format to resolve relocations:
637 // https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/
638 //
639 // Address of the source atom.
640 const P = self.address(elf_file) + @as(i64, @intCast(rel.r_offset));
641 // Addend from the relocation.
642 const A = rel.r_addend;
643 // Address of the target symbol - can be address of the symbol within an atom or address of PLT stub, or address of a Zig trampoline.
644 const S = target.address(.{}, elf_file);
645 // Address of the global offset table.
646 const GOT = elf_file.gotAddress();
647 // Relative offset to the start of the global offset table.
648 const G = target.gotAddress(elf_file) - GOT;
649 // // Address of the thread pointer.
650 const TP = elf_file.tpAddress();
651 // Address of the dynamic thread pointer.
652 const DTP = elf_file.dtpAddress();
653
654 relocs_log.debug(" {f}: {x}: [{x} => {x}] GOT({x}) ({s})", .{
655 relocation.fmtRelocType(rel.r_type(), cpu_arch),
656 r_offset,
657 P,
658 S + A,
659 G + GOT + A,
660 target.name(elf_file),
661 });
662
663 const args = ResolveArgs{ P, A, S, GOT, G, TP, DTP };
664
665 switch (cpu_arch) {
666 .x86_64 => x86_64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
667 error.RelocFailure,
668 error.RelaxFailure,
669 => has_reloc_errors = true,
670 else => |e| return e,
671 },
672 .aarch64, .aarch64_be => aarch64.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
673 error.RelocFailure,
674 error.RelaxFailure,
675 error.UnexpectedRemainder,
676 error.DivisionByZero,
677 => has_reloc_errors = true,
678 else => |e| return e,
679 },
680 .riscv64, .riscv64be => riscv.resolveRelocAlloc(self, elf_file, rel, target, args, &it, code) catch |err| switch (err) {
681 error.RelocFailure,
682 error.RelaxFailure,
683 => has_reloc_errors = true,
684 else => |e| return e,
685 },
686 else => return error.UnsupportedCpuArch,
687 }
688 }
689
690 if (has_reloc_errors) return error.RelaxFailure;
691}
692
693fn resolveDynAbsReloc(
694 self: Atom,
695 target: *const Symbol,
696 rel: elf.Elf64_Rela,
697 action: RelocAction,
698 elf_file: *Elf,
699 code: []u8,
700 r_offset: usize,
701) !void {
702 const comp = elf_file.base.comp;
703 const gpa = comp.gpa;
704 const cpu_arch = elf_file.getTarget().cpu.arch;
705 const P: u64 = @intCast(self.address(elf_file) + @as(i64, @intCast(rel.r_offset)));
706 const A = rel.r_addend;
707 const S = target.address(.{}, elf_file);
708 const is_writeable = self.inputShdr(elf_file).sh_flags & elf.SHF_WRITE != 0;
709
710 const num_dynrelocs = switch (self.file(elf_file).?) {
711 .linker_defined => unreachable,
712 .shared_object => unreachable,
713 inline else => |x| x.num_dynrelocs,
714 };
715 try elf_file.rela_dyn.ensureUnusedCapacity(gpa, num_dynrelocs);
716
717 switch (action) {
718 .@"error",
719 .plt,
720 => unreachable,
721
722 .copyrel,
723 .cplt,
724 .none,
725 => mem.writeInt(i64, code[r_offset..][0..8], S + A, .little),
726
727 .dyn_copyrel => {
728 if (is_writeable or elf_file.z_nocopyreloc) {
729 elf_file.addRelaDynAssumeCapacity(.{
730 .offset = P,
731 .sym = target.extra(elf_file).dynamic,
732 .type = relocation.encode(.abs, cpu_arch),
733 .addend = A,
734 .target = target,
735 });
736 applyDynamicReloc(A, code, r_offset);
737 } else {
738 mem.writeInt(i64, code[r_offset..][0..8], S + A, .little);
739 }
740 },
741
742 .dyn_cplt => {
743 if (is_writeable) {
744 elf_file.addRelaDynAssumeCapacity(.{
745 .offset = P,
746 .sym = target.extra(elf_file).dynamic,
747 .type = relocation.encode(.abs, cpu_arch),
748 .addend = A,
749 .target = target,
750 });
751 applyDynamicReloc(A, code, r_offset);
752 } else {
753 mem.writeInt(i64, code[r_offset..][0..8], S + A, .little);
754 }
755 },
756
757 .dynrel => {
758 elf_file.addRelaDynAssumeCapacity(.{
759 .offset = P,
760 .sym = target.extra(elf_file).dynamic,
761 .type = relocation.encode(.abs, cpu_arch),
762 .addend = A,
763 .target = target,
764 });
765 applyDynamicReloc(A, code, r_offset);
766 },
767
768 .baserel => {
769 elf_file.addRelaDynAssumeCapacity(.{
770 .offset = P,
771 .type = relocation.encode(.rel, cpu_arch),
772 .addend = S + A,
773 .target = target,
774 });
775 applyDynamicReloc(S + A, code, r_offset);
776 },
777
778 .ifunc => {
779 const S_ = target.address(.{ .plt = false }, elf_file);
780 elf_file.addRelaDynAssumeCapacity(.{
781 .offset = P,
782 .type = relocation.encode(.irel, cpu_arch),
783 .addend = S_ + A,
784 .target = target,
785 });
786 applyDynamicReloc(S_ + A, code, r_offset);
787 },
788 }
789}
790
791fn applyDynamicReloc(value: i64, code: []u8, r_offset: usize) void {
792 mem.writeInt(i64, code[r_offset..][0..8], value, .little);
793}
794
795pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: anytype) !void {
796 relocs_log.debug("0x{x}: {s}", .{ self.address(elf_file), self.name(elf_file) });
797
798 const cpu_arch = elf_file.getTarget().cpu.arch;
799 const file_ptr = self.file(elf_file).?;
800
801 const rels = self.relocs(elf_file);
802 var has_reloc_errors = false;
803 var it = RelocsIterator{ .relocs = rels };
804 while (it.next()) |rel| {
805 const r_kind = relocation.decode(rel.r_type(), cpu_arch);
806 if (r_kind == .none) continue;
807
808 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
809
810 const target_ref = file_ptr.resolveSymbol(rel.r_sym(), elf_file);
811 const target = elf_file.symbol(target_ref) orelse {
812 const sym_name = switch (file_ptr) {
813 .zig_object => |x| x.symbol(rel.r_sym()).name(elf_file),
814 inline else => |x| x.symbols.items[rel.r_sym()].name(elf_file),
815 };
816 // Violation of One Definition Rule for COMDATs.
817 // TODO convert into an error
818 log.debug("{f}: {s}: {s} refers to a discarded COMDAT section", .{
819 file_ptr.fmtPath(),
820 self.name(elf_file),
821 sym_name,
822 });
823 continue;
824 };
825 const is_synthetic_symbol = switch (file_ptr) {
826 .zig_object => false, // TODO: implement this once we support merge sections in ZigObject
827 .object => |x| rel.r_sym() >= x.symtab.items.len,
828 else => unreachable,
829 };
830
831 // Report an undefined symbol.
832 if (!is_synthetic_symbol and (try self.reportUndefined(elf_file, target, rel, undefs)))
833 continue;
834
835 // We will use equation format to resolve relocations:
836 // https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/
837 //
838 const P = self.address(elf_file) + @as(i64, @intCast(rel.r_offset));
839 // Addend from the relocation.
840 const A = rel.r_addend;
841 // Address of the target symbol - can be address of the symbol within an atom or address of PLT stub.
842 const S = target.address(.{}, elf_file);
843 // Address of the global offset table.
844 const GOT = elf_file.gotAddress();
845 // Address of the dynamic thread pointer.
846 const DTP = elf_file.dtpAddress();
847
848 const args = ResolveArgs{ P, A, S, GOT, 0, 0, DTP };
849
850 relocs_log.debug(" {f}: {x}: [{x} => {x}] ({s})", .{
851 relocation.fmtRelocType(rel.r_type(), cpu_arch),
852 rel.r_offset,
853 P,
854 S + A,
855 target.name(elf_file),
856 });
857
858 switch (cpu_arch) {
859 .x86_64 => x86_64.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
860 error.RelocFailure => has_reloc_errors = true,
861 else => |e| return e,
862 },
863 .aarch64, .aarch64_be => aarch64.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
864 error.RelocFailure => has_reloc_errors = true,
865 else => |e| return e,
866 },
867 .riscv64, .riscv64be => riscv.resolveRelocNonAlloc(self, elf_file, rel, target, args, code[r_offset..]) catch |err| switch (err) {
868 error.RelocFailure => has_reloc_errors = true,
869 else => |e| return e,
870 },
871 else => return error.UnsupportedCpuArch,
872 }
873 }
874
875 if (has_reloc_errors) return error.RelocFailure;
876}
877
878pub fn addExtra(atom: *Atom, opts: Extra.AsOptionals, elf_file: *Elf) void {
879 const file_ptr = atom.file(elf_file).?;
880 var extras = file_ptr.atomExtra(atom.extra_index);
881 inline for (@typeInfo(@TypeOf(opts)).@"struct".field_names) |field_name| {
882 if (@field(opts, field_name)) |x| {
883 @field(extras, field_name) = x;
884 }
885 }
886 file_ptr.setAtomExtra(atom.extra_index, extras);
887}
888
889pub fn extra(atom: Atom, elf_file: *Elf) Extra {
890 return atom.file(elf_file).?.atomExtra(atom.extra_index);
891}
892
893pub fn setExtra(atom: Atom, extras: Extra, elf_file: *Elf) void {
894 atom.file(elf_file).?.setAtomExtra(atom.extra_index, extras);
895}
896
897pub fn fmt(atom: Atom, elf_file: *Elf) std.fmt.Alt(Format, Format.default) {
898 return .{ .data = .{
899 .atom = atom,
900 .elf_file = elf_file,
901 } };
902}
903
904const Format = struct {
905 atom: Atom,
906 elf_file: *Elf,
907
908 fn default(f: Format, w: *Writer) Writer.Error!void {
909 const atom = f.atom;
910 const elf_file = f.elf_file;
911 try w.print("atom({d}) : {s} : @{x} : shdr({d}) : align({x}) : size({x}) : prev({f}) : next({f})", .{
912 atom.atom_index, atom.name(elf_file), atom.address(elf_file),
913 atom.output_section_index, atom.alignment.toByteUnits() orelse 0, atom.size,
914 atom.prev_atom_ref, atom.next_atom_ref,
915 });
916 if (atom.file(elf_file)) |atom_file| switch (atom_file) {
917 .object => |object| {
918 if (atom.fdes(object).len > 0) {
919 try w.writeAll(" : fdes{ ");
920 const extras = atom.extra(elf_file);
921 for (atom.fdes(object), extras.fde_start..) |fde, i| {
922 try w.print("{d}", .{i});
923 if (!fde.alive) try w.writeAll("([*])");
924 if (i - extras.fde_start < extras.fde_count - 1) try w.writeAll(", ");
925 }
926 try w.writeAll(" }");
927 }
928 },
929 else => {},
930 };
931 if (!atom.alive) {
932 try w.writeAll(" : [*]");
933 }
934 }
935};
936
937pub const Index = u32;
938
939const x86_64 = struct {
940 fn scanReloc(
941 atom: Atom,
942 elf_file: *Elf,
943 rel: elf.Elf64_Rela,
944 symbol: *Symbol,
945 code: ?[]const u8,
946 it: *RelocsIterator,
947 ) !void {
948 dev.check(.x86_64_backend);
949 const t = &elf_file.base.comp.root_mod.resolved_target.result;
950 const is_static = elf_file.base.isStatic();
951 const is_dyn_lib = elf_file.isEffectivelyDynLib();
952
953 const r_type: elf.R_X86_64 = @fromBackingInt(@intCast(rel.r_type()));
954 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
955
956 switch (r_type) {
957 .@"64" => {
958 try atom.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file);
959 },
960
961 .@"32",
962 .@"32S",
963 => {
964 try atom.scanReloc(symbol, rel, absRelocAction(symbol, elf_file), elf_file);
965 },
966
967 .GOT32,
968 .GOTPC32,
969 .GOTPC64,
970 .GOTPCREL,
971 .GOTPCREL64,
972 .GOTPCRELX,
973 .REX_GOTPCRELX,
974 => {
975 symbol.flags.needs_got = true;
976 },
977
978 .PLT32,
979 .PLTOFF64,
980 => {
981 if (symbol.flags.import) {
982 symbol.flags.needs_plt = true;
983 }
984 },
985
986 .PC32, .PC64 => {
987 try atom.scanReloc(symbol, rel, pcRelocAction(symbol, elf_file), elf_file);
988 },
989
990 .TLSGD => {
991 // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr
992
993 if (is_static or (!symbol.flags.import and !is_dyn_lib)) {
994 // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a
995 // We skip the next relocation.
996 it.skip(1);
997 } else if (!symbol.flags.import and is_dyn_lib) {
998 symbol.flags.needs_gottp = true;
999 it.skip(1);
1000 } else {
1001 symbol.flags.needs_tlsgd = true;
1002 }
1003 },
1004
1005 .TLSLD => {
1006 // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr
1007
1008 if (is_static or !is_dyn_lib) {
1009 // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a
1010 // We skip the next relocation.
1011 it.skip(1);
1012 } else {
1013 elf_file.got.flags.needs_tlsld = true;
1014 }
1015 },
1016
1017 .GOTTPOFF => {
1018 const should_relax = blk: {
1019 if (is_dyn_lib or symbol.flags.import) break :blk false;
1020 if (!x86_64.canRelaxGotTpOff(code.?[r_offset - 3 ..], t)) break :blk false;
1021 break :blk true;
1022 };
1023 if (!should_relax) {
1024 symbol.flags.needs_gottp = true;
1025 }
1026 },
1027
1028 .GOTPC32_TLSDESC => {
1029 const should_relax = is_static or (!is_dyn_lib and !symbol.flags.import);
1030 if (!should_relax) {
1031 symbol.flags.needs_tlsdesc = true;
1032 }
1033 },
1034
1035 .TPOFF32,
1036 .TPOFF64,
1037 => {
1038 if (is_dyn_lib) try atom.reportPicError(symbol, rel, elf_file);
1039 },
1040
1041 .GOTOFF64,
1042 .DTPOFF32,
1043 .DTPOFF64,
1044 .SIZE32,
1045 .SIZE64,
1046 .TLSDESC_CALL,
1047 => {},
1048
1049 else => try atom.reportUnhandledRelocError(rel, elf_file),
1050 }
1051 }
1052
1053 fn resolveRelocAlloc(
1054 atom: Atom,
1055 elf_file: *Elf,
1056 rel: elf.Elf64_Rela,
1057 target: *const Symbol,
1058 args: ResolveArgs,
1059 it: *RelocsIterator,
1060 code: []u8,
1061 ) !void {
1062 dev.check(.x86_64_backend);
1063 const t = &elf_file.base.comp.root_mod.resolved_target.result;
1064 const diags = &elf_file.base.comp.link_diags;
1065 const r_type: elf.R_X86_64 = @fromBackingInt(@intCast(rel.r_type()));
1066 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
1067
1068 const P, const A, const S, const GOT, const G, const TP, const DTP = args;
1069
1070 switch (r_type) {
1071 .NONE => unreachable,
1072
1073 .@"64" => {
1074 try atom.resolveDynAbsReloc(
1075 target,
1076 rel,
1077 dynAbsRelocAction(target, elf_file),
1078 elf_file,
1079 code,
1080 r_offset,
1081 );
1082 },
1083
1084 .PLT32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little),
1085 .PC32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little),
1086 .PC64 => mem.writeInt(i64, code[r_offset..][0..8], S + A - P, .little),
1087
1088 .GOTPCREL => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little),
1089 .GOTPC32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(GOT + A - P)), .little),
1090 .GOTPC64 => mem.writeInt(i64, code[r_offset..][0..8], GOT + A - P, .little),
1091
1092 .GOTPCRELX => {
1093 if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
1094 x86_64.relaxGotpcrelx(code[r_offset - 2 ..], t) catch break :blk;
1095 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little);
1096 return;
1097 }
1098 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little);
1099 },
1100
1101 .REX_GOTPCRELX => {
1102 if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
1103 x86_64.relaxRexGotpcrelx(code[r_offset - 3 ..], t) catch break :blk;
1104 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S + A - P)), .little);
1105 return;
1106 }
1107 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + GOT + A - P)), .little);
1108 },
1109
1110 .@"32" => mem.writeInt(u32, code[r_offset..][0..4], @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
1111 .@"32S" => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A)), .little),
1112
1113 .TPOFF32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A - TP)), .little),
1114 .TPOFF64 => mem.writeInt(i64, code[r_offset..][0..8], S + A - TP, .little),
1115
1116 .DTPOFF32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @truncate(S + A - DTP)), .little),
1117 .DTPOFF64 => mem.writeInt(i64, code[r_offset..][0..8], S + A - DTP, .little),
1118
1119 .TLSGD => {
1120 if (target.flags.has_tlsgd) {
1121 const S_ = target.tlsGdAddress(elf_file);
1122 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
1123 } else if (target.flags.has_gottp) {
1124 const S_ = target.gotTpAddress(elf_file);
1125 try x86_64.relaxTlsGdToIe(atom, &.{ rel, it.next().? }, @intCast(S_ - P), elf_file, code, r_offset);
1126 } else {
1127 try x86_64.relaxTlsGdToLe(
1128 atom,
1129 &.{ rel, it.next().? },
1130 @as(i32, @intCast(S - TP)),
1131 elf_file,
1132 code,
1133 r_offset,
1134 );
1135 }
1136 },
1137
1138 .TLSLD => {
1139 if (elf_file.got.tlsld_index) |entry_index| {
1140 const tlsld_entry = elf_file.got.entries.items[entry_index];
1141 const S_ = tlsld_entry.address(elf_file);
1142 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
1143 } else {
1144 try x86_64.relaxTlsLdToLe(
1145 atom,
1146 &.{ rel, it.next().? },
1147 @as(i32, @intCast(TP - elf_file.tlsAddress())),
1148 elf_file,
1149 code,
1150 r_offset,
1151 );
1152 }
1153 },
1154
1155 .GOTPC32_TLSDESC => {
1156 if (target.flags.has_tlsdesc) {
1157 const S_ = target.tlsDescAddress(elf_file);
1158 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
1159 } else {
1160 x86_64.relaxGotPcTlsDesc(code[r_offset - 3 ..], t) catch {
1161 var err = try diags.addErrorWithNotes(1);
1162 try err.addMsg("could not relax {s}", .{@tagName(r_type)});
1163 err.addNote("in {f}:{s} at offset 0x{x}", .{
1164 atom.file(elf_file).?.fmtPath(),
1165 atom.name(elf_file),
1166 rel.r_offset,
1167 });
1168 return error.RelaxFailure;
1169 };
1170 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S - TP)), .little);
1171 }
1172 },
1173
1174 .TLSDESC_CALL => if (!target.flags.has_tlsdesc) {
1175 // call -> nop
1176 code[r_offset..][0..2].* = .{ 0x66, 0x90 };
1177 },
1178
1179 .GOTTPOFF => {
1180 if (target.flags.has_gottp) {
1181 const S_ = target.gotTpAddress(elf_file);
1182 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S_ + A - P)), .little);
1183 } else {
1184 x86_64.relaxGotTpOff(code[r_offset - 3 ..], t);
1185 mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(S - TP)), .little);
1186 }
1187 },
1188
1189 .GOT32 => mem.writeInt(i32, code[r_offset..][0..4], @as(i32, @intCast(G + A)), .little),
1190
1191 else => try atom.reportUnhandledRelocError(rel, elf_file),
1192 }
1193 }
1194
1195 fn resolveRelocNonAlloc(
1196 atom: Atom,
1197 elf_file: *Elf,
1198 rel: elf.Elf64_Rela,
1199 target: *const Symbol,
1200 args: ResolveArgs,
1201 code: []u8,
1202 ) !void {
1203 dev.check(.x86_64_backend);
1204 const r_type: elf.R_X86_64 = @fromBackingInt(@intCast(rel.r_type()));
1205
1206 _, const A, const S, const GOT, _, _, const DTP = args;
1207
1208 var writer: Writer = .fixed(code);
1209
1210 switch (r_type) {
1211 .NONE => unreachable,
1212 .@"8" => try writer.writeInt(u8, @as(u8, @bitCast(@as(i8, @intCast(S + A)))), .little),
1213 .@"16" => try writer.writeInt(u16, @as(u16, @bitCast(@as(i16, @intCast(S + A)))), .little),
1214 .@"32" => try writer.writeInt(u32, @as(u32, @bitCast(@as(i32, @intCast(S + A)))), .little),
1215 .@"32S" => try writer.writeInt(i32, @as(i32, @intCast(S + A)), .little),
1216 .@"64" => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
1217 try writer.writeInt(u64, value, .little)
1218 else
1219 try writer.writeInt(i64, S + A, .little),
1220 .DTPOFF32 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
1221 try writer.writeInt(u64, value, .little)
1222 else
1223 try writer.writeInt(i32, @as(i32, @intCast(S + A - DTP)), .little),
1224 .DTPOFF64 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
1225 try writer.writeInt(u64, value, .little)
1226 else
1227 try writer.writeInt(i64, S + A - DTP, .little),
1228 .GOTOFF64 => try writer.writeInt(i64, S + A - GOT, .little),
1229 .GOTPC64 => try writer.writeInt(i64, GOT + A, .little),
1230 .SIZE32 => {
1231 const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
1232 try writer.writeInt(u32, @bitCast(@as(i32, @intCast(size + A))), .little);
1233 },
1234 .SIZE64 => {
1235 const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
1236 try writer.writeInt(i64, @intCast(size + A), .little);
1237 },
1238 else => try atom.reportUnhandledRelocError(rel, elf_file),
1239 }
1240 }
1241
1242 fn relaxGotpcrelx(code: []u8, t: *const std.Target) !void {
1243 dev.check(.x86_64_backend);
1244 const old_inst = disassemble(code) orelse return error.RelaxFailure;
1245 const inst: Instruction = switch (old_inst.encoding.mnemonic) {
1246 .call => try .new(old_inst.prefix, .call, &.{
1247 // TODO: hack to force imm32s in the assembler
1248 .{ .imm = .s(-129) },
1249 }, t),
1250 .jmp => try .new(old_inst.prefix, .jmp, &.{
1251 // TODO: hack to force imm32s in the assembler
1252 .{ .imm = .s(-129) },
1253 }, t),
1254 else => return error.RelaxFailure,
1255 };
1256 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
1257 const nop: Instruction = try .new(.none, .nop, &.{}, t);
1258 try encode(&.{ nop, inst }, code);
1259 }
1260
1261 fn relaxRexGotpcrelx(code: []u8, t: *const std.Target) !void {
1262 dev.check(.x86_64_backend);
1263 const old_inst = disassemble(code) orelse return error.RelaxFailure;
1264 switch (old_inst.encoding.mnemonic) {
1265 .mov => {
1266 const inst: Instruction = try .new(old_inst.prefix, .lea, &old_inst.ops, t);
1267 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
1268 try encode(&.{inst}, code);
1269 },
1270 else => return error.RelaxFailure,
1271 }
1272 }
1273
1274 fn relaxTlsGdToIe(
1275 self: Atom,
1276 rels: []const elf.Elf64_Rela,
1277 value: i32,
1278 elf_file: *Elf,
1279 code: []u8,
1280 r_offset: usize,
1281 ) !void {
1282 dev.check(.x86_64_backend);
1283 assert(rels.len == 2);
1284 const diags = &elf_file.base.comp.link_diags;
1285 const rel: elf.R_X86_64 = @fromBackingInt(@intCast(rels[1].r_type()));
1286 switch (rel) {
1287 .PC32,
1288 .PLT32,
1289 => {
1290 var insts = [_]u8{
1291 0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax
1292 0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax
1293 };
1294 std.mem.writeInt(i32, insts[12..][0..4], value - 12, .little);
1295 @memcpy(code[r_offset - 4 ..][0..insts.len], &insts);
1296 },
1297
1298 else => {
1299 var err = try diags.addErrorWithNotes(1);
1300 try err.addMsg("TODO: rewrite {f} when followed by {f}", .{
1301 relocation.fmtRelocType(rels[0].r_type(), .x86_64),
1302 relocation.fmtRelocType(rels[1].r_type(), .x86_64),
1303 });
1304 err.addNote("in {f}:{s} at offset 0x{x}", .{
1305 self.file(elf_file).?.fmtPath(),
1306 self.name(elf_file),
1307 rels[0].r_offset,
1308 });
1309 return error.RelaxFailure;
1310 },
1311 }
1312 }
1313
1314 fn relaxTlsLdToLe(
1315 self: Atom,
1316 rels: []const elf.Elf64_Rela,
1317 value: i32,
1318 elf_file: *Elf,
1319 code: []u8,
1320 r_offset: usize,
1321 ) !void {
1322 dev.check(.x86_64_backend);
1323 assert(rels.len == 2);
1324 const diags = &elf_file.base.comp.link_diags;
1325 const rel: elf.R_X86_64 = @fromBackingInt(@intCast(rels[1].r_type()));
1326 switch (rel) {
1327 .PC32,
1328 .PLT32,
1329 => {
1330 var insts = [_]u8{
1331 0x31, 0xc0, // xor %eax, %eax
1332 0x64, 0x48, 0x8b, 0, // mov %fs:(%rax), %rax
1333 0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
1334 };
1335 std.mem.writeInt(i32, insts[8..][0..4], value, .little);
1336 @memcpy(code[r_offset - 3 ..][0..insts.len], &insts);
1337 },
1338
1339 .GOTPCREL,
1340 .GOTPCRELX,
1341 => {
1342 var insts = [_]u8{
1343 0x31, 0xc0, // xor %eax, %eax
1344 0x64, 0x48, 0x8b, 0, // mov %fs:(%rax), %rax
1345 0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax
1346 0x90, // nop
1347 };
1348 std.mem.writeInt(i32, insts[8..][0..4], value, .little);
1349 @memcpy(code[r_offset - 3 ..][0..insts.len], &insts);
1350 },
1351
1352 else => {
1353 var err = try diags.addErrorWithNotes(1);
1354 try err.addMsg("TODO: rewrite {f} when followed by {f}", .{
1355 relocation.fmtRelocType(rels[0].r_type(), .x86_64),
1356 relocation.fmtRelocType(rels[1].r_type(), .x86_64),
1357 });
1358 err.addNote("in {f}:{s} at offset 0x{x}", .{
1359 self.file(elf_file).?.fmtPath(),
1360 self.name(elf_file),
1361 rels[0].r_offset,
1362 });
1363 return error.RelaxFailure;
1364 },
1365 }
1366 }
1367
1368 fn canRelaxGotTpOff(code: []const u8, t: *const std.Target) bool {
1369 dev.check(.x86_64_backend);
1370 const old_inst = disassemble(code) orelse return false;
1371 switch (old_inst.encoding.mnemonic) {
1372 .mov => {
1373 const inst = Instruction.new(old_inst.prefix, .mov, &.{
1374 old_inst.ops[0],
1375 // TODO: hack to force imm32s in the assembler
1376 .{ .imm = .s(-129) },
1377 }, t) catch return false;
1378 var trash: Writer.Discarding = .init(&.{});
1379 inst.encode(&trash.writer, .{}) catch return false;
1380 return true;
1381 },
1382 else => return false,
1383 }
1384 }
1385
1386 fn relaxGotTpOff(code: []u8, t: *const std.Target) void {
1387 dev.check(.x86_64_backend);
1388 const old_inst = disassemble(code) orelse unreachable;
1389 switch (old_inst.encoding.mnemonic) {
1390 .mov => {
1391 const inst = Instruction.new(old_inst.prefix, .mov, &.{
1392 old_inst.ops[0],
1393 // TODO: hack to force imm32s in the assembler
1394 .{ .imm = .s(-129) },
1395 }, t) catch unreachable;
1396 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
1397 encode(&.{inst}, code) catch unreachable;
1398 },
1399 else => unreachable,
1400 }
1401 }
1402
1403 fn relaxGotPcTlsDesc(code: []u8, target: *const std.Target) !void {
1404 dev.check(.x86_64_backend);
1405 const old_inst = disassemble(code) orelse return error.RelaxFailure;
1406 switch (old_inst.encoding.mnemonic) {
1407 .lea => {
1408 const inst: Instruction = try .new(old_inst.prefix, .mov, &.{
1409 old_inst.ops[0],
1410 // TODO: hack to force imm32s in the assembler
1411 .{ .imm = .s(-129) },
1412 }, target);
1413 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
1414 try encode(&.{inst}, code);
1415 },
1416 else => return error.RelaxFailure,
1417 }
1418 }
1419
1420 fn relaxTlsGdToLe(
1421 self: Atom,
1422 rels: []const elf.Elf64_Rela,
1423 value: i32,
1424 elf_file: *Elf,
1425 code: []u8,
1426 r_offset: usize,
1427 ) !void {
1428 dev.check(.x86_64_backend);
1429 assert(rels.len == 2);
1430 const diags = &elf_file.base.comp.link_diags;
1431 const rel: elf.R_X86_64 = @fromBackingInt(@intCast(rels[1].r_type()));
1432 switch (rel) {
1433 .PC32,
1434 .PLT32,
1435 .GOTPCREL,
1436 .GOTPCRELX,
1437 => {
1438 var insts = [_]u8{
1439 0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax
1440 0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
1441 };
1442 std.mem.writeInt(i32, insts[12..][0..4], value, .little);
1443 @memcpy(code[r_offset - 4 ..][0..insts.len], &insts);
1444 relocs_log.debug(" relaxing {f} and {f}", .{
1445 relocation.fmtRelocType(rels[0].r_type(), .x86_64),
1446 relocation.fmtRelocType(rels[1].r_type(), .x86_64),
1447 });
1448 },
1449
1450 else => {
1451 var err = try diags.addErrorWithNotes(1);
1452 try err.addMsg("fatal linker error: rewrite {f} when followed by {f}", .{
1453 relocation.fmtRelocType(rels[0].r_type(), .x86_64),
1454 relocation.fmtRelocType(rels[1].r_type(), .x86_64),
1455 });
1456 err.addNote("in {f}:{s} at offset 0x{x}", .{
1457 self.file(elf_file).?.fmtPath(),
1458 self.name(elf_file),
1459 rels[0].r_offset,
1460 });
1461 return error.RelaxFailure;
1462 },
1463 }
1464 }
1465
1466 fn disassemble(code: []const u8) ?Instruction {
1467 var disas = Disassembler.init(code);
1468 const inst = disas.next() catch return null;
1469 return inst;
1470 }
1471
1472 fn encode(insts: []const Instruction, code: []u8) !void {
1473 var writer: Writer = .fixed(code);
1474 for (insts) |inst| try inst.encode(&writer, .{});
1475 }
1476
1477 const bits = @import("../../codegen/x86_64/bits.zig");
1478 const encoder = @import("../../codegen/x86_64/encoder.zig");
1479 const Disassembler = @import("../../codegen/x86_64/Disassembler.zig");
1480 const Immediate = Instruction.Immediate;
1481 const Instruction = encoder.Instruction;
1482};
1483
1484const aarch64 = struct {
1485 fn scanReloc(
1486 atom: Atom,
1487 elf_file: *Elf,
1488 rel: elf.Elf64_Rela,
1489 symbol: *Symbol,
1490 code: ?[]const u8,
1491 it: *RelocsIterator,
1492 ) !void {
1493 _ = code;
1494 _ = it;
1495
1496 const r_type: elf.R_AARCH64 = @fromBackingInt(@intCast(rel.r_type()));
1497 const is_dyn_lib = elf_file.isEffectivelyDynLib();
1498
1499 switch (r_type) {
1500 .ABS64 => {
1501 try atom.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file);
1502 },
1503 .ADR_PREL_PG_HI21 => {
1504 try atom.scanReloc(symbol, rel, pcRelocAction(symbol, elf_file), elf_file);
1505 },
1506 .ADR_GOT_PAGE => {
1507 // TODO: relax if possible
1508 symbol.flags.needs_got = true;
1509 },
1510 .LD64_GOT_LO12_NC,
1511 .LD64_GOTPAGE_LO15,
1512 => {
1513 symbol.flags.needs_got = true;
1514 },
1515 .CALL26,
1516 .JUMP26,
1517 => {
1518 if (symbol.flags.import) {
1519 symbol.flags.needs_plt = true;
1520 }
1521 },
1522 .TLSLE_ADD_TPREL_HI12,
1523 .TLSLE_ADD_TPREL_LO12_NC,
1524 => {
1525 if (is_dyn_lib) try atom.reportPicError(symbol, rel, elf_file);
1526 },
1527 .TLSIE_ADR_GOTTPREL_PAGE21,
1528 .TLSIE_LD64_GOTTPREL_LO12_NC,
1529 => {
1530 symbol.flags.needs_gottp = true;
1531 },
1532 .TLSGD_ADR_PAGE21,
1533 .TLSGD_ADD_LO12_NC,
1534 => {
1535 symbol.flags.needs_tlsgd = true;
1536 },
1537 .TLSDESC_ADR_PAGE21,
1538 .TLSDESC_LD64_LO12,
1539 .TLSDESC_ADD_LO12,
1540 .TLSDESC_CALL,
1541 => {
1542 const should_relax = elf_file.base.isStatic() or (!is_dyn_lib and !symbol.flags.import);
1543 if (!should_relax) {
1544 symbol.flags.needs_tlsdesc = true;
1545 }
1546 },
1547 .ADD_ABS_LO12_NC,
1548 .ADR_PREL_LO21,
1549 .CONDBR19,
1550 .LDST128_ABS_LO12_NC,
1551 .LDST16_ABS_LO12_NC,
1552 .LDST32_ABS_LO12_NC,
1553 .LDST64_ABS_LO12_NC,
1554 .LDST8_ABS_LO12_NC,
1555 .PREL32,
1556 .PREL64,
1557 => {},
1558 else => try atom.reportUnhandledRelocError(rel, elf_file),
1559 }
1560 }
1561
1562 fn resolveRelocAlloc(
1563 atom: Atom,
1564 elf_file: *Elf,
1565 rel: elf.Elf64_Rela,
1566 target: *const Symbol,
1567 args: ResolveArgs,
1568 it: *RelocsIterator,
1569 code_buffer: []u8,
1570 ) (error{ UnexpectedRemainder, DivisionByZero } || RelocError)!void {
1571 _ = it;
1572
1573 const diags = &elf_file.base.comp.link_diags;
1574 const r_type: elf.R_AARCH64 = @fromBackingInt(@intCast(rel.r_type()));
1575 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
1576 const code = code_buffer[r_offset..][0..4];
1577 const file_ptr = atom.file(elf_file).?;
1578
1579 const P, const A, const S, const GOT, const G, const TP, const DTP = args;
1580 _ = DTP;
1581
1582 switch (r_type) {
1583 .NONE => unreachable,
1584 .ABS64 => {
1585 try atom.resolveDynAbsReloc(
1586 target,
1587 rel,
1588 dynAbsRelocAction(target, elf_file),
1589 elf_file,
1590 code_buffer,
1591 r_offset,
1592 );
1593 },
1594 .CALL26,
1595 .JUMP26,
1596 => {
1597 const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
1598 const th = atom.thunk(elf_file);
1599 const target_index = file_ptr.resolveSymbol(rel.r_sym(), elf_file);
1600 const S_ = th.targetAddress(target_index, elf_file);
1601 break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
1602 };
1603 util.writeBranchImm(disp, code);
1604 },
1605 .CONDBR19 => {
1606 const value = math.cast(i19, S + A - P) orelse return error.Overflow;
1607 util.writeCondBrImm(value, code);
1608 },
1609 .PREL32 => {
1610 const value = math.cast(i32, S + A - P) orelse return error.Overflow;
1611 mem.writeInt(u32, code, @bitCast(value), .little);
1612 },
1613 .PREL64 => {
1614 const value = S + A - P;
1615 mem.writeInt(u64, code_buffer[r_offset..][0..8], @bitCast(value), .little);
1616 },
1617 .ADR_PREL_LO21 => {
1618 const value = math.cast(i21, S + A - P) orelse return error.Overflow;
1619 util.writeAdrInst(value, code);
1620 },
1621 .ADR_PREL_PG_HI21 => {
1622 // TODO: check for relaxation of ADRP+ADD
1623 util.writeAdrInst(try util.calcNumberOfPages(P, S + A), code);
1624 },
1625 .ADR_GOT_PAGE => if (target.flags.has_got) {
1626 util.writeAdrInst(try util.calcNumberOfPages(P, G + GOT + A), code);
1627 } else {
1628 // TODO: relax
1629 var err = try diags.addErrorWithNotes(1);
1630 try err.addMsg("TODO: relax ADR_GOT_PAGE", .{});
1631 err.addNote("in {f}:{s} at offset 0x{x}", .{
1632 atom.file(elf_file).?.fmtPath(),
1633 atom.name(elf_file),
1634 r_offset,
1635 });
1636 },
1637 .LD64_GOT_LO12_NC => {
1638 assert(target.flags.has_got);
1639 const taddr = @as(u64, @intCast(G + GOT + A));
1640 util.writeLoadStoreRegInst(@divExact(@as(u12, @truncate(taddr)), 8), code);
1641 },
1642 .ADD_ABS_LO12_NC => {
1643 const taddr = @as(u64, @intCast(S + A));
1644 util.writeAddImmInst(@truncate(taddr), code);
1645 },
1646 .LDST8_ABS_LO12_NC,
1647 .LDST16_ABS_LO12_NC,
1648 .LDST32_ABS_LO12_NC,
1649 .LDST64_ABS_LO12_NC,
1650 .LDST128_ABS_LO12_NC,
1651 => {
1652 // TODO: NC means no overflow check
1653 const taddr = @as(u64, @intCast(S + A));
1654 const off: u12 = switch (r_type) {
1655 .LDST8_ABS_LO12_NC => @truncate(taddr),
1656 .LDST16_ABS_LO12_NC => @divExact(@as(u12, @truncate(taddr)), 2),
1657 .LDST32_ABS_LO12_NC => @divExact(@as(u12, @truncate(taddr)), 4),
1658 .LDST64_ABS_LO12_NC => @divExact(@as(u12, @truncate(taddr)), 8),
1659 .LDST128_ABS_LO12_NC => @divExact(@as(u12, @truncate(taddr)), 16),
1660 else => unreachable,
1661 };
1662 util.writeLoadStoreRegInst(off, code);
1663 },
1664 .TLSLE_ADD_TPREL_HI12 => {
1665 const value = math.cast(i12, (S + A - TP) >> 12) orelse
1666 return error.Overflow;
1667 util.writeAddImmInst(@bitCast(value), code);
1668 },
1669 .TLSLE_ADD_TPREL_LO12_NC => {
1670 const value: i12 = @truncate(S + A - TP);
1671 util.writeAddImmInst(@bitCast(value), code);
1672 },
1673 .TLSIE_ADR_GOTTPREL_PAGE21 => {
1674 const S_ = target.gotTpAddress(elf_file);
1675 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1676 util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code);
1677 },
1678 .TLSIE_LD64_GOTTPREL_LO12_NC => {
1679 const S_ = target.gotTpAddress(elf_file);
1680 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1681 const off: u12 = try math.divExact(u12, @truncate(@as(u64, @bitCast(S_ + A))), 8);
1682 util.writeLoadStoreRegInst(off, code);
1683 },
1684 .TLSGD_ADR_PAGE21 => {
1685 const S_ = target.tlsGdAddress(elf_file);
1686 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1687 util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code);
1688 },
1689 .TLSGD_ADD_LO12_NC => {
1690 const S_ = target.tlsGdAddress(elf_file);
1691 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1692 const off: u12 = @truncate(@as(u64, @bitCast(S_ + A)));
1693 util.writeAddImmInst(off, code);
1694 },
1695 .TLSDESC_ADR_PAGE21 => {
1696 if (target.flags.has_tlsdesc) {
1697 const S_ = target.tlsDescAddress(elf_file);
1698 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1699 util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code);
1700 } else {
1701 relocs_log.debug(" relaxing adrp => nop", .{});
1702 util.encoding.Instruction.nop().write(code);
1703 }
1704 },
1705 .TLSDESC_LD64_LO12 => {
1706 if (target.flags.has_tlsdesc) {
1707 const S_ = target.tlsDescAddress(elf_file);
1708 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1709 const off: u12 = try math.divExact(u12, @truncate(@as(u64, @bitCast(S_ + A))), 8);
1710 util.writeLoadStoreRegInst(off, code);
1711 } else {
1712 relocs_log.debug(" relaxing ldr => nop", .{});
1713 util.encoding.Instruction.nop().write(code);
1714 }
1715 },
1716 .TLSDESC_ADD_LO12 => {
1717 if (target.flags.has_tlsdesc) {
1718 const S_ = target.tlsDescAddress(elf_file);
1719 relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A });
1720 const off: u12 = @truncate(@as(u64, @bitCast(S_ + A)));
1721 util.writeAddImmInst(off, code);
1722 } else {
1723 relocs_log.debug(" relaxing add => movz(x0, {x})", .{S + A - TP});
1724 const value: u16 = @bitCast(math.cast(i16, (S + A - TP) >> 16) orelse return error.Overflow);
1725 util.encoding.Instruction.movz(.x0, value, .{ .lsl = .@"16" }).write(code);
1726 }
1727 },
1728 .TLSDESC_CALL => if (!target.flags.has_tlsdesc) {
1729 relocs_log.debug(" relaxing br => movk(x0, {x})", .{S + A - TP});
1730 const value: u16 = @bitCast(@as(i16, @truncate(S + A - TP)));
1731 util.encoding.Instruction.movk(.x0, value, .{}).write(code);
1732 },
1733 else => try atom.reportUnhandledRelocError(rel, elf_file),
1734 }
1735 }
1736
1737 fn resolveRelocNonAlloc(
1738 atom: Atom,
1739 elf_file: *Elf,
1740 rel: elf.Elf64_Rela,
1741 target: *const Symbol,
1742 args: ResolveArgs,
1743 code: []u8,
1744 ) !void {
1745 const r_type: elf.R_AARCH64 = @fromBackingInt(@intCast(rel.r_type()));
1746
1747 _, const A, const S, _, _, _, _ = args;
1748
1749 var writer: Writer = .fixed(code);
1750 switch (r_type) {
1751 .NONE => unreachable,
1752 .ABS32 => try writer.writeInt(i32, @as(i32, @intCast(S + A)), .little),
1753 .ABS64 => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
1754 try writer.writeInt(u64, value, .little)
1755 else
1756 try writer.writeInt(i64, S + A, .little),
1757 else => try atom.reportUnhandledRelocError(rel, elf_file),
1758 }
1759 }
1760
1761 const util = @import("../aarch64.zig");
1762};
1763
1764const riscv = struct {
1765 fn scanReloc(
1766 atom: Atom,
1767 elf_file: *Elf,
1768 rel: elf.Elf64_Rela,
1769 symbol: *Symbol,
1770 code: ?[]const u8,
1771 it: *RelocsIterator,
1772 ) !void {
1773 _ = code;
1774 _ = it;
1775
1776 const r_type: elf.R_RISCV = @fromBackingInt(@intCast(rel.r_type()));
1777
1778 switch (r_type) {
1779 .@"32" => try atom.scanReloc(symbol, rel, absRelocAction(symbol, elf_file), elf_file),
1780 .@"64" => try atom.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file),
1781 .HI20 => try atom.scanReloc(symbol, rel, absRelocAction(symbol, elf_file), elf_file),
1782
1783 .CALL_PLT => if (symbol.flags.import) {
1784 symbol.flags.needs_plt = true;
1785 },
1786 .GOT_HI20 => symbol.flags.needs_got = true,
1787
1788 .TPREL_HI20,
1789 .TPREL_LO12_I,
1790 .TPREL_LO12_S,
1791 .TPREL_ADD,
1792
1793 .PCREL_HI20,
1794 .PCREL_LO12_I,
1795 .PCREL_LO12_S,
1796 .LO12_I,
1797 .LO12_S,
1798 .ADD32,
1799 .SUB32,
1800
1801 .SUB_ULEB128,
1802 .SET_ULEB128,
1803 => {},
1804
1805 else => try atom.reportUnhandledRelocError(rel, elf_file),
1806 }
1807 }
1808
1809 fn resolveRelocAlloc(
1810 atom: Atom,
1811 elf_file: *Elf,
1812 rel: elf.Elf64_Rela,
1813 target: *const Symbol,
1814 args: ResolveArgs,
1815 it: *RelocsIterator,
1816 code: []u8,
1817 ) !void {
1818 const diags = &elf_file.base.comp.link_diags;
1819 const r_type: elf.R_RISCV = @fromBackingInt(@intCast(rel.r_type()));
1820 const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
1821
1822 const P, const A, const S, const GOT, const G, const TP, const DTP = args;
1823 _ = TP;
1824 _ = DTP;
1825
1826 switch (r_type) {
1827 .NONE => unreachable,
1828
1829 .@"32" => mem.writeInt(u32, code[r_offset..][0..4], @as(u32, @truncate(@as(u64, @intCast(S + A)))), .little),
1830
1831 .@"64" => {
1832 try atom.resolveDynAbsReloc(
1833 target,
1834 rel,
1835 dynAbsRelocAction(target, elf_file),
1836 elf_file,
1837 code,
1838 r_offset,
1839 );
1840 },
1841
1842 .ADD32 => riscv_util.writeAddend(i32, .add, code[r_offset..][0..4], S + A),
1843 .SUB32 => riscv_util.writeAddend(i32, .sub, code[r_offset..][0..4], S + A),
1844
1845 .HI20 => {
1846 const value: u32 = @bitCast(math.cast(i32, S + A) orelse return error.Overflow);
1847 riscv_util.writeInstU(code[r_offset..][0..4], value);
1848 },
1849
1850 .GOT_HI20 => {
1851 assert(target.flags.has_got);
1852 const disp: u32 = @bitCast(math.cast(i32, G + GOT + A - P) orelse return error.Overflow);
1853 riscv_util.writeInstU(code[r_offset..][0..4], disp);
1854 },
1855
1856 .CALL_PLT => {
1857 // TODO: relax
1858 const disp: u32 = @bitCast(math.cast(i32, S + A - P) orelse return error.Overflow);
1859 riscv_util.writeInstU(code[r_offset..][0..4], disp); // auipc
1860 riscv_util.writeInstI(code[r_offset + 4 ..][0..4], disp); // jalr
1861 },
1862
1863 .PCREL_HI20 => {
1864 const disp: u32 = @bitCast(math.cast(i32, S + A - P) orelse return error.Overflow);
1865 riscv_util.writeInstU(code[r_offset..][0..4], disp);
1866 },
1867
1868 .PCREL_LO12_I,
1869 .PCREL_LO12_S,
1870 => {
1871 assert(A == 0); // according to the spec
1872 // We need to find the paired reloc for this relocation.
1873 const file_ptr = atom.file(elf_file).?;
1874 const atom_addr = atom.address(elf_file);
1875 const pos = it.pos;
1876 const pair = while (it.prev()) |pair| {
1877 if (S == atom_addr + @as(i64, @intCast(pair.r_offset))) break pair;
1878 } else {
1879 // TODO: implement searching forward
1880 var err = try diags.addErrorWithNotes(1);
1881 try err.addMsg("TODO: find HI20 paired reloc scanning forward", .{});
1882 err.addNote("in {f}:{s} at offset 0x{x}", .{
1883 atom.file(elf_file).?.fmtPath(),
1884 atom.name(elf_file),
1885 rel.r_offset,
1886 });
1887 return error.RelocFailure;
1888 };
1889 it.pos = pos;
1890 const target_ref_ = file_ptr.resolveSymbol(pair.r_sym(), elf_file);
1891 const target_ = elf_file.symbol(target_ref_).?;
1892 const S_ = target_.address(.{}, elf_file);
1893 const A_ = pair.r_addend;
1894 const P_ = atom_addr + @as(i64, @intCast(pair.r_offset));
1895 const G_ = target_.gotAddress(elf_file) - GOT;
1896 const disp = switch (@as(elf.R_RISCV, @fromBackingInt(@intCast(pair.r_type())))) {
1897 .PCREL_HI20 => math.cast(i32, S_ + A_ - P_) orelse return error.Overflow,
1898 .GOT_HI20 => math.cast(i32, G_ + GOT + A_ - P_) orelse return error.Overflow,
1899 else => unreachable,
1900 };
1901 relocs_log.debug(" [{x} => {x}]", .{ P_, disp + P_ });
1902 switch (r_type) {
1903 .PCREL_LO12_I => riscv_util.writeInstI(code[r_offset..][0..4], @bitCast(disp)),
1904 .PCREL_LO12_S => riscv_util.writeInstS(code[r_offset..][0..4], @bitCast(disp)),
1905 else => unreachable,
1906 }
1907 },
1908
1909 .LO12_I,
1910 .LO12_S,
1911 => {
1912 const disp: u32 = @bitCast(math.cast(i32, S + A) orelse return error.Overflow);
1913 switch (r_type) {
1914 .LO12_I => riscv_util.writeInstI(code[r_offset..][0..4], disp),
1915 .LO12_S => riscv_util.writeInstS(code[r_offset..][0..4], disp),
1916 else => unreachable,
1917 }
1918 },
1919
1920 .TPREL_HI20 => {
1921 const target_addr: u32 = @intCast(target.address(.{}, elf_file));
1922 const val: i32 = @intCast(S + A - target_addr);
1923 riscv_util.writeInstU(code[r_offset..][0..4], @bitCast(val));
1924 },
1925
1926 .TPREL_LO12_I,
1927 .TPREL_LO12_S,
1928 => {
1929 const target_addr: u32 = @intCast(target.address(.{}, elf_file));
1930 const val: i32 = @intCast(S + A - target_addr);
1931 switch (r_type) {
1932 .TPREL_LO12_I => riscv_util.writeInstI(code[r_offset..][0..4], @bitCast(val)),
1933 .TPREL_LO12_S => riscv_util.writeInstS(code[r_offset..][0..4], @bitCast(val)),
1934 else => unreachable,
1935 }
1936 },
1937
1938 .TPREL_ADD => {
1939 // TODO: annotates an ADD instruction that can be removed when TPREL is relaxed
1940 },
1941
1942 else => try atom.reportUnhandledRelocError(rel, elf_file),
1943 }
1944 }
1945
1946 fn resolveRelocNonAlloc(
1947 atom: Atom,
1948 elf_file: *Elf,
1949 rel: elf.Elf64_Rela,
1950 target: *const Symbol,
1951 args: ResolveArgs,
1952 code: []u8,
1953 ) !void {
1954 const r_type: elf.R_RISCV = @fromBackingInt(@intCast(rel.r_type()));
1955
1956 _, const A, const S, const GOT, _, _, const DTP = args;
1957 _ = GOT;
1958 _ = DTP;
1959
1960 switch (r_type) {
1961 .NONE => unreachable,
1962
1963 .@"32" => mem.writeInt(i32, code[0..4], @intCast(S + A), .little),
1964 .@"64" => if (atom.debugTombstoneValue(target.*, elf_file)) |value|
1965 mem.writeInt(u64, code[0..8], value, .little)
1966 else
1967 mem.writeInt(i64, code[0..8], S + A, .little),
1968 .ADD8 => riscv_util.writeAddend(i8, .add, code[0..1], S + A),
1969 .SUB8 => riscv_util.writeAddend(i8, .sub, code[0..1], S + A),
1970 .ADD16 => riscv_util.writeAddend(i16, .add, code[0..2], S + A),
1971 .SUB16 => riscv_util.writeAddend(i16, .sub, code[0..2], S + A),
1972 .ADD32 => riscv_util.writeAddend(i32, .add, code[0..4], S + A),
1973 .SUB32 => riscv_util.writeAddend(i32, .sub, code[0..4], S + A),
1974 .ADD64 => riscv_util.writeAddend(i64, .add, code[0..8], S + A),
1975 .SUB64 => riscv_util.writeAddend(i64, .sub, code[0..8], S + A),
1976
1977 .SET8 => mem.writeInt(i8, code[0..1], @as(i8, @truncate(S + A)), .little),
1978 .SET16 => mem.writeInt(i16, code[0..2], @as(i16, @truncate(S + A)), .little),
1979 .SET32 => mem.writeInt(i32, code[0..4], @as(i32, @truncate(S + A)), .little),
1980
1981 .SET6 => riscv_util.writeSetSub6(.set, code[0..1], S + A),
1982 .SUB6 => riscv_util.writeSetSub6(.sub, code[0..1], S + A),
1983
1984 .SET_ULEB128 => riscv_util.writeSetUleb(code, S + A),
1985 .SUB_ULEB128 => riscv_util.writeSubUleb(code, S - A),
1986
1987 else => try atom.reportUnhandledRelocError(rel, elf_file),
1988 }
1989 }
1990
1991 const riscv_util = @import("../riscv.zig");
1992};
1993
1994const ResolveArgs = struct { i64, i64, i64, i64, i64, i64, i64 };
1995
1996const RelocError = error{
1997 Overflow,
1998 OutOfMemory,
1999 NoSpaceLeft,
2000 RelocFailure,
2001 RelaxFailure,
2002 UnsupportedCpuArch,
2003};
2004
2005const RelocsIterator = struct {
2006 relocs: []const elf.Elf64_Rela,
2007 pos: i64 = -1,
2008
2009 fn next(it: *RelocsIterator) ?elf.Elf64_Rela {
2010 it.pos += 1;
2011 if (it.pos >= it.relocs.len) return null;
2012 return it.relocs[@intCast(it.pos)];
2013 }
2014
2015 fn prev(it: *RelocsIterator) ?elf.Elf64_Rela {
2016 if (it.pos == -1) return null;
2017 const rel = it.relocs[@intCast(it.pos)];
2018 it.pos -= 1;
2019 return rel;
2020 }
2021
2022 fn skip(it: *RelocsIterator, num: usize) void {
2023 assert(num > 0);
2024 it.pos += @intCast(num);
2025 }
2026};
2027
2028pub const Extra = struct {
2029 /// Index of the range extension thunk of this atom.
2030 thunk: u32 = 0,
2031
2032 /// Start index of FDEs referencing this atom.
2033 fde_start: u32 = 0,
2034
2035 /// Count of FDEs referencing this atom.
2036 fde_count: u32 = 0,
2037
2038 /// Start index of relocations belonging to this atom.
2039 rel_index: u32 = 0,
2040
2041 /// Count of relocations belonging to this atom.
2042 rel_count: u32 = 0,
2043
2044 pub const AsOptionals = struct {
2045 thunk: ?u32 = null,
2046 fde_start: ?u32 = null,
2047 fde_count: ?u32 = null,
2048 rel_index: ?u32 = null,
2049 rel_count: ?u32 = null,
2050 };
2051};
2052
2053const std = @import("std");
2054const assert = std.debug.assert;
2055const elf = std.elf;
2056const log = std.log.scoped(.link);
2057const math = std.math;
2058const mem = std.mem;
2059const relocs_log = std.log.scoped(.link_relocs);
2060const Allocator = mem.Allocator;
2061const Writer = std.Io.Writer;
2062
2063const eh_frame = @import("eh_frame.zig");
2064const relocation = @import("relocation.zig");
2065
2066const Atom = @This();
2067const Elf = @import("../Elf.zig");
2068const Fde = eh_frame.Fde;
2069const File = @import("file.zig").File;
2070const Object = @import("Object.zig");
2071const Symbol = @import("Symbol.zig");
2072const Thunk = @import("Thunk.zig");
2073const ZigObject = @import("ZigObject.zig");
2074const dev = @import("../../dev.zig");