danos/library/kernel/start.zig

88 lines
3.9 KiB
Zig

//! The user-space process entry shim. Every user binary roots `_start` here (via
//! `entry = _start` in build.zig); the shared compilation root, root.zig, forces
//! this file to be analysed with `comptime { _ = &runtime.start._start; }`, so
//! the whole runtime is linked in.
const std = @import("std");
const logging = @import("logging");
const process = @import("process");
/// The kernel enters at `_start` with rsp 16-aligned, pointing at the System V
/// process-entry block it built: argc, argv pointers, NULL, envp terminator, the
/// auxiliary vector, then the strings (see system/kernel/process.zig,
/// `buildEntryStack`). Capture that address in rdi — the first SysV argument —
/// before `call` disturbs the stack; the call's pushed return address also puts
/// rsp ≡ 8 (mod 16), satisfying the ABI before any Zig frame runs. The `ud2` is a
/// safety net if `rt_start` ever returns.
pub export fn _start() callconv(.naked) noreturn {
asm volatile (
\\mov %%rsp, %%rdi
\\call rt_start
\\ud2
);
}
/// The first Zig frame, entered with `stack` pointing at the kernel-built entry
/// block. Build the `process.Init` from it and dispatch to the program's `main`,
/// whose signature is inspected at comptime. The heap is lazy (first alloc grows
/// it), so there is no other runtime init to order here.
export fn rt_start(stack: [*]const u64) callconv(.c) noreturn {
const init: process.Init = .{ .arguments = .{
.count = stack[0],
.vector = @ptrCast(stack + 1),
} };
process.exit(callMain(init));
}
/// Comptime-dispatch on root.main's signature, in the spirit of std's start.zig:
/// zero parameters or one `process.Init`; returns void, noreturn, u8, !void, or !u8.
fn callMain(init: process.Init) u8 {
const root = @import("root"); // root.zig, re-exporting the program's main
const main_information = @typeInfo(@TypeOf(root.main)).@"fn";
const call_arguments = switch (main_information.params.len) {
0 => .{},
1 => arguments: {
const Parameter = main_information.params[0].type orelse
@compileError("main's parameter must be runtime.process.Init (not anytype)");
if (Parameter != process.Init)
@compileError("main's parameter must be runtime.process.Init, found " ++ @typeName(Parameter));
break :arguments .{init};
},
else => @compileError("main takes no parameters or a single runtime.process.Init"),
};
const ReturnType = main_information.return_type.?;
switch (@typeInfo(ReturnType)) {
.noreturn => @call(.auto, root.main, call_arguments),
.void => {
@call(.auto, root.main, call_arguments);
return 0;
},
.int => {
if (ReturnType != u8)
@compileError("main's integer return type must be u8, found " ++ @typeName(ReturnType));
return @call(.auto, root.main, call_arguments);
},
.error_union => {
const payload = @call(.auto, root.main, call_arguments) catch |err| {
var buffer: [128]u8 = undefined;
const line = std.fmt.bufPrint(&buffer, "main returned error: {s}\n", .{@errorName(err)}) catch "main returned an error\n";
_ = logging.write(line);
return 1; // distinct from panic's 127
};
if (@TypeOf(payload) == void) return 0;
if (@TypeOf(payload) == u8) return payload;
@compileError("main's error-union payload must be void or u8, found " ++ @typeName(@TypeOf(payload)));
},
else => @compileError("main must return void, noreturn, u8, !void, or !u8, found " ++ @typeName(ReturnType)),
}
}
/// No runtime to unwind into — report a panic as a nonzero exit code.
pub const panic = std.debug.FullPanic(struct {
fn panic(_: []const u8, _: ?usize) noreturn {
process.exit(127);
}
}.panic);