Commit 2386bfe854

Alex Rønne Petersen <alex@alexrp.com>
2024-07-25 23:55:37
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.
1 parent 68cebde
Changed files (1)
lib
std
os
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));
         }