authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-09-28 18:35:26+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-09-28 18:35:26+02:00
log89c2151a97ca684924c5b29c0b78367e5ed11bcd
tree7dd5e5d6fb6bbc9aa33aa02e21f41e19741f429b
parent91f2e66bf9c010bd09a41e21aeb3e7eda80dd49f

elf: move logic for extracing atom's code into input files


6 files changed, 237 insertions(+), 50 deletions(-)

src/link/Elf.zig+9-7
......@@ -1321,16 +1321,14 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
13211321 // Beyond this point, everything has been allocated a virtual address and we can resolve
13221322 // the relocations, and commit objects to file.
13231323 if (self.zig_module_index) |index| {
1324 for (self.file(index).?.zig_module.atoms.keys()) |atom_index| {
1324 const zig_module = self.file(index).?.zig_module;
1325 for (zig_module.atoms.keys()) |atom_index| {
13251326 const atom_ptr = self.atom(atom_index).?;
13261327 if (!atom_ptr.flags.alive) continue;
1328 const code = try zig_module.codeAlloc(self, atom_index);
1329 defer gpa.free(code);
13271330 const shdr = &self.shdrs.items[atom_ptr.outputShndx().?];
13281331 const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr;
1329 const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow;
1330 const code = try gpa.alloc(u8, size);
1331 defer gpa.free(code);
1332 const amt = try self.base.file.?.preadAll(code, file_offset);
1333 if (amt != code.len) return error.InputOutput;
13341332 try atom_ptr.resolveRelocs(self, code);
13351333 try self.base.file.?.pwriteAll(code, file_offset);
13361334 }
......@@ -1864,7 +1862,7 @@ fn writeObjects(self: *Elf) !void {
18641862
18651863 const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr;
18661864 log.debug("writing atom({d}) at 0x{x}", .{ atom_ptr.atom_index, file_offset });
1867 const code = try atom_ptr.codeInObjectUncompressAlloc(self);
1865 const code = try object.codeDecompressAlloc(self, atom_ptr.atom_index);
18681866 defer gpa.free(code);
18691867
18701868 try atom_ptr.resolveRelocs(self, code);
......@@ -3905,6 +3903,10 @@ pub fn calcImageBase(self: Elf) u64 {
39053903 };
39063904}
39073905
3906pub fn isStatic(self: Elf) bool {
3907 return self.base.options.link_mode == .Static;
3908}
3909
39083910pub fn isDynLib(self: Elf) bool {
39093911 return self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic;
39103912}
src/link/Elf/Atom.zig+152-35
......@@ -59,38 +59,6 @@ pub fn outputShndx(self: Atom) ?u16 {
5959 return self.output_section_index;
6060}
6161
62pub fn codeInObject(self: Atom, elf_file: *Elf) error{Overflow}![]const u8 {
63 const object = self.file(elf_file).?.object;
64 return object.shdrContents(self.input_section_index);
65}
66
67/// Returns atom's code and optionally uncompresses data if required (for compressed sections).
68/// Caller owns the memory.
69pub fn codeInObjectUncompressAlloc(self: Atom, elf_file: *Elf) ![]u8 {
70 const gpa = elf_file.base.allocator;
71 const data = try self.codeInObject(elf_file);
72 const shdr = self.inputShdr(elf_file);
73 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
74 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
75 switch (chdr.ch_type) {
76 .ZLIB => {
77 var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]);
78 var zlib_stream = std.compress.zlib.decompressStream(gpa, stream.reader()) catch
79 return error.InputOutput;
80 defer zlib_stream.deinit();
81 const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow;
82 const decomp = try gpa.alloc(u8, size);
83 const nread = zlib_stream.reader().readAll(decomp) catch return error.InputOutput;
84 if (nread != decomp.len) {
85 return error.InputOutput;
86 }
87 return decomp;
88 },
89 else => @panic("TODO unhandled compression scheme"),
90 }
91 } else return gpa.dupe(u8, data);
92}
93
9462pub fn priority(self: Atom, elf_file: *Elf) u64 {
9563 const index = self.file(elf_file).?.index();
9664 return (@as(u64, @intCast(index)) << 32) | @as(u64, @intCast(self.input_section_index));
......@@ -327,7 +295,15 @@ pub fn freeRelocs(self: Atom, elf_file: *Elf) void {
327295 zig_module.relocs.items[self.relocs_section_index].clearRetainingCapacity();
328296}
329297
330pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
298pub fn scanRelocsRequiresCode(self: Atom, elf_file: *Elf) error{Overflow}!bool {
299 for (try self.relocs(elf_file)) |rel| {
300 if (rel.r_type() == elf.R_X86_64_GOTTPOFF) return true;
301 }
302 return false;
303}
304
305pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype) !void {
306 const is_dyn_lib = elf_file.isDynLib();
331307 const file_ptr = self.file(elf_file).?;
332308 const rels = try self.relocs(elf_file);
333309 var i: usize = 0;
......@@ -391,7 +367,38 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
391367 elf.R_X86_64_TPOFF32,
392368 elf.R_X86_64_TPOFF64,
393369 => {
394 // if (is_shared) self.picError(symbol, rel, elf_file);
370 if (is_dyn_lib) {
371 // TODO
372 // self.picError(symbol, rel, elf_file);
373 }
374 },
375
376 elf.R_X86_64_TLSGD => {
377 // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr
378
379 if (elf_file.isStatic() or
380 (!symbol.flags.import and !is_dyn_lib))
381 {
382 // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a
383 // We skip the next relocation.
384 i += 1;
385 } else if (!symbol.flags.import and is_dyn_lib) {
386 symbol.flags.needs_gottp = true;
387 i += 1;
388 } else {
389 symbol.flags.needs_tlsgd = true;
390 }
391 },
392
393 elf.R_X86_64_GOTTPOFF => {
394 const should_relax = blk: {
395 // if (!elf_file.options.relax or is_shared or symbol.flags.import) break :blk false;
396 if (!x86_64.canRelaxGotTpOff(code.?[rel.r_offset - 3 ..])) break :blk false;
397 break :blk true;
398 };
399 if (!should_relax) {
400 symbol.flags.needs_gottp = true;
401 }
395402 },
396403
397404 else => {
......@@ -446,7 +453,10 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
446453 var stream = std.io.fixedBufferStream(code);
447454 const cwriter = stream.writer();
448455
449 for (try self.relocs(elf_file)) |rel| {
456 const rels = try self.relocs(elf_file);
457 var i: usize = 0;
458 while (i < rels.len) : (i += 1) {
459 const rel = rels[i];
450460 const r_type = rel.r_type();
451461 if (r_type == elf.R_X86_64_NONE) continue;
452462
......@@ -531,6 +541,39 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
531541 elf.R_X86_64_TPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A - TP))),
532542 elf.R_X86_64_TPOFF64 => try cwriter.writeIntLittle(i64, S + A - TP),
533543
544 elf.R_X86_64_TLSGD => {
545 if (target.flags.has_tlsgd) {
546 // TODO
547 // const S_ = @as(i64, @intCast(target.tlsGdAddress(elf_file)));
548 // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P)));
549 } else if (target.flags.has_gottp) {
550 // TODO
551 // const S_ = @as(i64, @intCast(target.getGotTpAddress(elf_file)));
552 // try relaxTlsGdToIe(relocs[i .. i + 2], @intCast(S_ - P), elf_file, &stream);
553 i += 1;
554 } else {
555 try x86_64.relaxTlsGdToLe(
556 self,
557 rels[i .. i + 2],
558 @as(i32, @intCast(S - TP)),
559 elf_file,
560 &stream,
561 );
562 i += 1;
563 }
564 },
565
566 elf.R_X86_64_GOTTPOFF => {
567 if (target.flags.has_gottp) {
568 // TODO
569 // const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file)));
570 // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P)));
571 } else {
572 x86_64.relaxGotTpOff(code[rel.r_offset - 3 ..]) catch unreachable;
573 try cwriter.writeIntLittle(i32, @as(i32, @intCast(S - TP)));
574 }
575 },
576
534577 else => {},
535578 }
536579 }
......@@ -697,6 +740,80 @@ const x86_64 = struct {
697740 }
698741 }
699742
743 pub fn canRelaxGotTpOff(code: []const u8) bool {
744 const old_inst = disassemble(code) orelse return false;
745 switch (old_inst.encoding.mnemonic) {
746 .mov => if (Instruction.new(old_inst.prefix, .mov, &.{
747 old_inst.ops[0],
748 // TODO: hack to force imm32s in the assembler
749 .{ .imm = Immediate.s(-129) },
750 })) |inst| {
751 inst.encode(std.io.null_writer, .{}) catch return false;
752 return true;
753 } else |_| return false,
754 else => return false,
755 }
756 }
757
758 pub fn relaxGotTpOff(code: []u8) !void {
759 const old_inst = disassemble(code) orelse return error.RelaxFail;
760 switch (old_inst.encoding.mnemonic) {
761 .mov => {
762 const inst = try Instruction.new(old_inst.prefix, .mov, &.{
763 old_inst.ops[0],
764 // TODO: hack to force imm32s in the assembler
765 .{ .imm = Immediate.s(-129) },
766 });
767 relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
768 encode(&.{inst}, code) catch return error.RelaxFail;
769 },
770 else => return error.RelaxFail,
771 }
772 }
773
774 pub fn relaxTlsGdToLe(
775 self: Atom,
776 rels: []align(1) const elf.Elf64_Rela,
777 value: i32,
778 elf_file: *Elf,
779 stream: anytype,
780 ) !void {
781 assert(rels.len == 2);
782 const writer = stream.writer();
783 switch (rels[1].r_type()) {
784 elf.R_X86_64_PC32,
785 elf.R_X86_64_PLT32,
786 elf.R_X86_64_GOTPCREL,
787 elf.R_X86_64_GOTPCRELX,
788 => {
789 var insts = [_]u8{
790 0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax
791 0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
792 };
793 std.mem.writeIntLittle(i32, insts[12..][0..4], value);
794 try stream.seekBy(-4);
795 try writer.writeAll(&insts);
796 relocs_log.debug(" relaxing {} and {}", .{
797 fmtRelocType(rels[0].r_type()),
798 fmtRelocType(rels[1].r_type()),
799 });
800 },
801
802 else => {
803 var err = try elf_file.addErrorWithNotes(1);
804 try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{
805 fmtRelocType(rels[0].r_type()),
806 fmtRelocType(rels[1].r_type()),
807 });
808 try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{
809 self.file(elf_file).?.fmtPath(),
810 self.name(elf_file),
811 rels[0].r_offset,
812 });
813 },
814 }
815 }
816
700817 fn disassemble(code: []const u8) ?Instruction {
701818 var disas = Disassembler.init(code);
702819 const inst = disas.next() catch return null;
src/link/Elf/Object.zig+40-3
......@@ -208,6 +208,8 @@ fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) er
208208 break :blk prefix;
209209 }
210210 }
211 if (std.mem.eql(u8, name, ".tcommon")) break :blk ".tbss";
212 if (std.mem.eql(u8, name, ".common")) break :blk ".bss";
211213 break :blk name;
212214 };
213215 const @"type" = switch (shdr.sh_type) {
......@@ -427,7 +429,13 @@ pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void {
427429 const shdr = atom.inputShdr(elf_file);
428430 if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue;
429431 if (shdr.sh_type == elf.SHT_NOBITS) continue;
430 try atom.scanRelocs(elf_file, undefs);
432 if (try atom.scanRelocsRequiresCode(elf_file)) {
433 // TODO ideally, we don't have to decompress at this stage (should already be done)
434 // and we just fetch the code slice.
435 const code = try self.codeDecompressAlloc(elf_file, atom_index);
436 defer elf_file.base.allocator.free(code);
437 try atom.scanRelocs(elf_file, code, undefs);
438 } else try atom.scanRelocs(elf_file, null, undefs);
431439 }
432440
433441 for (self.cies.items) |cie| {
......@@ -590,7 +598,7 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
590598 try self.atoms.append(gpa, atom_index);
591599
592600 const is_tls = global.getType(elf_file) == elf.STT_TLS;
593 const name = if (is_tls) ".tls_common" else ".common";
601 const name = if (is_tls) ".tbss" else ".bss";
594602
595603 const atom = elf_file.atom(atom_index).?;
596604 atom.atom_index = atom_index;
......@@ -684,7 +692,7 @@ pub fn globals(self: *Object) []const Symbol.Index {
684692 return self.symbols.items[start..];
685693}
686694
687pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 {
695fn shdrContents(self: Object, index: u32) error{Overflow}![]const u8 {
688696 assert(index < self.shdrs.items.len);
689697 const shdr = self.shdrs.items[index];
690698 const offset = math.cast(usize, shdr.sh_offset) orelse return error.Overflow;
......@@ -692,6 +700,35 @@ pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 {
692700 return self.data[offset..][0..size];
693701}
694702
703/// Returns atom's code and optionally uncompresses data if required (for compressed sections).
704/// Caller owns the memory.
705pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
706 const gpa = elf_file.base.allocator;
707 const atom_ptr = elf_file.atom(atom_index).?;
708 assert(atom_ptr.file_index == self.index);
709 const data = try self.shdrContents(atom_ptr.input_section_index);
710 const shdr = atom_ptr.inputShdr(elf_file);
711 if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
712 const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
713 switch (chdr.ch_type) {
714 .ZLIB => {
715 var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]);
716 var zlib_stream = std.compress.zlib.decompressStream(gpa, stream.reader()) catch
717 return error.InputOutput;
718 defer zlib_stream.deinit();
719 const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow;
720 const decomp = try gpa.alloc(u8, size);
721 const nread = zlib_stream.reader().readAll(decomp) catch return error.InputOutput;
722 if (nread != decomp.len) {
723 return error.InputOutput;
724 }
725 return decomp;
726 },
727 else => @panic("TODO unhandled compression scheme"),
728 }
729 } else return gpa.dupe(u8, data);
730}
731
695732fn getString(self: *Object, off: u32) [:0]const u8 {
696733 assert(off < self.strtab.len);
697734 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
src/link/Elf/Symbol.zig+4-2
......@@ -327,10 +327,12 @@ pub const Flags = packed struct {
327327 has_dynamic: bool = false,
328328
329329 /// Whether the symbol contains TLSGD indirection.
330 tlsgd: bool = false,
330 needs_tlsgd: bool = false,
331 has_tlsgd: bool = false,
331332
332333 /// Whether the symbol contains GOTTP indirection.
333 gottp: bool = false,
334 needs_gottp: bool = false,
335 has_gottp: bool = false,
334336
335337 /// Whether the symbol contains TLSDESC indirection.
336338 tlsdesc: bool = false,
src/link/Elf/ZigModule.zig+24-1
......@@ -144,7 +144,14 @@ pub fn scanRelocs(self: *ZigModule, elf_file: *Elf, undefs: anytype) !void {
144144 for (self.atoms.keys()) |atom_index| {
145145 const atom = elf_file.atom(atom_index) orelse continue;
146146 if (!atom.flags.alive) continue;
147 try atom.scanRelocs(elf_file, undefs);
147 if (try atom.scanRelocsRequiresCode(elf_file)) {
148 // TODO ideally we don't have to fetch the code here.
149 // Perhaps it would make sense to save the code until flushModule where we
150 // would free all of generated code?
151 const code = try self.codeAlloc(elf_file, atom_index);
152 defer elf_file.base.allocator.free(code);
153 try atom.scanRelocs(elf_file, code, undefs);
154 } else try atom.scanRelocs(elf_file, null, undefs);
148155 }
149156}
150157
......@@ -253,6 +260,22 @@ pub fn asFile(self: *ZigModule) File {
253260 return .{ .zig_module = self };
254261}
255262
263/// Returns atom's code.
264/// Caller owns the memory.
265pub fn codeAlloc(self: ZigModule, elf_file: *Elf, atom_index: Atom.Index) ![]u8 {
266 const gpa = elf_file.base.allocator;
267 const atom = elf_file.atom(atom_index).?;
268 assert(atom.file_index == self.index);
269 const shdr = &elf_file.shdrs.items[atom.outputShndx().?];
270 const file_offset = shdr.sh_offset + atom.value - shdr.sh_addr;
271 const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
272 const code = try gpa.alloc(u8, size);
273 errdefer gpa.free(code);
274 const amt = try elf_file.base.file.?.preadAll(code, file_offset);
275 if (amt != code.len) return error.InputOutput;
276 return code;
277}
278
256279pub fn fmtSymtab(self: *ZigModule, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
257280 return .{ .data = .{
258281 .self = self,
test/link/elf.zig+8-2
......@@ -158,19 +158,25 @@ fn addRunArtifact(comp: *Compile) *Run {
158158 return run;
159159}
160160
161fn addZigSourceBytes(comp: *Compile, bytes: []const u8) void {
161fn addZigSourceBytes(comp: *Compile, comptime bytes: []const u8) void {
162162 const b = comp.step.owner;
163163 const file = WriteFile.create(b).add("a.zig", bytes);
164164 file.addStepDependencies(&comp.step);
165165 comp.root_src = file;
166166}
167167
168fn addCSourceBytes(comp: *Compile, bytes: []const u8) void {
168fn addCSourceBytes(comp: *Compile, comptime bytes: []const u8) void {
169169 const b = comp.step.owner;
170170 const file = WriteFile.create(b).add("a.c", bytes);
171171 comp.addCSourceFile(.{ .file = file, .flags = &.{} });
172172}
173173
174fn addAsmSourceBytes(comp: *Compile, comptime bytes: []const u8) void {
175 const b = comp.step.owner;
176 const file = WriteFile.create(b).add("a.s", bytes ++ "\n");
177 comp.addAssemblyFile(file);
178}
179
174180const std = @import("std");
175181
176182const Build = std.Build;