From 2386bfe854826e0726b7002c04cd9fc4c08d68f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 25 Jul 2024 23:55:37 +0200 Subject: [PATCH] std.os.linux.start_pie: Rewrite relocate() to avoid jump tables and libcalls. The code would cause LLVM to emit a jump table for the switch in the loop over the dynamic tags. That jump table was far enough away that the compiler decided to go through the GOT, which would of course break at this early stage as we haven't applied MIPS's local GOT relocations yet, nor can we until we've walked through the _DYNAMIC array. The first attempt at rewriting this used code like this: var sorted_dynv = [_]elf.Addr{0} ** elf.DT_NUM; But this is also problematic as it results in a memcpy() call. Instead, we explicitly initialize it to undefined and use a loop of volatile stores to clear it. --- lib/std/os/linux/start_pie.zig | 48 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig index ab44abd898..b5cc06f429 100644 --- a/lib/std/os/linux/start_pie.zig +++ b/lib/std/os/linux/start_pie.zig @@ -193,6 +193,7 @@ pub fn relocate(phdrs: []elf.Phdr) void { @disableInstrumentation(); const dynv = getDynamicSymbol(); + // Recover the delta applied by the loader by comparing the effective and // the theoretical load addresses for the `_DYNAMIC` symbol. const base_addr = base: { @@ -204,34 +205,45 @@ pub fn relocate(phdrs: []elf.Phdr) void { @trap(); }; - var rel_addr: usize = 0; - var rela_addr: usize = 0; - var rel_size: usize = 0; - var rela_size: usize = 0; + var sorted_dynv: [elf.DT_NUM]elf.Addr = undefined; + + // Zero-initialized this way to prevent the compiler from turning this into + // `memcpy` or `memset` calls (which can require relocations). + for (&sorted_dynv) |*dyn| { + const pdyn: *volatile elf.Addr = @ptrCast(dyn); + pdyn.* = 0; + } + { + // `dynv` has no defined order. Fix that. var i: usize = 0; while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) { - switch (dynv[i].d_tag) { - elf.DT_REL => rel_addr = base_addr + dynv[i].d_val, - elf.DT_RELA => rela_addr = base_addr + dynv[i].d_val, - elf.DT_RELSZ => rel_size = dynv[i].d_val, - elf.DT_RELASZ => rela_size = dynv[i].d_val, - else => {}, - } + if (dynv[i].d_tag < elf.DT_NUM) sorted_dynv[@bitCast(dynv[i].d_tag)] = dynv[i].d_val; } } - // Apply the relocations. - if (rel_addr != 0) { - const rel = std.mem.bytesAsSlice(elf.Rel, @as([*]u8, @ptrFromInt(rel_addr))[0..rel_size]); - for (rel) |r| { + + // Apply normal relocations. + + const rel = sorted_dynv[elf.DT_REL]; + if (rel != 0) { + const rels = @call(.always_inline, std.mem.bytesAsSlice, .{ + elf.Rel, + @as([*]u8, @ptrFromInt(base_addr + rel))[0..sorted_dynv[elf.DT_RELSZ]], + }); + for (rels) |r| { if (r.r_type() != R_RELATIVE) continue; @as(*usize, @ptrFromInt(base_addr + r.r_offset)).* += base_addr; } } - if (rela_addr != 0) { - const rela = std.mem.bytesAsSlice(elf.Rela, @as([*]u8, @ptrFromInt(rela_addr))[0..rela_size]); - for (rela) |r| { + + const rela = sorted_dynv[elf.DT_RELA]; + if (rela != 0) { + const relas = @call(.always_inline, std.mem.bytesAsSlice, .{ + elf.Rela, + @as([*]u8, @ptrFromInt(base_addr + rela))[0..sorted_dynv[elf.DT_RELASZ]], + }); + for (relas) |r| { if (r.r_type() != R_RELATIVE) continue; @as(*usize, @ptrFromInt(base_addr + r.r_offset)).* = base_addr + @as(usize, @bitCast(r.r_addend)); }