Skip to content

Instantly share code, notes, and snippets.

@daurnimator
Last active December 12, 2023 05:55
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daurnimator/6518ece625b9c5f143ac51274b9dacfe to your computer and use it in GitHub Desktop.
Save daurnimator/6518ece625b9c5f143ac51274b9dacfe to your computer and use it in GitHub Desktop.
Trying to write a linux kernel module in zig
const std = @import("std");
const Builder = std.build.Builder;
const Step = std.build.Step;
fn uname() !std.os.utsname {
var uts: std.os.utsname = undefined;
return switch (std.os.errno(std.os.linux.uname(&uts))) {
0 => uts,
std.os.EFAULT => unreachable,
std.os.EPERM => error.PermissionDenied,
else => |err| std.os.unexpectedErrno(err),
};
}
var current_uname: ?std.os.utsname = null;
fn getUname() !*std.os.utsname {
if (current_uname == null) {
current_uname = try uname();
}
return &current_uname.?;
}
fn getKernelRelease() ![]const u8 {
const uts = try getUname();
return std.mem.toSliceConst(u8, @ptrCast([*:0]const u8, &uts.release));
}
pub fn build(b: *Builder) !void {
const target = b.standardTargetOptions(.{
.default_target = .{
.cpu_model = .baseline,
.os_tag = .freestanding,
.abi = .gnu,
},
});
const mode = b.standardReleaseOptions();
const module = b.addObject("my_module", "my_module.zig");
module.setTarget(target);
module.setBuildMode(mode);
module.setDisableGenH(true);
const kernel_version = b.option([]const u8, "kernel-version", "kernel version") orelse try getKernelRelease();
const dir = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "/lib/modules/", kernel_version, "/build/" });
defer b.allocator.free(dir);
const arch = "x86"; // TODO
const arch_include_dir = try std.mem.concat(b.allocator, u8, &[_][]const u8{ dir, "arch/", arch, "/include" });
defer b.allocator.free(arch_include_dir);
module.addIncludeDir(arch_include_dir);
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ arch_include_dir, "/generated" }));
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ dir, "include" }));
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ arch_include_dir, "/uapi" }));
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ arch_include_dir, "/generated/uapi" }));
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ dir, "include/uapi" }));
module.addIncludeDir(try std.mem.concat(b.allocator, u8, &[_][]const u8{ dir, "include/generated/uapi" }));
module.setOutputDir(".");
const orc = b.addSystemCommand(&[_][]const u8{
try std.mem.concat(b.allocator, u8, &[_][]const u8{ dir, "tools/objtool/objtool" }),
"orc",
"generate",
"--module",
"--no-fp",
"--retpoline",
"--uaccess",
});
orc.addArtifactArg(module);
b.default_step.dependOn(&orc.step);
}
const std = @import("std");
// TODO: Have this function do the export
// https://github.com/ziglang/zig/issues/3753
fn MODULE_INFO(comptime tag: []const u8, comptime info: []const u8) [tag.len + 1 + info.len:0]u8 {
return (tag ++ "=" ++ info ++ "").*;
}
// The names of these symbols doesn't matter
export const MODINFO_0 linksection(".modinfo") = MODULE_INFO("name", "my_module");
export const MODINFO_1 linksection(".modinfo") = MODULE_INFO("license", "Dual MIT/GPL");
export const MODINFO_2 linksection(".modinfo") = MODULE_INFO("author", "Daurnimator");
export const MODINFO_3 linksection(".modinfo") = MODULE_INFO("description", "A proof of concept kernel module written in zig");
export const MODINFO_4 linksection(".modinfo") = MODULE_INFO("version", "0.0");
// TODO: https://github.com/ziglang/zig/issues/3755
// export const MODINFO_5 linksection(".modinfo") = MODULE_INFO("retpoline", "Y");
const linux = @cImport({
@cDefine("__KERNEL__", {});
@cDefine("MODULE", {});
@cDefine("KBUILD_BASENAME", "\"my_module\"");
@cDefine("KBUILD_MODNAME", "\"my_module\"");
@cInclude("linux/kconfig.h");
// Workaround recommended by Kees Cook (similar to hack done in bpf headers)
@cInclude("linux/types.h");
@cUndef("asm_inline");
@cDefine("asm_inline", "asm");
@cInclude("linux/init.h");
@cInclude("linux/module.h");
@cInclude("linux/kernel.h");
@cInclude("linux/printk.h");
});
// TODO: https://github.com/ziglang/zig/issues/1499
// As a workaround I fake everything that uses `kobject`
const fake_module = struct {
usingnamespace linux;
pub const fake_struct_kobject = extern struct {
name: [*:0]const u8,
entry: struct_list_head,
parent: *fake_struct_kobject,
// kset: [*c]struct_kset,
kset: *@OpaqueType(),
ktype: [*c]struct_kobj_type,
sd: [*c]struct_kernfs_node,
kref: struct_kref,
// unsigned int state_initialized:1;
// unsigned int state_in_sysfs:1;
// unsigned int state_add_uevent_sent:1;
// unsigned int state_remove_uevent_sent:1;
// unsigned int uevent_suppress:1;
packed_bits: u8,
};
pub const fake_struct_module_kobject = extern struct {
kobj: fake_struct_kobject,
mod: [*c]fake_struct_module,
drivers_dir: ?*fake_struct_kobject,
mp: ?*struct_module_param_attrs,
kobj_completion: [*c]struct_completion,
};
pub const fake_struct_mod_tree_node = extern struct {
mod: [*c]fake_struct_module,
node: struct_latch_tree_node,
};
pub const fake_struct_module_layout = extern struct {
base: ?*c_void,
size: c_uint,
text_size: c_uint,
ro_size: c_uint,
ro_after_init_size: c_uint,
mtn: fake_struct_mod_tree_node,
};
pub const fake_struct_module = extern struct {
state: enum_module_state,
list: struct_list_head,
name: [56]u8,
mkobj: fake_struct_module_kobject,
modinfo_attrs: *@OpaqueType(),
version: [*c]const u8,
srcversion: [*c]const u8,
holders_dir: ?*fake_struct_kobject,
syms: [*c]const struct_kernel_symbol,
crcs: [*c]const s32,
num_syms: c_uint,
param_lock: struct_mutex,
kp: *@OpaqueType(),
num_kp: c_uint,
num_gpl_syms: c_uint,
gpl_syms: [*c]const struct_kernel_symbol,
gpl_crcs: [*c]const s32,
unused_syms: [*c]const struct_kernel_symbol,
unused_crcs: [*c]const s32,
num_unused_syms: c_uint,
num_unused_gpl_syms: c_uint,
unused_gpl_syms: [*c]const struct_kernel_symbol,
unused_gpl_crcs: [*c]const s32,
sig_ok: _bool,
async_probe_requested: _bool,
gpl_future_syms: [*c]const struct_kernel_symbol,
gpl_future_crcs: [*c]const s32,
num_gpl_future_syms: c_uint,
num_exentries: c_uint,
extable: ?*struct_exception_table_entry,
init: ?fn () callconv(.C) c_int,
core_layout: fake_struct_module_layout,
init_layout: fake_struct_module_layout,
arch: struct_mod_arch_specific,
taints: c_ulong,
num_bugs: c_uint,
bug_list: struct_list_head,
bug_table: [*c]struct_bug_entry,
kallsyms: [*c]struct_mod_kallsyms,
core_kallsyms: struct_mod_kallsyms,
sect_attrs: ?*struct_module_sect_attrs,
notes_attrs: ?*struct_module_notes_attrs,
args: [*c]u8,
percpu: ?*c_void,
percpu_size: c_uint,
num_tracepoints: c_uint,
tracepoints_ptrs: [*c]const tracepoint_ptr_t,
num_srcu_structs: c_uint,
srcu_struct_ptrs: [*c][*c]struct_srcu_struct,
num_bpf_raw_events: c_uint,
bpf_raw_events: [*c]struct_bpf_raw_event_map,
jump_entries: [*c]struct_jump_entry,
num_jump_entries: c_uint,
num_trace_bprintk_fmt: c_uint,
trace_bprintk_fmt_start: [*c][*c]const u8,
trace_events: [*c]?*struct_trace_event_call,
num_trace_events: c_uint,
trace_evals: [*c]?*struct_trace_eval_map,
num_trace_evals: c_uint,
num_ftrace_callsites: c_uint,
ftrace_callsites: [*c]c_ulong,
source_list: struct_list_head,
target_list: struct_list_head,
exit: ?fn () callconv(.C) void,
refcnt: atomic_t,
ei_funcs: [*c]struct_error_injection_entry,
num_ei_funcs: c_uint,
};
}.fake_struct_module;
export var __this_module linksection(".gnu.linkonce.this_module") = blk: {
// var module: linux.module = undefined;
var module: fake_module = undefined;
module.name = ("my_module" ++ [_]u8{0} ** 47).*;
module.init = init_module;
module.exit = cleanup_module;
break :blk module;
};
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn {
@setCold(true);
// TODO: https://github.com/ziglang/zig/issues/1481#issuecomment-557872850
// linux.panic("%s", message);
linux.panic("zig panic!");
}
export fn init_module() linksection(".init.text") c_int {
@setCold(true);
_ = linux.printk("hello\n");
return 0;
}
export fn cleanup_module() linksection(".exit.text") void {
@setCold(true);
_ = linux.printk("bye\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment