| 1 | //! To get started, run this tool with no args and read the help message. |
| 2 | //! |
| 3 | //! This tool extracts the Linux syscall numbers from the Linux source tree |
| 4 | //! directly, and emits an enumerated list per supported Zig arch. |
| 5 | //! |
| 6 | //! As of kernel version 6.11, all supported architectures have their syscalls |
| 7 | //! defined in files with the following tabular format: |
| 8 | //! |
| 9 | //! # Comment |
| 10 | //! <number> <abi> <name> ... |
| 11 | //! |
| 12 | //! Everything after `name` is ignored for the purposes of this tool. |
| 13 | |
| 14 | const std = @import("std"); |
| 15 | const Io = std.Io; |
| 16 | const mem = std.mem; |
| 17 | |
| 18 | const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{ |
| 19 | // Remove underscore prefix. |
| 20 | .{ "_llseek", "llseek" }, |
| 21 | .{ "_newselect", "newselect" }, |
| 22 | .{ "_sysctl", "sysctl" }, |
| 23 | // ARM EABI/Thumb. |
| 24 | .{ "arm_sync_file_range", "sync_file_range" }, |
| 25 | .{ "arm_fadvise64_64", "fadvise64_64" }, |
| 26 | // Alpha |
| 27 | // See https://github.com/torvalds/linux/blob/8bf22c33e7a172fbc72464f4cc484d23a6b412ba/arch/alpha/include/uapi/asm/unistd.h#L10 |
| 28 | .{ "getxpid", "getpid" }, |
| 29 | .{ "getxuid", "getuid" }, |
| 30 | .{ "getxgid", "getgid" }, |
| 31 | }); |
| 32 | |
| 33 | /// Filter syscalls that aren't actually syscalls. |
| 34 | fn isReserved(name: []const u8) bool { |
| 35 | return mem.startsWith(u8, name, "available") or |
| 36 | mem.startsWith(u8, name, "reserved") or |
| 37 | mem.startsWith(u8, name, "unused"); |
| 38 | } |
| 39 | |
| 40 | /// Values of the `abi` field in use by the syscall tables. |
| 41 | /// |
| 42 | /// Since c. 2012, all new Linux architectures use the same numbers for their syscalls. |
| 43 | /// Before kernel 6.11, the source of truth for this list was the arch-specific `uapi` headers. |
| 44 | /// The 6.11 release converted this into a unified table with the same format as the older archs. |
| 45 | /// For these targets, syscalls are enabled/disabled based on the `abi` field. |
| 46 | /// These fields are sourced from the respective `arch/{arch}/kernel/Makefile.syscalls` |
| 47 | /// files in the kernel source tree. |
| 48 | /// Architecture-specific syscalls between [244...259] are also enabled by adding the arch name as an abi. |
| 49 | const Abi = enum { |
| 50 | /// Syscalls common to two or more sub-targets. |
| 51 | /// Often used for single targets in lieu of a nil value. |
| 52 | common, |
| 53 | /// Syscalls using 64-bit types on 32-bit targets. |
| 54 | @"32", |
| 55 | /// 64-bit native syscalls. |
| 56 | @"64", |
| 57 | /// 32-bit time syscalls. |
| 58 | time32, |
| 59 | /// Supports the older renameat syscall along with renameat2. |
| 60 | renameat, |
| 61 | /// Supports the fstatat64 syscall. |
| 62 | stat64, |
| 63 | /// Supports the {get,set}rlimit syscalls. |
| 64 | rlimit, |
| 65 | /// Implements `memfd_secret` and friends. |
| 66 | memfd_secret, |
| 67 | // Architecture-specific syscalls. |
| 68 | x32, |
| 69 | eabi, |
| 70 | nospu, |
| 71 | arc, |
| 72 | csky, |
| 73 | nios2, |
| 74 | or1k, |
| 75 | riscv, |
| 76 | hexagon, |
| 77 | }; |
| 78 | |
| 79 | const __X32_SYSCALL_BIT: u32 = 0x40000000; |
| 80 | const __NR_Linux_O32: u32 = 4000; |
| 81 | const __NR_Linux_N64: u32 = 5000; |
| 82 | const __NR_Linux_N32: u32 = 6000; |
| 83 | |
| 84 | const Arch = struct { |
| 85 | /// Name for the generated enum variable. |
| 86 | @"var": []const u8, |
| 87 | /// Location of the table if this arch doesn't use the generic one. |
| 88 | table: union(enum) { generic: void, specific: []const u8 }, |
| 89 | /// List of abi features to filter on. |
| 90 | /// An empty list implies the abi field is a constant value, thus skipping validation. |
| 91 | abi: []const Abi = &.{}, |
| 92 | /// Some architectures need special handling: |
| 93 | /// - x32 system calls must have their number OR'ed with |
| 94 | /// `__X32_SYSCALL_BIT` to distinguish them against the regular x86_64 calls. |
| 95 | /// - Mips systems calls are offset by a set number based on the ABI. |
| 96 | /// |
| 97 | /// Because the `__X32_SYSCALL_BIT` mask is so large, we can turn the OR into a |
| 98 | /// normal addition and apply a base offset for all targets, defaulting to 0. |
| 99 | offset: u32 = 0, |
| 100 | header: ?[]const u8 = null, |
| 101 | footer: ?[]const u8 = null, |
| 102 | |
| 103 | fn get(self: Arch, line: []const u8) ?struct { []const u8, u32 } { |
| 104 | var iter = mem.tokenizeAny(u8, line, " \t"); |
| 105 | const num_str = iter.next() orelse @panic("Bad field"); |
| 106 | const abi = iter.next() orelse @panic("Bad field"); |
| 107 | const name = iter.next() orelse @panic("Bad field"); |
| 108 | |
| 109 | // Filter out syscalls that aren't actually syscalls. |
| 110 | if (isReserved(name)) return null; |
| 111 | // Check abi field matches |
| 112 | const abi_match: bool = if (self.abi.len == 0) true else blk: { |
| 113 | for (self.abi) |a| |
| 114 | if (mem.eql(u8, @tagName(a), abi)) break :blk true; |
| 115 | break :blk false; |
| 116 | }; |
| 117 | if (!abi_match) return null; |
| 118 | |
| 119 | var num = std.fmt.parseInt(u32, num_str, 10) catch @panic("Bad syscall number"); |
| 120 | num += self.offset; |
| 121 | |
| 122 | return .{ name, num }; |
| 123 | } |
| 124 | }; |
| 125 | |
| 126 | const architectures: []const Arch = &.{ |
| 127 | .{ .@"var" = "X86", .table = .{ .specific = "arch/x86/entry/syscalls/syscall_32.tbl" } }, |
| 128 | .{ .@"var" = "X64", .table = .{ .specific = "arch/x86/entry/syscalls/syscall_64.tbl" }, .abi = &.{ .common, .@"64" } }, |
| 129 | .{ .@"var" = "X32", .table = .{ .specific = "arch/x86/entry/syscalls/syscall_64.tbl" }, .abi = &.{ .common, .x32 }, .offset = __X32_SYSCALL_BIT }, |
| 130 | .{ |
| 131 | .@"var" = "Arm", |
| 132 | .table = .{ .specific = "arch/arm/tools/syscall.tbl" }, |
| 133 | .abi = &.{ .common, .eabi }, |
| 134 | // These values haven't been brought over from `arch/arm/include/uapi/asm/unistd.h`, |
| 135 | // so we are forced to add them ourselves. |
| 136 | .header = " const arm_base = 0x0f0000;\n\n", |
| 137 | .footer = |
| 138 | \\ |
| 139 | \\ breakpoint = arm_base + 1, |
| 140 | \\ cacheflush = arm_base + 2, |
| 141 | \\ usr26 = arm_base + 3, |
| 142 | \\ usr32 = arm_base + 4, |
| 143 | \\ set_tls = arm_base + 5, |
| 144 | \\ get_tls = arm_base + 6, |
| 145 | \\ |
| 146 | , |
| 147 | }, |
| 148 | .{ .@"var" = "Sparc", .table = .{ .specific = "arch/sparc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"32" } }, |
| 149 | .{ .@"var" = "Sparc64", .table = .{ .specific = "arch/sparc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"64" } }, |
| 150 | .{ .@"var" = "M68k", .table = .{ .specific = "arch/m68k/kernel/syscalls/syscall.tbl" } }, |
| 151 | // For Mips, the abi for these tables is always o32/n64/n32. |
| 152 | .{ .@"var" = "MipsO32", .table = .{ .specific = "arch/mips/kernel/syscalls/syscall_o32.tbl" }, .offset = __NR_Linux_O32 }, |
| 153 | .{ .@"var" = "MipsN64", .table = .{ .specific = "arch/mips/kernel/syscalls/syscall_n64.tbl" }, .offset = __NR_Linux_N64 }, |
| 154 | .{ .@"var" = "MipsN32", .table = .{ .specific = "arch/mips/kernel/syscalls/syscall_n32.tbl" }, .offset = __NR_Linux_N32 }, |
| 155 | .{ .@"var" = "PowerPC", .table = .{ .specific = "arch/powerpc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"32", .nospu } }, |
| 156 | .{ .@"var" = "PowerPC64", .table = .{ .specific = "arch/powerpc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"64", .nospu } }, |
| 157 | .{ .@"var" = "S390x", .table = .{ .specific = "arch/s390/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"64" } }, |
| 158 | .{ .@"var" = "Xtensa", .table = .{ .specific = "arch/xtensa/kernel/syscalls/syscall.tbl" } }, |
| 159 | .{ .@"var" = "Alpha", .table = .{ .specific = "arch/alpha/kernel/syscalls/syscall.tbl" } }, |
| 160 | .{ .@"var" = "Arm64", .table = .generic, .abi = &.{ .common, .@"64", .renameat, .rlimit, .memfd_secret } }, |
| 161 | .{ .@"var" = "RiscV32", .table = .generic, .abi = &.{ .common, .@"32", .riscv, .memfd_secret } }, |
| 162 | .{ .@"var" = "RiscV64", .table = .generic, .abi = &.{ .common, .@"64", .riscv, .rlimit, .memfd_secret } }, |
| 163 | .{ .@"var" = "LoongArch32", .table = .generic, .abi = &.{ .common, .@"32" } }, |
| 164 | .{ .@"var" = "LoongArch64", .table = .generic, .abi = &.{ .common, .@"64" } }, |
| 165 | .{ .@"var" = "Arc", .table = .generic, .abi = &.{ .common, .@"32", .arc, .time32, .renameat, .stat64, .rlimit } }, |
| 166 | .{ .@"var" = "CSky", .table = .generic, .abi = &.{ .common, .@"32", .csky, .time32, .stat64, .rlimit } }, |
| 167 | .{ .@"var" = "Hexagon", .table = .generic, .abi = &.{ .common, .@"32", .hexagon, .time32, .stat64, .rlimit, .renameat } }, |
| 168 | .{ .@"var" = "OpenRisc", .table = .generic, .abi = &.{ .common, .@"32", .or1k, .time32, .stat64, .rlimit, .renameat } }, |
| 169 | .{ .@"var" = "Hppa", .table = .{ .specific = "arch/parisc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"32" } }, |
| 170 | .{ .@"var" = "Hppa64", .table = .{ .specific = "arch/parisc/kernel/syscalls/syscall.tbl" }, .abi = &.{ .common, .@"64" } }, |
| 171 | .{ .@"var" = "Sh", .table = .{ .specific = "arch/sh/kernel/syscalls/syscall.tbl" } }, |
| 172 | .{ .@"var" = "Microblaze", .table = .{ .specific = "arch/microblaze/kernel/syscalls/syscall.tbl" } }, |
| 173 | }; |
| 174 | |
| 175 | pub fn main(init: std.process.Init) !void { |
| 176 | const gpa = init.gpa; |
| 177 | const io = init.io; |
| 178 | |
| 179 | const args = try init.minimal.args.toSlice(init.arena.allocator()); |
| 180 | if (args.len < 2 or mem.eql(u8, args[1], "--help")) { |
| 181 | const stderr = std.debug.lockStderr(&.{}); |
| 182 | const w = &stderr.file_writer.interface; |
| 183 | usage(w, args[0]) catch std.process.exit(2); |
| 184 | std.process.exit(1); |
| 185 | } |
| 186 | const linux_path = args[1]; |
| 187 | |
| 188 | var stdout_buffer: [2048]u8 = undefined; |
| 189 | var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); |
| 190 | const stdout = &stdout_writer.interface; |
| 191 | |
| 192 | var linux_dir = try Io.Dir.cwd().openDir(io, linux_path, .{}); |
| 193 | defer linux_dir.close(io); |
| 194 | |
| 195 | // As of 6.11, the largest table is 24195 bytes. |
| 196 | // 32k should be enough for now. |
| 197 | const buf = try gpa.alloc(u8, 1 << 15); |
| 198 | defer gpa.free(buf); |
| 199 | |
| 200 | // Fetch the kernel version from the Makefile variables. |
| 201 | const version = blk: { |
| 202 | const head = try linux_dir.readFile(io, "Makefile", buf[0..128]); |
| 203 | var lines = mem.tokenizeScalar(u8, head, '\n'); |
| 204 | _ = lines.next(); // Skip SPDX identifier |
| 205 | |
| 206 | var ver = mem.zeroes(std.SemanticVersion); |
| 207 | inline for (.{ "major", "minor", "patch" }, .{ "VERSION", "PATCHLEVEL", "SUBLEVEL" }) |field, make_var| { |
| 208 | const line = lines.next() orelse @panic("Bad line"); |
| 209 | const offset = (make_var ++ " = ").len; |
| 210 | @field(ver, field) = try std.fmt.parseInt(usize, line[offset..], 10); |
| 211 | } |
| 212 | |
| 213 | break :blk ver; |
| 214 | }; |
| 215 | |
| 216 | try Io.Writer.print(stdout, |
| 217 | \\// This file is automatically generated by tools/generate_linux_syscalls.zig |
| 218 | \\// This list current as of kernel: {f} |
| 219 | \\ |
| 220 | \\ |
| 221 | , .{version}); |
| 222 | |
| 223 | for (architectures, 0..) |arch, i| { |
| 224 | const table = try linux_dir.readFile(io, switch (arch.table) { |
| 225 | .generic => "scripts/syscall.tbl", |
| 226 | .specific => |f| f, |
| 227 | }, buf); |
| 228 | |
| 229 | try Io.Writer.print(stdout, "pub const {s} = enum(usize) {{\n", .{arch.@"var"}); |
| 230 | if (arch.header) |h| |
| 231 | try Io.Writer.writeAll(stdout, h); |
| 232 | |
| 233 | var lines = mem.tokenizeScalar(u8, table, '\n'); |
| 234 | while (lines.next()) |line| { |
| 235 | if (line[0] == '#') continue; |
| 236 | if (arch.get(line)) |res| { |
| 237 | const name, const num = res; |
| 238 | const final_name = stdlib_renames.get(name) orelse name; |
| 239 | try Io.Writer.print(stdout, " {f} = {d},\n", .{ std.zig.fmtId(final_name), num }); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | if (arch.footer) |f| |
| 244 | try Io.Writer.writeAll(stdout, f); |
| 245 | try Io.Writer.writeAll(stdout, "};\n"); |
| 246 | if (i != architectures.len - 1) |
| 247 | try Io.Writer.writeByte(stdout, '\n'); |
| 248 | } |
| 249 | |
| 250 | try Io.Writer.flush(stdout); |
| 251 | } |
| 252 | |
| 253 | fn usage(w: *std.Io.Writer, arg0: []const u8) std.Io.Writer.Error!void { |
| 254 | try w.print( |
| 255 | \\Usage: {s} /path/to/linux |
| 256 | \\Alternative Usage: zig run /path/to/git/zig/tools/generate_linux_syscalls.zig -- /path/to/linux |
| 257 | \\ |
| 258 | \\Generates the list of Linux syscalls for each supported cpu arch, using the Linux development tree. |
| 259 | \\Prints to stdout Zig code which you can use to replace the file lib/std/os/linux/syscalls.zig. |
| 260 | \\ |
| 261 | , .{arg0}); |
| 262 | } |