From ecd459b8647a1b7cade31f0286694e14101f80f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 21 Jul 2024 22:31:59 +0200 Subject: [PATCH 1/3] start: Avoid going through the argc_argv_ptr global variable. This is problematic for PIE. There's nothing but luck preventing the accesses to this global variable from requiring relocations. I've observed this being an issue on MIPS and PowerPC personally, but others may be affected. Besides, we're really just passing the initial stack pointer value to posixCallMainAndExit(), so... just do that. --- lib/std/start.zig | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index adcfb9d71bb2..9da28423e1fc 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -9,8 +9,6 @@ const elf = std.elf; const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; -var argc_argv_ptr: [*]usize = undefined; - const start_sym_name = if (native_arch.isMIPS()) "__start" else "_start"; // The self-hosted compiler is not fully capable of handling all of this start.zig file. @@ -277,41 +275,42 @@ fn _start() callconv(.Naked) noreturn { asm volatile (switch (native_arch) { .x86_64 => \\ xorl %%ebp, %%ebp - \\ movq %%rsp, %[argc_argv_ptr] + \\ movq %%rsp, %%rdi \\ andq $-16, %%rsp \\ callq %[posixCallMainAndExit:P] , .x86 => \\ xorl %%ebp, %%ebp - \\ movl %%esp, %[argc_argv_ptr] + \\ movl %%esp, %%eax \\ andl $-16, %%esp + \\ subl $12, %%esp + \\ pushl %%eax \\ calll %[posixCallMainAndExit:P] , .aarch64, .aarch64_be => \\ mov fp, #0 \\ mov lr, #0 \\ mov x0, sp - \\ str x0, %[argc_argv_ptr] \\ b %[posixCallMainAndExit] , .arm, .armeb, .thumb, .thumbeb => \\ mov fp, #0 \\ mov lr, #0 - \\ str sp, %[argc_argv_ptr] + \\ mov a1, sp \\ and sp, #-16 \\ b %[posixCallMainAndExit] , .riscv64 => \\ li s0, 0 \\ li ra, 0 - \\ sd sp, %[argc_argv_ptr] + \\ mv a0, sp \\ andi sp, sp, -16 \\ tail %[posixCallMainAndExit]@plt , .mips, .mipsel => // The lr is already zeroed on entry, as specified by the ABI. \\ addiu $fp, $zero, 0 - \\ sw $sp, %[argc_argv_ptr] + \\ move $a0, $sp \\ .set push \\ .set noat \\ addiu $1, $zero, -16 @@ -322,7 +321,7 @@ fn _start() callconv(.Naked) noreturn { .mips64, .mips64el => // The lr is already zeroed on entry, as specified by the ABI. \\ addiu $fp, $zero, 0 - \\ sd $sp, %[argc_argv_ptr] + \\ move $a0, $sp \\ .set push \\ .set noat \\ daddiu $1, $zero, -16 @@ -332,7 +331,7 @@ fn _start() callconv(.Naked) noreturn { , .powerpc, .powerpcle => // Setup the initial stack frame and clear the back chain pointer. - \\ stw 1, %[argc_argv_ptr] + \\ mr 3, 1 \\ li 0, 0 \\ stwu 1, -16(1) \\ stw 0, 0(1) @@ -342,7 +341,7 @@ fn _start() callconv(.Naked) noreturn { .powerpc64, .powerpc64le => // Setup the initial stack frame and clear the back chain pointer. // TODO: Support powerpc64 (big endian) on ELFv2. - \\ std 1, %[argc_argv_ptr] + \\ mr 3, 1 \\ li 0, 0 \\ stdu 0, -32(1) \\ mtlr 0 @@ -352,12 +351,12 @@ fn _start() callconv(.Naked) noreturn { // argc is stored after a register window (16 registers) plus stack bias \\ mov %%g0, %%i6 \\ add %%o6, 2175, %%l0 - \\ ba %[posixCallMainAndExit] - \\ stx %%l0, %[argc_argv_ptr] + \\ mov %%l0, %%o0 + \\ ba,a %[posixCallMainAndExit] , else => @compileError("unsupported arch"), } - : [argc_argv_ptr] "=m" (argc_argv_ptr), + : : [posixCallMainAndExit] "X" (&posixCallMainAndExit), ); } @@ -385,7 +384,7 @@ fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn { std.os.windows.ntdll.RtlExitUserProcess(@as(std.os.windows.UINT, @bitCast(result))); } -fn posixCallMainAndExit() callconv(.C) noreturn { +fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn { const argc = argc_argv_ptr[0]; const argv = @as([*][*:0]u8, @ptrCast(argc_argv_ptr + 1)); From 7bc78967b400322a0fc5651f37a1b0428c37fb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 22 Jul 2024 01:22:47 +0200 Subject: [PATCH 2/3] start: Fix _start() to initialize the ToC for powerpc64. The previous version of this function referenced the argc_argv_ptr global variable as an inline asm operand. This caused LLVM to generate prologue code to initialize the ToC so that the global variable can actually be accessed. Ordinarily, there's nothing wrong with that. But _start() is a naked function! This makes it actually super surprising that LLVM did this. It also means that the old version only really worked by accident. Once the reference to the global variable was removed, no ToC was set up, thus violating the calling convention once we got to posixCallMainAndExit(). This then caused any attempt to access global variables here to crash - namely when setting std.os.linux.elf_aux_maybe. The fix is to just initialize the ToC manually in _start(). --- lib/std/start.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 9da28423e1fc..28c77fa49e06 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -341,6 +341,8 @@ fn _start() callconv(.Naked) noreturn { .powerpc64, .powerpc64le => // Setup the initial stack frame and clear the back chain pointer. // TODO: Support powerpc64 (big endian) on ELFv2. + \\ addis 2, 12, .TOC. - _start@ha + \\ addi 2, 2, .TOC. - _start@l \\ mr 3, 1 \\ li 0, 0 \\ stdu 0, -32(1) From 81232f7c91ca8e0969e9d3f92cc11caeedc017d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 22 Jul 2024 01:23:04 +0200 Subject: [PATCH 3/3] start: Add clrrdi to align the stack pointer on powerpc64. --- lib/std/start.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 28c77fa49e06..e0f99b476248 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -340,10 +340,10 @@ fn _start() callconv(.Naked) noreturn { , .powerpc64, .powerpc64le => // Setup the initial stack frame and clear the back chain pointer. - // TODO: Support powerpc64 (big endian) on ELFv2. \\ addis 2, 12, .TOC. - _start@ha \\ addi 2, 2, .TOC. - _start@l \\ mr 3, 1 + \\ clrrdi 1, 1, 4 \\ li 0, 0 \\ stdu 0, -32(1) \\ mtlr 0