From f4064aff8926bd527b57dbbd53c8fa2d911de1a8 Mon Sep 17 00:00:00 2001 From: Pascal Zittlau Date: Fri, 12 Dec 2025 14:07:00 +0100 Subject: [PATCH] clone tests to help debugging --- src/main.zig | 13 +++++++++ src/test/clone_raw.zig | 65 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/test/clone_raw.zig diff --git a/src/main.zig b/src/main.zig index 8b1daa2..cb9a1d1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -326,6 +326,19 @@ test "nolibc_pie_readlink" { // try testReadlink("libc_pie_readlink"); // } +test "nolibc_nopie_clone_raw" { + try testHelper( + &.{ flicker_path, getTestExePath("nolibc_nopie_clone_raw") }, + "Child: Hello\nParent: Goodbye\n", + ); +} +test "nolibc_pie_clone_raw" { + try testHelper( + &.{ flicker_path, getTestExePath("nolibc_pie_clone_raw") }, + "Child: Hello\nParent: Goodbye\n", + ); +} + test "echo" { try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n"); } diff --git a/src/test/clone_raw.zig b/src/test/clone_raw.zig new file mode 100644 index 0000000..6c97cfd --- /dev/null +++ b/src/test/clone_raw.zig @@ -0,0 +1,65 @@ +const std = @import("std"); +const linux = std.os.linux; +const clone = linux.CLONE; + +var child_stack: [4096 * 4]u8 align(16) = undefined; +pub fn main() !void { + // SIGCHLD: Send signal to parent on exit (required for waitpid) + const flags = clone.VM | clone.FILES | clone.FS | clone.SIGHAND | linux.SIG.CHLD; + + // Stack grows downwards. Point to the end. + const stack_top = @intFromPtr(&child_stack) + child_stack.len; + + const msg = "Child: Hello\n"; + const msg_len = msg.len; + + // We use inline assembly to perform the clone syscall and handle the child path completely to + // avoid the compiler generating code that relies on the parent's stack frame in the child + // process (where the stack is empty). + const ret = asm volatile ( + \\ syscall + \\ test %%rax, %%rax + \\ jnz 1f + \\ + \\ # Child Path + \\ # Write to stdout + \\ mov $1, %%rdi # fd = 1 (stdout) + \\ mov %[msg], %%rsi # buffer + \\ mov %[len], %%rdx # length + \\ mov $1, %%rax # SYS_write + \\ syscall + \\ + \\ # Exit + \\ mov $0, %%rdi # code = 0 + \\ mov $60, %%rax # SYS_exit + \\ syscall + \\ + \\ # Should not be reached + \\ ud2 + \\ + \\ 1: + \\ # Parent Path continues + : [ret] "={rax}" (-> usize), + : [number] "{rax}" (@intFromEnum(linux.syscalls.X64.clone)), + [arg1] "{rdi}" (flags), + [arg2] "{rsi}" (stack_top), + [arg3] "{rdx}" (0), + [arg4] "{r10}" (0), + [arg5] "{r8}" (0), + [msg] "r" (msg.ptr), + [len] "r" (msg_len), + : .{ .rcx = true, .r11 = true, .memory = true }); + + // Parent Process + const child_pid: i32 = @intCast(ret); + if (child_pid < 0) { + _ = linux.syscall3(.write, 1, @intFromPtr("Parent: Clone failed\n"), 21); + return; + } + + var status: u32 = 0; + // wait4 for the child to exit + _ = linux.syscall4(.wait4, @as(usize, @intCast(child_pid)), @intFromPtr(&status), 0, 0); + + _ = linux.syscall3(.write, 1, @intFromPtr("Parent: Goodbye\n"), 16); +}