384 lines
17 KiB
Zig
384 lines
17 KiB
Zig
const lib = @import("lib");
|
|
const assert = lib.assert;
|
|
const log = lib.log.scoped(.RUNNER);
|
|
const host = @import("host");
|
|
const Configuration = lib.Configuration;
|
|
|
|
const Error = error{
|
|
wrong_argument_count,
|
|
disk_image_path_not_found,
|
|
cpu_driver_not_found,
|
|
loader_path_not_found,
|
|
qemu_options_not_found,
|
|
configuration_not_found,
|
|
configuration_wrong_argument,
|
|
ci_not_found,
|
|
debug_user_not_found,
|
|
debug_loader_not_found,
|
|
init_not_found,
|
|
image_configuration_path_not_found,
|
|
qemu_error,
|
|
not_implemented,
|
|
architecture_not_supported,
|
|
execution_environment_not_supported,
|
|
};
|
|
|
|
pub fn main() anyerror!void {
|
|
const max_file_length = lib.maxInt(usize);
|
|
var arena_allocator = host.ArenaAllocator.init(host.page_allocator);
|
|
defer arena_allocator.deinit();
|
|
var wrapped_allocator = lib.Allocator.wrap(arena_allocator.allocator());
|
|
|
|
const arguments_result: lib.ArgumentParser.Runner.Result = blk: {
|
|
const arguments = (try host.allocateArguments(wrapped_allocator.zigUnwrap()))[1..];
|
|
|
|
var argument_parser = lib.ArgumentParser.Runner{};
|
|
var argument_disk_image_path: ?[]const u8 = null;
|
|
var argument_cpu_driver_path: ?[]const u8 = null;
|
|
var argument_loader_path: ?[]const u8 = null;
|
|
var argument_qemu_options: ?lib.QEMUOptions = null;
|
|
var argument_configuration: ?Configuration = null;
|
|
var argument_image_configuration_path: ?[]const u8 = null;
|
|
var argument_ci: ?bool = null;
|
|
var argument_debug_user: ?bool = null;
|
|
var argument_debug_loader: ?bool = null;
|
|
var argument_init_path: ?[]const u8 = null;
|
|
var argument_index: usize = 0;
|
|
|
|
while (argument_parser.next()) |argument_type| switch (argument_type) {
|
|
.disk_image_path => {
|
|
argument_disk_image_path = arguments[argument_index];
|
|
argument_index += 1;
|
|
},
|
|
.cpu_driver => {
|
|
argument_cpu_driver_path = arguments[argument_index];
|
|
argument_index += 1;
|
|
},
|
|
.loader_path => {
|
|
argument_loader_path = arguments[argument_index];
|
|
argument_index += 1;
|
|
},
|
|
.qemu_options => {
|
|
const boolean_argument_strings = [2][]const u8{ arguments[argument_index], arguments[argument_index + 1] };
|
|
argument_index += 2;
|
|
|
|
argument_qemu_options = undefined;
|
|
inline for (lib.fields(lib.QEMUOptions), 0..) |field, field_index| {
|
|
@field(argument_qemu_options.?, field.name) = if (lib.equal(u8, boolean_argument_strings[field_index], "true")) true else if (lib.equal(u8, boolean_argument_strings[field_index], "false")) false else return Error.qemu_options_not_found;
|
|
}
|
|
},
|
|
.configuration => {
|
|
argument_configuration = undefined;
|
|
const configuration = &argument_configuration.?;
|
|
inline for (lib.fields(Configuration)) |field| {
|
|
@field(configuration, field.name) = lib.stringToEnum(field.type, arguments[argument_index]) orelse return Error.configuration_wrong_argument;
|
|
argument_index += 1;
|
|
}
|
|
},
|
|
.image_configuration_path => {
|
|
argument_image_configuration_path = arguments[argument_index];
|
|
argument_index += 1;
|
|
},
|
|
.ci => {
|
|
argument_ci = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.ci_not_found;
|
|
argument_index += 1;
|
|
},
|
|
.debug_user => {
|
|
argument_debug_user = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.debug_user_not_found;
|
|
argument_index += 1;
|
|
},
|
|
.debug_loader => {
|
|
argument_debug_loader = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.debug_loader_not_found;
|
|
argument_index += 1;
|
|
},
|
|
.init => {
|
|
argument_init_path = arguments[argument_index];
|
|
argument_index += 1;
|
|
},
|
|
};
|
|
|
|
if (argument_index != arguments.len) return Error.wrong_argument_count;
|
|
|
|
break :blk .{
|
|
.disk_image_path = argument_disk_image_path orelse return Error.disk_image_path_not_found,
|
|
.cpu_driver = argument_cpu_driver_path orelse return Error.cpu_driver_not_found,
|
|
.loader_path = argument_loader_path orelse return Error.loader_path_not_found,
|
|
.qemu_options = argument_qemu_options orelse return Error.qemu_options_not_found,
|
|
.configuration = argument_configuration orelse return Error.configuration_not_found,
|
|
.image_configuration_path = argument_image_configuration_path orelse return Error.image_configuration_path_not_found,
|
|
.ci = argument_ci orelse return Error.ci_not_found,
|
|
.debug_user = argument_debug_user orelse return Error.debug_user_not_found,
|
|
.debug_loader = argument_debug_loader orelse return Error.debug_loader_not_found,
|
|
.init = argument_init_path orelse return Error.init_not_found,
|
|
};
|
|
};
|
|
|
|
switch (arguments_result.configuration.execution_environment) {
|
|
.qemu => {
|
|
const qemu_options = arguments_result.qemu_options;
|
|
|
|
const config_file = try host.cwd().readFileAlloc(wrapped_allocator.zigUnwrap(), "config/qemu.json", max_file_length);
|
|
const parsed_arguments = try lib.json.parseFromSlice(Arguments, wrapped_allocator.zigUnwrap(), config_file, .{});
|
|
const arguments = parsed_arguments.value;
|
|
|
|
var argument_list = host.ArrayList([]const u8).init(wrapped_allocator.zigUnwrap());
|
|
|
|
try argument_list.append(try lib.concat(wrapped_allocator.zigUnwrap(), u8, &.{ "qemu-system-", @tagName(arguments_result.configuration.architecture) }));
|
|
|
|
if (qemu_options.is_test and !qemu_options.is_debug) {
|
|
try argument_list.appendSlice(&.{ "-device", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "isa-debug-exit,iobase=0x{x:0>2},iosize=0x{x:0>2}", .{ lib.QEMU.isa_debug_exit.io_base, lib.QEMU.isa_debug_exit.io_size }) });
|
|
}
|
|
|
|
switch (arguments_result.configuration.boot_protocol) {
|
|
.uefi => {
|
|
const ovmf_path = "tools/OVMF.fd";
|
|
if (host.cwd().openFile(ovmf_path, .{})) |file_descriptor| {
|
|
file_descriptor.close();
|
|
} else |_| {
|
|
const url = "https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd";
|
|
const uri = try host.Uri.parse(url);
|
|
|
|
var http_client = host.http.Client{ .allocator = wrapped_allocator.zigUnwrap() };
|
|
defer http_client.deinit();
|
|
|
|
var request_headers = host.http.Headers{ .allocator = wrapped_allocator.zigUnwrap() };
|
|
defer request_headers.deinit();
|
|
|
|
var request = try http_client.request(.GET, uri, request_headers, .{});
|
|
defer request.deinit();
|
|
|
|
try request.start();
|
|
try request.wait();
|
|
|
|
if (request.response.status != .ok) return error.ResponseNotOk;
|
|
const content_length = request.response.content_length orelse return error.OutOfMemory;
|
|
|
|
const buffer = try wrapped_allocator.zigUnwrap().alloc(u8, content_length);
|
|
const read_byte_count = try request.readAll(buffer);
|
|
if (read_byte_count != buffer.len) {
|
|
return error.OutOfMemory;
|
|
}
|
|
|
|
try host.cwd().writeFile(ovmf_path, buffer);
|
|
}
|
|
|
|
try argument_list.appendSlice(&.{ "-bios", ovmf_path });
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
const image_config = try lib.ImageConfig.get(wrapped_allocator.zigUnwrap(), arguments_result.image_configuration_path);
|
|
_ = image_config;
|
|
const disk_image_path = arguments_result.disk_image_path;
|
|
try argument_list.appendSlice(&.{ "-drive", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "file={s},index=0,media=disk,format=raw", .{disk_image_path}) });
|
|
|
|
try argument_list.append("-no-reboot");
|
|
|
|
if (!qemu_options.is_test) {
|
|
try argument_list.append("-no-shutdown");
|
|
}
|
|
|
|
if (arguments_result.ci) {
|
|
try argument_list.appendSlice(&.{ "-display", "none" });
|
|
}
|
|
|
|
//if (arguments.vga) |vga| {
|
|
//try argument_list.append("-vga");
|
|
//try argument_list.append(@tagName(vga));
|
|
//}
|
|
|
|
if (arguments.smp) |smp| {
|
|
try argument_list.append("-smp");
|
|
const smp_string = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "{}", .{smp});
|
|
try argument_list.append(smp_string);
|
|
}
|
|
|
|
if (arguments.debugcon) |debugcon| {
|
|
try argument_list.append("-debugcon");
|
|
try argument_list.append(@tagName(debugcon));
|
|
}
|
|
|
|
if (arguments.memory) |memory| {
|
|
try argument_list.append("-m");
|
|
if (arguments_result.ci) {
|
|
try argument_list.append("1G");
|
|
} else {
|
|
const memory_argument = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "{}{c}", .{ memory.amount, @as(u8, switch (memory.unit) {
|
|
.kilobyte => 'K',
|
|
.megabyte => 'M',
|
|
.gigabyte => 'G',
|
|
else => @panic("Unit too big"),
|
|
}) });
|
|
try argument_list.append(memory_argument);
|
|
}
|
|
}
|
|
|
|
if (lib.canVirtualizeWithQEMU(arguments_result.configuration.architecture, arguments_result.ci) and (arguments_result.configuration.execution_type == .accelerated or (arguments.virtualize orelse false))) {
|
|
try argument_list.appendSlice(&.{
|
|
"-accel",
|
|
switch (lib.os) {
|
|
.windows => "whpx",
|
|
.linux => "kvm",
|
|
.macos => "hvf",
|
|
else => @compileError("OS not supported"),
|
|
},
|
|
"-cpu",
|
|
"host",
|
|
});
|
|
} else {
|
|
switch (arguments_result.configuration.architecture) {
|
|
.x86_64 => try argument_list.appendSlice(&.{ "-cpu", "max" }),
|
|
else => return Error.architecture_not_supported,
|
|
}
|
|
|
|
if (arguments.trace) |tracees| {
|
|
for (tracees) |tracee| {
|
|
const tracee_slice = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "-{s}*", .{tracee});
|
|
try argument_list.append("-trace");
|
|
try argument_list.append(tracee_slice);
|
|
}
|
|
}
|
|
|
|
if (!arguments_result.ci) {
|
|
if (arguments.log) |log_configuration| {
|
|
var log_what = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap());
|
|
|
|
if (log_configuration.guest_errors) try log_what.appendSlice("guest_errors,");
|
|
if (log_configuration.interrupts) try log_what.appendSlice("int,");
|
|
if (!arguments_result.ci and log_configuration.assembly) try log_what.appendSlice("in_asm,");
|
|
|
|
if (log_what.items.len > 0) {
|
|
// Delete the last comma
|
|
_ = log_what.pop();
|
|
|
|
try argument_list.append("-d");
|
|
try argument_list.append(log_what.items);
|
|
|
|
if (log_configuration.interrupts) {
|
|
try argument_list.appendSlice(&.{ "-machine", "smm=off" });
|
|
}
|
|
}
|
|
|
|
if (log_configuration.file) |log_file| {
|
|
try argument_list.append("-D");
|
|
try argument_list.append(log_file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qemu_options.is_debug) {
|
|
try argument_list.append("-s");
|
|
if (!(arguments_result.configuration.execution_type == .accelerated or (arguments.virtualize orelse false))) {
|
|
try argument_list.append("-S");
|
|
}
|
|
|
|
// GF2, when not found in the PATH, can give problems
|
|
const use_gf = switch (lib.os) {
|
|
.macos => false,
|
|
.linux => false,
|
|
else => false,
|
|
};
|
|
|
|
var command_line_gdb = host.ArrayList([]const u8).init(wrapped_allocator.zigUnwrap());
|
|
if (use_gf) {
|
|
try command_line_gdb.append("gf2");
|
|
} else {
|
|
try command_line_gdb.append("kitty");
|
|
try command_line_gdb.append(switch (lib.os) {
|
|
.linux => "gdb",
|
|
.macos => "x86_64-elf-gdb",
|
|
else => "gdb",
|
|
});
|
|
}
|
|
|
|
try command_line_gdb.appendSlice(&.{ "-ex", switch (arguments_result.configuration.architecture) {
|
|
.x86_64 => "set disassembly-flavor intel\n",
|
|
else => return Error.architecture_not_supported,
|
|
} });
|
|
|
|
try command_line_gdb.appendSlice(&.{ "-ex", "target remote localhost:1234" });
|
|
if (arguments_result.debug_user) {
|
|
assert(!arguments_result.debug_loader);
|
|
try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.init}) });
|
|
} else if (arguments_result.debug_loader) {
|
|
assert(!arguments_result.debug_user);
|
|
try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.loader_path}) });
|
|
} else {
|
|
try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.cpu_driver}) });
|
|
}
|
|
|
|
const gdb_script_file = try host.cwd().openFile("config/gdb_script", .{});
|
|
var gdb_script_reader = gdb_script_file.reader();
|
|
while (try gdb_script_reader.readUntilDelimiterOrEofAlloc(wrapped_allocator.zigUnwrap(), '\n', max_file_length)) |gdb_script_line| {
|
|
try command_line_gdb.appendSlice(&.{ "-ex", gdb_script_line });
|
|
}
|
|
|
|
const debugger_process_arguments = switch (lib.os) {
|
|
.linux, .macos => command_line_gdb.items,
|
|
else => return Error.not_implemented,
|
|
};
|
|
|
|
var debugger_process = host.ChildProcess.init(debugger_process_arguments, wrapped_allocator.zigUnwrap());
|
|
try debugger_process.spawn();
|
|
}
|
|
|
|
var process = host.ChildProcess.init(argument_list.items, wrapped_allocator.zigUnwrap());
|
|
const result = try process.spawnAndWait();
|
|
|
|
if (result == .Exited) {
|
|
const exit_code = result.Exited;
|
|
if (exit_code & 1 != 0) {
|
|
const mask = lib.maxInt(@TypeOf(exit_code)) - 1;
|
|
const masked_exit_code = exit_code & mask;
|
|
|
|
if (masked_exit_code != 0) {
|
|
const qemu_exit_code = @as(lib.QEMU.ExitCode, @enumFromInt(masked_exit_code >> 1));
|
|
|
|
switch (qemu_exit_code) {
|
|
.success => return log.info("Success!", .{}),
|
|
.failure => log.err("QEMU exited with failure code 0x{x}", .{exit_code}),
|
|
_ => log.err("Totally unexpected value", .{}),
|
|
}
|
|
} else log.err("QEMU exited with unexpected code: {}. Masked: {}", .{ exit_code, masked_exit_code });
|
|
} else log.err("QEMU exited with unexpected code: {}", .{exit_code});
|
|
} else {
|
|
log.err("QEMU was {s}", .{@tagName(result)});
|
|
}
|
|
|
|
return Error.qemu_error;
|
|
},
|
|
}
|
|
}
|
|
|
|
const Arguments = struct {
|
|
const VGA = enum {
|
|
std,
|
|
cirrus,
|
|
vmware,
|
|
qxl,
|
|
xenfb,
|
|
tcx,
|
|
cg3,
|
|
virtio,
|
|
none,
|
|
};
|
|
memory: ?struct {
|
|
amount: u64,
|
|
unit: lib.SizeUnit,
|
|
},
|
|
virtualize: ?bool,
|
|
vga: ?VGA,
|
|
smp: ?usize,
|
|
debugcon: ?enum {
|
|
stdio,
|
|
},
|
|
log: ?struct {
|
|
file: ?[]const u8,
|
|
guest_errors: bool,
|
|
assembly: bool,
|
|
interrupts: bool,
|
|
},
|
|
trace: ?[]const []const u8,
|
|
};
|