minor refactoring
This commit is contained in:
@@ -39,9 +39,12 @@ const prefixes = [_]u8{
|
|||||||
0x36,
|
0x36,
|
||||||
};
|
};
|
||||||
|
|
||||||
var syscall_flicken_bytes = [13]u8{
|
/// As of the SysV ABI: 'The kernel destroys registers %rcx and %r11."
|
||||||
0x49, 0xBB, // mov r11
|
/// So we put the address of the function to call into %r11.
|
||||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, // 8byte immediate
|
// 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
|
0x41, 0xff, 0xd3, // call r11
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ pub fn init() !void {
|
|||||||
page_size / @sizeOf(Flicken),
|
page_size / @sizeOf(Flicken),
|
||||||
);
|
);
|
||||||
flicken_templates.putAssumeCapacity("nop", .{ .name = "nop", .bytes = &.{} });
|
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 });
|
flicken_templates.putAssumeCapacity("syscall", .{ .name = "syscall", .bytes = &syscall_flicken_bytes });
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,19 +27,12 @@ pub const UserRegs = extern struct {
|
|||||||
///
|
///
|
||||||
/// This function is called from `syscall_entry` with a pointer to the saved registers.
|
/// This function is called from `syscall_entry` with a pointer to the saved registers.
|
||||||
/// It effectively emulates the syscall instruction while allowing for interception.
|
/// 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 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
|
// TODO: Handle `clone` specially because the child thread wakes up with a fresh stack
|
||||||
// and cannot pop the registers we saved here.
|
// and cannot pop the registers we saved here.
|
||||||
|
|
||||||
const sys_nr = regs.rax;
|
const sys: linux.SYS = @enumFromInt(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;
|
|
||||||
|
|
||||||
switch (sys) {
|
switch (sys) {
|
||||||
.readlink => {
|
.readlink => {
|
||||||
@@ -63,43 +56,57 @@ export fn syscall_handler(regs: *UserRegs) void {
|
|||||||
return;
|
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 => {},
|
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.
|
// 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 {
|
inline fn executeSyscall(regs: *UserRegs) u64 {
|
||||||
const needle = "/proc/self/exe";
|
return linux.syscall6(
|
||||||
var i: usize = 0;
|
@enumFromInt(regs.rax),
|
||||||
while (i < needle.len) : (i += 1) {
|
regs.rdi,
|
||||||
if (path[i] != needle[i]) return false;
|
regs.rsi,
|
||||||
}
|
regs.rdx,
|
||||||
return path[i] == 0;
|
regs.r10,
|
||||||
}
|
regs.r8,
|
||||||
|
regs.r9,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assembly trampoline that saves state and calls the Zig handler.
|
/// Assembly trampoline that saves state and calls the Zig handler.
|
||||||
pub fn syscall_entry() callconv(.naked) void {
|
pub fn syscall_entry() callconv(.naked) void {
|
||||||
asm volatile (
|
asm volatile (
|
||||||
\\ # Respect the Red Zone (128 bytes)
|
|
||||||
\\ sub $128, %rsp
|
|
||||||
\\
|
|
||||||
\\ # Save all GPRs that must be preserved or are arguments
|
\\ # Save all GPRs that must be preserved or are arguments
|
||||||
\\ push %r15
|
\\ push %r15
|
||||||
\\ push %r14
|
\\ push %r14
|
||||||
@@ -117,6 +124,7 @@ pub fn syscall_entry() callconv(.naked) void {
|
|||||||
\\ push %rbx
|
\\ push %rbx
|
||||||
\\ push %rax
|
\\ push %rax
|
||||||
\\ pushfq # Save Flags
|
\\ pushfq # Save Flags
|
||||||
|
\\ # TODO: save return_address
|
||||||
\\
|
\\
|
||||||
\\ # Align stack
|
\\ # Align stack
|
||||||
\\ # Current pushes: 16 * 8 = 128 bytes.
|
\\ # Current pushes: 16 * 8 = 128 bytes.
|
||||||
@@ -148,8 +156,6 @@ pub fn syscall_entry() callconv(.naked) void {
|
|||||||
\\ pop %r14
|
\\ pop %r14
|
||||||
\\ pop %r15
|
\\ pop %r15
|
||||||
\\
|
\\
|
||||||
\\ # Restore Red Zone and Return
|
|
||||||
\\ add $128, %rsp
|
|
||||||
\\ ret
|
\\ ret
|
||||||
:
|
:
|
||||||
// TODO: can we somehow use %[handler] in the assembly instead?
|
// 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),
|
: [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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user