Commit 7f1378909b

Matt Knight <mattnite@protonmail.com>
2020-08-19 07:03:51
moved bpf syscall, added some bpf instructions and tests
1 parent 243b5c7
Changed files (2)
lib
std
os
bits
linux
lib/std/os/bits/linux/bpf.zig
@@ -5,6 +5,61 @@
 // and substantial portions of the software.
 usingnamespace std.os;
 const std = @import("../../../std.zig");
+const expectEqual = std.testing.expectEqual;
+const fd_t = std.os.fd_t;
+const pid_t = std.os.pid_t;
+
+// instruction classes
+pub const LD = 0x00;
+pub const LDX = 0x01;
+pub const ST = 0x02;
+pub const STX = 0x03;
+pub const ALU = 0x04;
+pub const JMP = 0x05;
+pub const RET = 0x06;
+pub const MISC = 0x07;
+
+/// 32-bit
+pub const W = 0x00;
+/// 16-bit
+pub const H = 0x08;
+/// 8-bit
+pub const B = 0x10;
+/// 64-bit
+pub const DW = 0x18;
+
+pub const IMM = 0x00;
+pub const ABS = 0x20;
+pub const IND = 0x40;
+pub const MEM = 0x60;
+pub const LEN = 0x80;
+pub const MSH = 0xa0;
+
+// alu fields
+pub const ADD = 0x00;
+pub const SUB = 0x10;
+pub const MUL = 0x20;
+pub const DIV = 0x30;
+pub const OR = 0x40;
+pub const AND = 0x50;
+pub const LSH = 0x60;
+pub const RSH = 0x70;
+pub const NEG = 0x80;
+pub const MOD = 0x90;
+pub const XOR = 0xa0;
+
+// jmp fields
+pub const JA = 0x00;
+pub const JEQ = 0x10;
+pub const JGT = 0x20;
+pub const JGE = 0x30;
+pub const JSET = 0x40;
+
+//#define BPF_SRC(code)   ((code) & 0x08)
+pub const K = 0x00;
+pub const X = 0x08;
+
+pub const MAXINSNS = 4096;
 
 // instruction classes
 /// jmp mode in word width
@@ -13,8 +68,6 @@ pub const JMP32 = 0x06;
 pub const ALU64 = 0x07;
 
 // ld/ldx fields
-/// double word (64-bit)
-pub const DW = 0x18;
 /// exclusive add
 pub const XADD = 0xc0;
 
@@ -153,6 +206,130 @@ pub const BPF_F_CLONE = 0x200;
 /// flag for BPF_MAP_CREATE command. Enable memory-mapping BPF map
 pub const BPF_F_MMAPABLE = 0x400;
 
+/// These values correspond to "syscalls" within the BPF program's environment
+pub const Helper = enum(i32) {
+    unspec,
+    map_lookup_elem,
+    map_update_elem,
+    map_delete_elem,
+    probe_read,
+    ktime_get_ns,
+    trace_printk,
+    get_prandom_u32,
+    get_smp_processor_id,
+    skb_store_bytes,
+    l3_csum_replace,
+    l4_csum_replace,
+    tail_call,
+    clone_redirect,
+    get_current_pid_tgid,
+    get_current_uid_gid,
+    get_current_comm,
+    get_cgroup_classid,
+    skb_vlan_push,
+    skb_vlan_pop,
+    skb_get_tunnel_key,
+    skb_set_tunnel_key,
+    perf_event_read,
+    redirect,
+    get_route_realm,
+    perf_event_output,
+    skb_load_bytes,
+    get_stackid,
+    csum_diff,
+    skb_get_tunnel_opt,
+    skb_set_tunnel_opt,
+    skb_change_proto,
+    skb_change_type,
+    skb_under_cgroup,
+    get_hash_recalc,
+    get_current_task,
+    probe_write_user,
+    current_task_under_cgroup,
+    skb_change_tail,
+    skb_pull_data,
+    csum_update,
+    set_hash_invalid,
+    get_numa_node_id,
+    skb_change_head,
+    xdp_adjust_head,
+    probe_read_str,
+    get_socket_cookie,
+    get_socket_uid,
+    set_hash,
+    setsockopt,
+    skb_adjust_room,
+    redirect_map,
+    sk_redirect_map,
+    sock_map_update,
+    xdp_adjust_meta,
+    perf_event_read_value,
+    perf_prog_read_value,
+    getsockopt,
+    override_return,
+    sock_ops_cb_flags_set,
+    msg_redirect_map,
+    msg_apply_bytes,
+    msg_cork_bytes,
+    msg_pull_data,
+    bind,
+    xdp_adjust_tail,
+    skb_get_xfrm_state,
+    get_stack,
+    skb_load_bytes_relative,
+    fib_lookup,
+    sock_hash_update,
+    msg_redirect_hash,
+    sk_redirect_hash,
+    lwt_push_encap,
+    lwt_seg6_store_bytes,
+    lwt_seg6_adjust_srh,
+    lwt_seg6_action,
+    rc_repeat,
+    rc_keydown,
+    skb_cgroup_id,
+    get_current_cgroup_id,
+    get_local_storage,
+    sk_select_reuseport,
+    skb_ancestor_cgroup_id,
+    sk_lookup_tcp,
+    sk_lookup_udp,
+    sk_release,
+    map_push_elem,
+    map_pop_elem,
+    map_peek_elem,
+    msg_push_data,
+    msg_pop_data,
+    rc_pointer_rel,
+    spin_lock,
+    spin_unlock,
+    sk_fullsock,
+    tcp_sock,
+    skb_ecn_set_ce,
+    get_listener_sock,
+    skc_lookup_tcp,
+    tcp_check_syncookie,
+    sysctl_get_name,
+    sysctl_get_current_value,
+    sysctl_get_new_value,
+    sysctl_set_new_value,
+    strtol,
+    strtoul,
+    sk_storage_get,
+    sk_storage_delete,
+    send_signal,
+    tcp_gen_syncookie,
+    skb_output,
+    probe_read_user,
+    probe_read_kernel,
+    probe_read_user_str,
+    probe_read_kernel_str,
+    tcp_send_ack,
+    send_signal_thread,
+    jiffies64,
+    _,
+};
+
 /// a single BPF instruction
 pub const Insn = packed struct {
     code: u8,
@@ -163,32 +340,163 @@ pub const Insn = packed struct {
 
     /// r0 - r9 are general purpose 64-bit registers, r10 points to the stack
     /// frame
-    pub const Reg = enum(u4) {
-        r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10
+    pub const Reg = packed enum(u4) { r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10 };
+    const Source = packed enum(u1) { reg, imm };
+    const AluOp = packed enum(u8) {
+        add = ADD,
+        sub = SUB,
+        mul = MUL,
+        div = DIV,
+        op_or = OR,
+        op_and = AND,
+        lsh = LSH,
+        rsh = RSH,
+        neg = NEG,
+        mod = MOD,
+        xor = XOR,
+        mov = MOV,
+    };
+
+    pub const Size = packed enum(u8) {
+        byte = B,
+        half_word = H,
+        word = W,
+        double_word = DW,
     };
 
-    const alu = 0x04;
-    const jmp = 0x05;
-    const mov = 0xb0;
-    const k = 0;
-    const exit_code = 0x90;
+    const JmpOp = packed enum(u8) {
+        ja = JA,
+        jeq = JEQ,
+        jgt = JGT,
+        jge = JGE,
+        jset = JSET,
+    };
+
+    const ImmOrReg = union(Source) {
+        imm: i32,
+        reg: Reg,
+    };
+
+    fn imm_reg(code: u8, dst: Reg, src: anytype, off: i16) Insn {
+        const imm_or_reg = if (@typeInfo(@TypeOf(src)) == .EnumLiteral)
+            ImmOrReg{ .reg = @as(Reg, src) }
+        else
+            ImmOrReg{ .imm = src };
+
+        const src_type = switch (imm_or_reg) {
+            .imm => K,
+            .reg => X,
+        };
+
+        return Insn{
+            .code = code | src_type,
+            .dst = @enumToInt(dst),
+            .src = switch (imm_or_reg) {
+                .imm => 0,
+                .reg => |r| @enumToInt(r),
+            },
+            .off = off,
+            .imm = switch (imm_or_reg) {
+                .imm => |i| i,
+                .reg => 0,
+            },
+        };
+    }
+
+    fn alu(comptime width: comptime_int, op: AluOp, dst: Reg, src: anytype) Insn {
+        const width_bitfield = switch (width) {
+            32 => ALU,
+            64 => ALU64,
+            else => @compileError("width must be 32 or 64"),
+        };
+
+        return imm_reg(width_bitfield | @enumToInt(op), dst, src, 0);
+    }
+
+    pub fn mov(dst: Reg, src: anytype) Insn {
+        return alu(64, .mov, dst, src);
+    }
+
+    pub fn add(dst: Reg, src: anytype) Insn {
+        return alu(64, .add, dst, src);
+    }
+
+    fn jmp(op: JmpOp, dst: Reg, src: anytype, off: i16) Insn {
+        return imm_reg(JMP | @enumToInt(op), dst, src, off);
+    }
+
+    pub fn jeq(dst: Reg, src: anytype, off: i16) Insn {
+        return jmp(.jeq, dst, src, off);
+    }
 
-    // TODO: implement more factory functions for the other instructions
-    /// load immediate value into a register
-    pub fn load_imm(dst: Reg, imm: i32) Insn {
+    pub fn stx_mem(size: Size, dst: Reg, src: Reg, off: i16) Insn {
         return Insn{
-            .code = alu | mov | k,
+            .code = STX | @enumToInt(size) | MEM,
             .dst = @enumToInt(dst),
+            .src = @enumToInt(src),
+            .off = off,
+            .imm = 0,
+        };
+    }
+
+    pub fn xadd(dst: Reg, src: Reg) Insn {
+        return Insn{
+            .code = STX | XADD | DW,
+            .dst = @enumToInt(dst),
+            .src = @enumToInt(src),
+            .off = 0,
+            .imm = 0,
+        };
+    }
+
+    /// direct packet access, R0 = *(uint *)(skb->data + imm32)
+    pub fn ld_abs(size: Size, imm: i32) Insn {
+        return Insn{
+            .code = LD | @enumToInt(size) | ABS,
+            .dst = 0,
             .src = 0,
             .off = 0,
             .imm = imm,
         };
     }
 
+    fn ld_imm_impl(dst: Reg, src: Reg, imm: u64) [2]Insn {
+        return [2]Insn{
+            Insn{
+                .code = LD | DW | IMM,
+                .dst = @enumToInt(dst),
+                .src = @enumToInt(src),
+                .off = 0,
+                .imm = @intCast(i32, @truncate(u32, imm)),
+            },
+            Insn{
+                .code = 0,
+                .dst = 0,
+                .src = 0,
+                .off = 0,
+                .imm = @intCast(i32, @truncate(u32, imm >> 32)),
+            },
+        };
+    }
+
+    pub fn ld_map_fd(dst: Reg, map_fd: fd_t) [2]Insn {
+        return ld_imm_impl(dst, @intToEnum(Reg, PSEUDO_MAP_FD), @intCast(u64, map_fd));
+    }
+
+    pub fn call(helper: Helper) Insn {
+        return Insn{
+            .code = JMP | CALL,
+            .dst = 0,
+            .src = 0,
+            .off = 0,
+            .imm = @enumToInt(helper),
+        };
+    }
+
     /// exit BPF program
     pub fn exit() Insn {
         return Insn{
-            .code = jmp | exit_code,
+            .code = JMP | EXIT,
             .dst = 0,
             .src = 0,
             .off = 0,
@@ -197,6 +505,62 @@ pub const Insn = packed struct {
     }
 };
 
+fn expect_insn(insn: Insn, val: u64) void {
+    expectEqual(@bitCast(u64, insn), val);
+}
+
+test "insn bitsize" {
+    expectEqual(@bitSizeOf(Insn), 64);
+}
+
+// mov instructions
+test "mov imm" {
+    expect_insn(Insn.mov(.r1, 1), 0x00000001000001b7);
+}
+
+test "mov reg" {
+    expect_insn(Insn.mov(.r6, .r1), 0x00000000000016bf);
+}
+
+// alu instructions
+test "add imm" {
+    expect_insn(Insn.add(.r2, -4), 0xfffffffc00000207);
+}
+
+// ld instructions
+test "ld_abs" {
+    expect_insn(Insn.ld_abs(.byte, 42), 0x0000002a00000030);
+}
+
+test "ld_map_fd" {
+    const insns = Insn.ld_map_fd(.r1, 42);
+    expect_insn(insns[0], 0x0000002a00001118);
+    expect_insn(insns[1], 0x0000000000000000);
+}
+
+// st instructions
+test "stx_mem" {
+    expect_insn(Insn.stx_mem(.word, .r10, .r0, -4), 0x00000000fffc0a63);
+}
+
+test "xadd" {
+    expect_insn(Insn.xadd(.r0, .r1), 0x00000000000010db);
+}
+
+// jmp instructions
+test "jeq imm" {
+    expect_insn(Insn.jeq(.r0, 0, 2), 0x0000000000020015);
+}
+
+// other instructions
+test "call" {
+    expect_insn(Insn.call(.map_lookup_elem), 0x0000000100000085);
+}
+
+test "exit" {
+    expect_insn(Insn.exit(), 0x0000000000000095);
+}
+
 pub const Cmd = extern enum(usize) {
     map_create,
     map_lookup_elem,
@@ -605,7 +969,3 @@ pub const Attr = extern union {
     enable_stats: EnableStatsAttr,
     iter_create: IterCreateAttr,
 };
-
-pub fn bpf(cmd: Cmd, attr: *Attr, size: u32) usize {
-    return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size);
-}
lib/std/os/linux.zig
@@ -1221,6 +1221,10 @@ pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64,
     );
 }
 
+pub fn bpf_syscall(cmd: bpf.Cmd, attr: *bpf.Attr, size: u32) usize {
+    return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size);
+}
+
 test "" {
     if (builtin.os.tag == .linux) {
         _ = @import("linux/test.zig");