87 lines
3.8 KiB
Zig
87 lines
3.8 KiB
Zig
//! The user-space process entry shim. Every user binary roots `_start` here (via
|
|
//! `entry = _start` in build.zig) and forces this file to be analysed with
|
|
//! `comptime { _ = &runtime.start._start; }`, so the whole runtime is linked in.
|
|
|
|
const std = @import("std");
|
|
const system = @import("system.zig");
|
|
const process = @import("process.zig");
|
|
|
|
/// 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),
|
|
} };
|
|
system.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"); // the user binary's root source file
|
|
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";
|
|
_ = system.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 {
|
|
system.exit(127);
|
|
}
|
|
}.panic);
|