minor refactoring

This commit is contained in:
2025-12-15 11:31:12 +01:00
parent f4064aff89
commit d0c227faa8
2 changed files with 69 additions and 41 deletions

View File

@@ -39,9 +39,12 @@ const prefixes = [_]u8{
0x36,
};
var syscall_flicken_bytes = [13]u8{
0x49, 0xBB, // mov r11
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, // 8byte immediate
/// As of the SysV ABI: 'The kernel destroys registers %rcx and %r11."
/// So we put the address of the function to call into %r11.
// TODO: Don't we need to save the red zone here, because we push the return address onto the stack
// with the `call r11` instruction?
var syscall_flicken_bytes = [_]u8{
0x49, 0xBB, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, // mov r11 <imm>
0x41, 0xff, 0xd3, // call r11
};
@@ -65,7 +68,7 @@ pub fn init() !void {
page_size / @sizeOf(Flicken),
);
flicken_templates.putAssumeCapacity("nop", .{ .name = "nop", .bytes = &.{} });
mem.writeInt(u64, syscall_flicken_bytes[2..][0..8], @intFromPtr(&syscalls.syscall_entry), .little);
mem.writeInt(u64, syscall_flicken_bytes[4..][0..8], @intFromPtr(&syscalls.syscall_entry), .little);
flicken_templates.putAssumeCapacity("syscall", .{ .name = "syscall", .bytes = &syscall_flicken_bytes });
{

View File

@@ -27,19 +27,12 @@ pub const UserRegs = extern struct {
///
/// This function is called from `syscall_entry` with a pointer to the saved registers.
/// It effectively emulates the syscall instruction while allowing for interception.
export fn syscall_handler(regs: *UserRegs) void {
export fn syscall_handler(regs: *UserRegs) callconv(.c) void {
// TODO: Handle signals (masking) to prevent re-entrancy issues if we touch global state.
// TODO: Handle `clone` specially because the child thread wakes up with a fresh stack
// and cannot pop the registers we saved here.
const sys_nr = regs.rax;
const sys: linux.SYS = @enumFromInt(sys_nr);
const arg1 = regs.rdi;
const arg2 = regs.rsi;
const arg3 = regs.rdx;
const arg4 = regs.r10;
const arg5 = regs.r8;
const arg6 = regs.r9;
const sys: linux.SYS = @enumFromInt(regs.rax);
switch (sys) {
.readlink => {
@@ -63,43 +56,57 @@ export fn syscall_handler(regs: *UserRegs) void {
return;
}
},
.clone, .clone3 => {
@panic("Clone is not supported yet");
},
.fork, .vfork => {
// fork/vfork duplicate the stack (or share it until exec), so the return path via
// syscall_entry works fine.
},
.rt_sigreturn => {
@panic("sigreturn is not supported yet");
},
.execve, .execveat => |s| {
// TODO: option to persist across new processes
std.debug.print("syscall {} called\n", .{s});
},
.prctl, .arch_prctl, .set_tid_address => |s| {
// TODO: what do we need to handle from these?
// process name
// fs base(gs?)
// thread id pointers
std.debug.print("syscall {} called\n", .{s});
},
.mmap, .mprotect => {
// TODO: JIT support
// TODO: cleanup
},
.munmap, .mremap => {
// TODO: cleanup
},
else => {},
}
std.debug.print("Got syscall {s}\n", .{@tagName(sys)});
// For now, we just pass through everything.
// In the future, we will switch on `sys` to handle mmap, mprotect, etc.
const result = std.os.linux.syscall6(sys, arg1, arg2, arg3, arg4, arg5, arg6);
// Write result back to the saved RAX so it is restored to the application.
regs.rax = result;
regs.rax = executeSyscall(regs);
}
fn isProcSelfExe(path: [*:0]const u8) bool {
const needle = "/proc/self/exe";
var i: usize = 0;
while (i < needle.len) : (i += 1) {
if (path[i] != needle[i]) return false;
}
return path[i] == 0;
}
fn handleReadlink(buf_addr: u64, buf_size: u64, regs: *UserRegs) void {
const target = Patcher.target_exec_path;
const len = @min(target.len, buf_size);
const dest = @as([*]u8, @ptrFromInt(buf_addr));
@memcpy(dest[0..len], target[0..len]);
// readlink does not null-terminate if the buffer is full, it just returns length.
regs.rax = len;
inline fn executeSyscall(regs: *UserRegs) u64 {
return linux.syscall6(
@enumFromInt(regs.rax),
regs.rdi,
regs.rsi,
regs.rdx,
regs.r10,
regs.r8,
regs.r9,
);
}
/// Assembly trampoline that saves state and calls the Zig handler.
pub fn syscall_entry() callconv(.naked) void {
asm volatile (
\\ # Respect the Red Zone (128 bytes)
\\ sub $128, %rsp
\\
\\ # Save all GPRs that must be preserved or are arguments
\\ push %r15
\\ push %r14
@@ -117,6 +124,7 @@ pub fn syscall_entry() callconv(.naked) void {
\\ push %rbx
\\ push %rax
\\ pushfq # Save Flags
\\ # TODO: save return_address
\\
\\ # Align stack
\\ # Current pushes: 16 * 8 = 128 bytes.
@@ -148,8 +156,6 @@ pub fn syscall_entry() callconv(.naked) void {
\\ pop %r14
\\ pop %r15
\\
\\ # Restore Red Zone and Return
\\ add $128, %rsp
\\ ret
:
// TODO: can we somehow use %[handler] in the assembly instead?
@@ -157,3 +163,22 @@ pub fn syscall_entry() callconv(.naked) void {
: [handler] "i" (syscall_handler),
);
}
fn isProcSelfExe(path: [*:0]const u8) bool {
const needle = "/proc/self/exe";
var i: usize = 0;
while (i < needle.len) : (i += 1) {
if (path[i] != needle[i]) return false;
}
return path[i] == 0;
}
fn handleReadlink(buf_addr: u64, buf_size: u64, regs: *UserRegs) void {
const target = Patcher.target_exec_path;
const len = @min(target.len, buf_size);
const dest = @as([*]u8, @ptrFromInt(buf_addr));
@memcpy(dest[0..len], target[0..len]);
// readlink does not null-terminate if the buffer is full, it just returns length.
regs.rax = len;
}