Skip to content

Instantly share code, notes, and snippets.

@The-King-of-Toasters
Created September 16, 2024 11:36
Show Gist options
  • Save The-King-of-Toasters/4ce433deca8a21130040b10f59cc297d to your computer and use it in GitHub Desktop.
Save The-King-of-Toasters/4ce433deca8a21130040b10f59cc297d to your computer and use it in GitHub Desktop.
Syscall crash
//! To get started, run this tool with no args and read the help message.
//!
//! This tool extracts syscall numbers from the Linux source tree and emits an enumerated list per arch.
const std = @import("std");
const mem = std.mem;
const fmt = std.fmt;
const zig = std.zig;
const fs = std.fs;
const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
// Remove underscore prefix.
.{ "_llseek", "llseek" },
.{ "_newselect", "newselect" },
.{ "_sysctl", "sysctl" },
// Most 64-bit archs.
.{ "newfstat", "fstat64" },
.{ "newfstatat", "fstatat64" },
// POWER.
.{ "sync_file_range2", "sync_file_range" },
// ARM EABI/Thumb.
.{ "arm_sync_file_range", "sync_file_range" },
.{ "arm_fadvise64_64", "fadvise64_64" },
});
fn abiGen(comptime fields: []const []const u8) type {
const common = [_][]const u8{"common"} ++ fields;
return struct {
fn gen(abi: []const u8) bool {
for (common) |f|
if (mem.eql(u8, abi, f)) return true;
return false;
}
}.gen;
}
/// Used for arch's where the abi is the same value.
fn everythingAbi(_: []const u8) bool {
return true;
}
/// "common" or "32"
const abi32 = abiGen(&.{"32"});
/// "common" or "64"
const abi64 = abiGen(&.{"64"});
/// "common" or "oabi"
const abiArm = abiGen(&.{"oabi"});
/// "common", "32" or "nospu"
const abiPpc32 = abiGen(&.{ "32", "nospu" });
/// "common", "64" or "nospu"
const abiPpc64 = abiGen(&.{ "64", "nospu" });
/// These architectures have custom syscall numbers defined in arch-specific tables.
const specific: []struct {
var_name: []const u8,
table: []const u8,
abi: *const fn (abi: []const u8) bool,
header: ?[]const u8 = null,
footer: ?[]const u8 = null,
} = &.{
.{ .var_name = "X86", .table = "arch/x86/entry/syscalls/syscall_32.tbl", .abi = everythingAbi },
.{ .var_name = "X64", .table = "arch/x86/entry/syscalls/syscall_64.tbl", .abi = abi64 },
.{
.var_name = "Arm",
.table = "arch/arm/tools/syscall.tbl",
.abi = abiArm,
// TODO: These values haven't been brought over from `arch/arm/include/uapi/asm/unistd.h`.
.header = " const arm_base = 0x0f0000;\n\n",
.footer =
\\
\\ breakpoint = arm_base + 1,
\\ cacheflush = arm_base + 2,
\\ usr26 = arm_base + 3,
\\ usr32 = arm_base + 4,
\\ set_tls = arm_base + 5,
\\ get_tls = arm_base + 6,
\\
,
},
.{ .var_name = "Sparc", .table = "arch/sparc/kernel/syscalls/syscall.tbl", .abi = abi32 },
.{ .var_name = "Sparc64", .table = "arch/sparc/kernel/syscalls/syscall.tbl", .abi = abi64 },
.{ .var_name = "M68k", .table = "arch/m68k/kernel/syscalls/syscall.tbl", .abi = everythingAbi },
.{ .var_name = "PowerPC", .table = "arch/powerpc/kernel/syscalls/syscall.tbl", .abi = abiPpc32 },
.{ .var_name = "PowerPC64", .table = "arch/powerpc/kernel/syscalls/syscall.tbl", .abi = abiPpc64 },
.{ .var_name = "S390x", .table = "arch/s390/kernel/syscalls/syscall.tbl", .abi = abi64 },
.{ .var_name = "Xtensa", .table = "arch/xtensa/kernel/syscalls/syscall.tbl", .abi = everythingAbi },
.{ .var_name = "SuperH", .table = "arch/sh/kernel/syscalls/syscall.tbl", .abi = everythingAbi },
.{ .var_name = "Alpha", .table = "arch/alpha/kernel/syscalls/syscall.tbl", .abi = everythingAbi },
.{ .var_name = "PARisc", .table = "arch/parisc/kernel/syscalls/syscall.tbl", .abi = abi32 },
.{ .var_name = "PARisc64", .table = "arch/parisc/kernel/syscalls/syscall.tbl", .abi = abi64 },
};
/// The MIPS-based architectures are similar to the specific ones, except that
/// the syscall numbers are offset by a number specific to the arch.
const mips: []struct {
var_name: []const u8,
table: []const u8,
base: usize,
} = &.{
.{ .var_name = "MipsO32", .table = "arch/mips/kernel/syscalls/syscall_o32.tbl", .base = 4000 },
.{ .var_name = "MipsN64", .table = "arch/mips/kernel/syscalls/syscall_n64.tbl", .base = 5000 },
.{ .var_name = "MipsN32", .table = "arch/mips/kernel/syscalls/syscall_n32.tbl", .base = 6000 },
};
/// These architectures have their syscall numbers defined using the generic syscall
/// list introduced in c. 2012 for AArch64.
/// The 6.11 release converted this list into a single table, where parts of the
/// syscall ABI are enabled based on the presence of certain abi fields:
/// - 32: Syscalls using 64-bit types on 32-bit targets.
/// - 64: 64-bit native syscalls.
/// - time32: 32-bit time syscalls.
/// - renameat: Supports the older renameat syscall along with renameat2.
/// - rlimit: Supports the {get,set}rlimit syscalls.
/// - memfd_secret: Has an implementation of `memfd_secret`.
///
/// Arch-specfic syscalls between [244...259] are also enabled by adding the arch name as an abi.
///
/// The `abi` values are sourced from the respective `arch/{arch}/kernel/Makefile.syscalls` file.
const generic: []struct {
var_name: []const u8,
abi: *const fn (abi: []const u8) bool,
} = &.{
.{ .var_name = "Arm64", .abi = abiGen(&.{ "64", "renameat", "rlimit", "memfd_secret" }) },
.{ .var_name = "RiscV32", .abi = abiGen(&.{ "32", "riscv", "memfd_secret" }) },
.{ .var_name = "RiscV64", .abi = abiGen(&.{ "64", "riscv", "rlimit", "memfd_secret" }) },
.{ .var_name = "LoongArch64", .abi = abi64 },
.{ .var_name = "Arc", .abi = abiGen(&.{ "32", "arc", "time32", "renameat", "stat64", "rlimit" }) },
.{ .var_name = "CSky", .abi = abiGen(&.{ "32", "csky", "time32", "stat64", "rlimit" }) },
.{ .var_name = "OpenRisc", .abi = abiGen(&.{ "32", "or1k", "time32", "stat64", "rlimit", "renameat" }) },
.{ .var_name = "Nios2", .abi = abiGen(&.{ "32", "nios2", "time32", "stat64", "renameat", "rlimit" }) },
.{ .var_name = "Hexagon", .abi = abiGen(&.{ "32", "hexagon", "time32", "stat64", "rlimit", "renameat" }) },
};
const generic_table = "scripts/syscall.tbl";
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const args = try std.process.argsAlloc(allocator);
if (args.len < 2 or mem.eql(u8, args[1], "--help"))
usageAndExit(std.io.getStdErr(), args[0], 1);
const linux_path = args[1];
var buf_out = std.io.bufferedWriter(std.io.getStdOut().writer());
const writer = buf_out.writer();
var linux_dir = try std.fs.cwd().openDir(linux_path, .{});
defer linux_dir.close();
try writer.writeAll(
\\// This file is automatically generated.
\\// See tools/generate_linux_syscalls.zig for more info.
\\
\\
);
// As of 5.17.1, the largest table is 23467 bytes.
// 32k should be enough for now.
const buf = try allocator.alloc(u8, 1 << 15);
defer allocator.free(buf);
for (specific) |arch| {
try writer.print("pub const {s} = enum(usize) {\n", .{arch.var_name});
if (arch.header) |h|
writer.writeAll(h);
const table = try linux_dir.readFile(arch.table, buf);
var lines = mem.tokenizeScalar(u8, table, '\n');
while (lines.next()) |line| {
if (line[0] == '#') continue;
var fields = mem.tokenizeAny(u8, line, " \t");
const number = fields.next() orelse return error.Incomplete;
const abi = fields.next() orelse return error.Incomplete;
const name = fields.next() orelse return error.Incomplete;
if (!arch.abi(abi)) continue;
const final_name = stdlib_renames.get(name) orelse name;
try writer.print(" {s} = {},\n", .{ final_name, number });
}
if (arch.footer) |f|
writer.writeAll(f);
try writer.writeAll("};\n\n");
}
for (mips) |arch| {
try writer.print(
\\pub const {s} = enum(usize) {
\\ pub const linux_base = {}
\\
, .{ arch.var_name, arch.base });
const table = try linux_dir.readFile(arch.table, buf);
var lines = mem.tokenizeScalar(u8, table, '\n');
while (lines.next()) |line| {
if (line[0] == '#') continue;
var fields = mem.tokenizeAny(u8, line, " \t");
const number = fields.next() orelse return error.Incomplete;
_ = fields.next() orelse return error.Incomplete; // Always the same.
const name = fields.next() orelse return error.Incomplete;
const final_name = stdlib_renames.get(name) orelse name;
try writer.print(" {s} = linux_base + {},\n", .{ final_name, number });
}
try writer.writeAll("};\n\n");
}
for (generic, 0..) |arch, i| {
try writer.print("pub const {s} = enum(usize) {\n", .{arch.var_name});
const table = try linux_dir.readFile(arch.table, buf);
var lines = mem.tokenizeScalar(u8, table, '\n');
while (lines.next()) |line| {
if (line[0] == '#') continue;
var fields = mem.tokenizeAny(u8, line, " \t");
const number = fields.next() orelse return error.Incomplete;
const abi = fields.next() orelse return error.Incomplete;
const name = fields.next() orelse return error.Incomplete;
if (!arch.abi(abi)) continue;
const final_name = stdlib_renames.get(name) orelse name;
try writer.print(" {s} = {},\n", .{ final_name, number });
}
try writer.writeAll("};");
try writer.writeByteNTimes('\n', 1 + @intFromBool(i < generic.len - 1));
}
try buf_out.flush();
}
fn usageAndExit(file: fs.File, arg0: []const u8, code: u8) noreturn {
file.writer().print(
\\Usage: {s} /path/to/zig /path/to/linux
\\Alternative Usage: zig run /path/to/git/zig/tools/generate_linux_syscalls.zig -- /path/to/zig /path/to/linux
\\
\\Generates the list of Linux syscalls for each supported cpu arch, using the Linux development tree.
\\Prints to stdout Zig code which you can use to replace the file lib/std/os/linux/syscalls.zig.
\\
, .{arg0}) catch std.process.exit(1);
std.process.exit(code);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment