Skip to content

Commit 873c695

Browse files
authored
Merge pull request #17319 from ziglang/elf-tls
elf: add basic TLS segment handling
2 parents 101df76 + e72fd18 commit 873c695

File tree

7 files changed

+486
-143
lines changed

7 files changed

+486
-143
lines changed

src/link/Elf.zig

Lines changed: 184 additions & 86 deletions
Large diffs are not rendered by default.

src/link/Elf/Atom.zig

Lines changed: 175 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -59,38 +59,6 @@ pub fn outputShndx(self: Atom) ?u16 {
5959
return self.output_section_index;
6060
}
6161

62-
pub 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.
69-
pub 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-
9462
pub 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

330-
pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
298+
pub 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+
305+
pub 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;
@@ -336,6 +312,8 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
336312

337313
if (rel.r_type() == elf.R_X86_64_NONE) continue;
338314

315+
const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
316+
339317
const symbol_index = switch (file_ptr) {
340318
.zig_module => |x| x.symbol(rel.r_sym()),
341319
.object => |x| x.symbols.items[rel.r_sym()],
@@ -388,7 +366,54 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
388366

389367
elf.R_X86_64_PC32 => {},
390368

391-
else => @panic("TODO"),
369+
elf.R_X86_64_TPOFF32,
370+
elf.R_X86_64_TPOFF64,
371+
=> {
372+
if (is_dyn_lib) {
373+
// TODO
374+
// self.picError(symbol, rel, elf_file);
375+
}
376+
},
377+
378+
elf.R_X86_64_TLSGD => {
379+
// TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr
380+
381+
if (elf_file.isStatic() or
382+
(!symbol.flags.import and !is_dyn_lib))
383+
{
384+
// Relax if building with -static flag as __tls_get_addr() will not be present in libc.a
385+
// We skip the next relocation.
386+
i += 1;
387+
} else if (!symbol.flags.import and is_dyn_lib) {
388+
symbol.flags.needs_gottp = true;
389+
i += 1;
390+
} else {
391+
symbol.flags.needs_tlsgd = true;
392+
}
393+
},
394+
395+
elf.R_X86_64_GOTTPOFF => {
396+
const should_relax = blk: {
397+
// if (!elf_file.options.relax or is_shared or symbol.flags.import) break :blk false;
398+
if (!x86_64.canRelaxGotTpOff(code.?[r_offset - 3 ..])) break :blk false;
399+
break :blk true;
400+
};
401+
if (!should_relax) {
402+
symbol.flags.needs_gottp = true;
403+
}
404+
},
405+
406+
else => {
407+
var err = try elf_file.addErrorWithNotes(1);
408+
try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {}", .{
409+
fmtRelocType(rel.r_type()),
410+
});
411+
try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{
412+
self.file(elf_file).?.fmtPath(),
413+
self.name(elf_file),
414+
r_offset,
415+
});
416+
},
392417
}
393418
}
394419
}
@@ -430,7 +455,10 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
430455
var stream = std.io.fixedBufferStream(code);
431456
const cwriter = stream.writer();
432457

433-
for (try self.relocs(elf_file)) |rel| {
458+
const rels = try self.relocs(elf_file);
459+
var i: usize = 0;
460+
while (i < rels.len) : (i += 1) {
461+
const rel = rels[i];
434462
const r_type = rel.r_type();
435463
if (r_type == elf.R_X86_64_NONE) continue;
436464

@@ -463,9 +491,9 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
463491
// Relative offset to the start of the global offset table.
464492
const G = @as(i64, @intCast(target.gotAddress(elf_file))) - GOT;
465493
// // Address of the thread pointer.
466-
// const TP = @as(i64, @intCast(elf_file.getTpAddress()));
494+
const TP = @as(i64, @intCast(elf_file.tpAddress()));
467495
// // Address of the dynamic thread pointer.
468-
// const DTP = @as(i64, @intCast(elf_file.getDtpAddress()));
496+
// const DTP = @as(i64, @intCast(elf_file.dtpAddress()));
469497

470498
relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ({s})", .{
471499
fmtRelocType(r_type),
@@ -512,10 +540,43 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
512540
try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P)));
513541
},
514542

515-
else => {
516-
log.err("TODO: unhandled relocation type {}", .{fmtRelocType(rel.r_type())});
517-
@panic("TODO unhandled relocation type");
543+
elf.R_X86_64_TPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A - TP))),
544+
elf.R_X86_64_TPOFF64 => try cwriter.writeIntLittle(i64, S + A - TP),
545+
546+
elf.R_X86_64_TLSGD => {
547+
if (target.flags.has_tlsgd) {
548+
// TODO
549+
// const S_ = @as(i64, @intCast(target.tlsGdAddress(elf_file)));
550+
// try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P)));
551+
} else if (target.flags.has_gottp) {
552+
// TODO
553+
// const S_ = @as(i64, @intCast(target.getGotTpAddress(elf_file)));
554+
// try relaxTlsGdToIe(relocs[i .. i + 2], @intCast(S_ - P), elf_file, &stream);
555+
i += 1;
556+
} else {
557+
try x86_64.relaxTlsGdToLe(
558+
self,
559+
rels[i .. i + 2],
560+
@as(i32, @intCast(S - TP)),
561+
elf_file,
562+
&stream,
563+
);
564+
i += 1;
565+
}
566+
},
567+
568+
elf.R_X86_64_GOTTPOFF => {
569+
if (target.flags.has_gottp) {
570+
// TODO
571+
// const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file)));
572+
// try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P)));
573+
} else {
574+
x86_64.relaxGotTpOff(code[r_offset - 3 ..]) catch unreachable;
575+
try cwriter.writeIntLittle(i32, @as(i32, @intCast(S - TP)));
576+
}
518577
},
578+
579+
else => {},
519580
}
520581
}
521582
}
@@ -681,6 +742,80 @@ const x86_64 = struct {
681742
}
682743
}
683744

745+
pub fn canRelaxGotTpOff(code: []const u8) bool {
746+
const old_inst = disassemble(code) orelse return false;
747+
switch (old_inst.encoding.mnemonic) {
748+
.mov => if (Instruction.new(old_inst.prefix, .mov, &.{
749+
old_inst.ops[0],
750+
// TODO: hack to force imm32s in the assembler
751+
.{ .imm = Immediate.s(-129) },
752+
})) |inst| {
753+
inst.encode(std.io.null_writer, .{}) catch return false;
754+
return true;
755+
} else |_| return false,
756+
else => return false,
757+
}
758+
}
759+
760+
pub fn relaxGotTpOff(code: []u8) !void {
761+
const old_inst = disassemble(code) orelse return error.RelaxFail;
762+
switch (old_inst.encoding.mnemonic) {
763+
.mov => {
764+
const inst = try Instruction.new(old_inst.prefix, .mov, &.{
765+
old_inst.ops[0],
766+
// TODO: hack to force imm32s in the assembler
767+
.{ .imm = Immediate.s(-129) },
768+
});
769+
relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
770+
encode(&.{inst}, code) catch return error.RelaxFail;
771+
},
772+
else => return error.RelaxFail,
773+
}
774+
}
775+
776+
pub fn relaxTlsGdToLe(
777+
self: Atom,
778+
rels: []align(1) const elf.Elf64_Rela,
779+
value: i32,
780+
elf_file: *Elf,
781+
stream: anytype,
782+
) !void {
783+
assert(rels.len == 2);
784+
const writer = stream.writer();
785+
switch (rels[1].r_type()) {
786+
elf.R_X86_64_PC32,
787+
elf.R_X86_64_PLT32,
788+
elf.R_X86_64_GOTPCREL,
789+
elf.R_X86_64_GOTPCRELX,
790+
=> {
791+
var insts = [_]u8{
792+
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax
793+
0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax
794+
};
795+
std.mem.writeIntLittle(i32, insts[12..][0..4], value);
796+
try stream.seekBy(-4);
797+
try writer.writeAll(&insts);
798+
relocs_log.debug(" relaxing {} and {}", .{
799+
fmtRelocType(rels[0].r_type()),
800+
fmtRelocType(rels[1].r_type()),
801+
});
802+
},
803+
804+
else => {
805+
var err = try elf_file.addErrorWithNotes(1);
806+
try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{
807+
fmtRelocType(rels[0].r_type()),
808+
fmtRelocType(rels[1].r_type()),
809+
});
810+
try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{
811+
self.file(elf_file).?.fmtPath(),
812+
self.name(elf_file),
813+
rels[0].r_offset,
814+
});
815+
},
816+
}
817+
}
818+
684819
fn disassemble(code: []const u8) ?Instruction {
685820
var disas = Disassembler.init(code);
686821
const inst = disas.next() catch return null;

src/link/Elf/Object.zig

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {
@@ -233,8 +235,7 @@ fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) er
233235
const is_alloc = flags & elf.SHF_ALLOC != 0;
234236
const is_write = flags & elf.SHF_WRITE != 0;
235237
const is_exec = flags & elf.SHF_EXECINSTR != 0;
236-
const is_tls = flags & elf.SHF_TLS != 0;
237-
if (!is_alloc or is_tls) {
238+
if (!is_alloc) {
238239
log.err("{}: output section {s} not found", .{ self.fmtPath(), name });
239240
@panic("TODO: missing output section!");
240241
}
@@ -243,7 +244,7 @@ fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) er
243244
if (is_exec) phdr_flags |= elf.PF_X;
244245
const phdr_index = try elf_file.allocateSegment(.{
245246
.size = Elf.padToIdeal(shdr.sh_size),
246-
.alignment = if (is_tls) shdr.sh_addralign else elf_file.page_size,
247+
.alignment = elf_file.page_size,
247248
.flags = phdr_flags,
248249
});
249250
const shndx = try elf_file.allocateAllocSection(.{
@@ -428,7 +429,13 @@ pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void {
428429
const shdr = atom.inputShdr(elf_file);
429430
if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue;
430431
if (shdr.sh_type == elf.SHT_NOBITS) continue;
431-
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);
432439
}
433440

434441
for (self.cies.items) |cie| {
@@ -591,7 +598,7 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
591598
try self.atoms.append(gpa, atom_index);
592599

593600
const is_tls = global.getType(elf_file) == elf.STT_TLS;
594-
const name = if (is_tls) ".tls_common" else ".common";
601+
const name = if (is_tls) ".tbss" else ".bss";
595602

596603
const atom = elf_file.atom(atom_index).?;
597604
atom.atom_index = atom_index;
@@ -685,14 +692,43 @@ pub fn globals(self: *Object) []const Symbol.Index {
685692
return self.symbols.items[start..];
686693
}
687694

688-
pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 {
695+
fn shdrContents(self: Object, index: u32) error{Overflow}![]const u8 {
689696
assert(index < self.shdrs.items.len);
690697
const shdr = self.shdrs.items[index];
691698
const offset = math.cast(usize, shdr.sh_offset) orelse return error.Overflow;
692699
const size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
693700
return self.data[offset..][0..size];
694701
}
695702

703+
/// Returns atom's code and optionally uncompresses data if required (for compressed sections).
704+
/// Caller owns the memory.
705+
pub 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+
696732
fn getString(self: *Object, off: u32) [:0]const u8 {
697733
assert(off < self.strtab.len);
698734
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);

src/link/Elf/Symbol.zig

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
196196
// if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file);
197197
// break :blk 0;
198198
// }
199-
// if (st_shndx == elf.SHN_ABS) break :blk symbol.value;
200-
// const shdr = &elf_file.sections.items(.shdr)[st_shndx];
201-
// if (Elf.shdrIsTls(shdr)) break :blk symbol.value - elf_file.getTlsAddress();
199+
if (st_shndx == elf.SHN_ABS) break :blk symbol.value;
200+
const shdr = &elf_file.shdrs.items[st_shndx];
201+
if (shdr.sh_flags & elf.SHF_TLS != 0 and file_ptr != .linker_defined)
202+
break :blk symbol.value - elf_file.tlsAddress();
202203
break :blk symbol.value;
203204
};
204205
out.* = .{
@@ -327,10 +328,12 @@ pub const Flags = packed struct {
327328
has_dynamic: bool = false,
328329

329330
/// Whether the symbol contains TLSGD indirection.
330-
tlsgd: bool = false,
331+
needs_tlsgd: bool = false,
332+
has_tlsgd: bool = false,
331333

332334
/// Whether the symbol contains GOTTP indirection.
333-
gottp: bool = false,
335+
needs_gottp: bool = false,
336+
has_gottp: bool = false,
334337

335338
/// Whether the symbol contains TLSDESC indirection.
336339
tlsdesc: bool = false,

0 commit comments

Comments
 (0)