| ... | @@ -0,0 +1,1008 @@ |
| 1 | //! This package provides instrumentation for creating Berkeley Packet Filter[1] |
| 2 | //! (BPF) programs, along with a simulator for running them. |
| 3 | //! |
| 4 | //! BPF is a mechanism for cheap, in-kernel packet filtering. Programs are |
| 5 | //! attached to a network device and executed for every packet that flows |
| 6 | //! through it. The program must then return a verdict: the amount of packet |
| 7 | //! bytes that the kernel should copy into userspace. Execution speed is |
| 8 | //! achieved by having programs run in a limited virtual machine, which has the |
| 9 | //! added benefit of graceful failure in the face of buggy programs. |
| 10 | //! |
| 11 | //! The BPF virtual machine has a 32-bit word length and a small number of |
| 12 | //! word-sized registers: |
| 13 | //! |
| 14 | //! - The accumulator, `a`: The source/destination of arithmetic and logic |
| 15 | //! operations. |
| 16 | //! - The index register, `x`: Used as an offset for indirect memory access and |
| 17 | //! as a comparison value for conditional jumps. |
| 18 | //! - The scratch memory store, `M[0]..M[15]`: Used for saving the value of a/x |
| 19 | //! for later use. |
| 20 | //! |
| 21 | //! The packet being examined is an array of bytes, and is addressed using plain |
| 22 | //! array subscript notation, e.g. [10] for the byte at offset 10. An implicit |
| 23 | //! program counter, `pc`, is intialized to zero and incremented for each instruction. |
| 24 | //! |
| 25 | //! The machine has a fixed instruction set with the following form, where the |
| 26 | //! numbers represent bit length: |
| 27 | //! |
| 28 | //! ``` |
| 29 | //! ┌───────────┬──────┬──────┐ |
| 30 | //! │ opcode:16 │ jt:8 │ jt:8 │ |
| 31 | //! ├───────────┴──────┴──────┤ |
| 32 | //! │ k:32 │ |
| 33 | //! └─────────────────────────┘ |
| 34 | //! ``` |
| 35 | //! |
| 36 | //! The `opcode` indicates the instruction class and its addressing mode. |
| 37 | //! Opcodes are generated by performing binary addition on the 8-bit class and |
| 38 | //! mode constants. For example, the opcode for loading a byte from the packet |
| 39 | //! at X + 2, (`ldb [x + 2]`), is: |
| 40 | //! |
| 41 | //! ``` |
| 42 | //! LD | IND | B = 0x00 | 0x40 | 0x20 |
| 43 | //! = 0x60 |
| 44 | //! ``` |
| 45 | //! |
| 46 | //! `jt` is an offset used for conditional jumps, and increments the program |
| 47 | //! counter by its amount if the comparison was true. Conversely, `jf` |
| 48 | //! increments the counter if it was false. These fields are ignored in all |
| 49 | //! other cases. `k` is a generic variable used for various purposes, most |
| 50 | //! commonly as some sort of constant. |
| 51 | //! |
| 52 | //! This package contains opcode extensions used by different implementations, |
| 53 | //! where "extension" is anything outside of the original that was imported into |
| 54 | //! 4.4BSD[2]. These are marked with "EXTENSION", along with a list of |
| 55 | //! implementations that use them. |
| 56 | //! |
| 57 | //! Most of the doc-comments use the BPF assembly syntax as described in the |
| 58 | //! original paper[1]. For the sake of completeness, here is the complete |
| 59 | //! instruction set, along with the extensions: |
| 60 | //! |
| 61 | //!``` |
| 62 | //! opcode addressing modes |
| 63 | //! ld #k #len M[k] [k] [x + k] |
| 64 | //! ldh [k] [x + k] |
| 65 | //! ldb [k] [x + k] |
| 66 | //! ldx #k #len M[k] 4 * ([k] & 0xf) arc4random() |
| 67 | //! st M[k] |
| 68 | //! stx M[k] |
| 69 | //! jmp L |
| 70 | //! jeq #k, Lt, Lf |
| 71 | //! jgt #k, Lt, Lf |
| 72 | //! jge #k, Lt, Lf |
| 73 | //! jset #k, Lt, Lf |
| 74 | //! add #k x |
| 75 | //! sub #k x |
| 76 | //! mul #k x |
| 77 | //! div #k x |
| 78 | //! or #k x |
| 79 | //! and #k x |
| 80 | //! lsh #k x |
| 81 | //! rsh #k x |
| 82 | //! neg #k x |
| 83 | //! mod #k x |
| 84 | //! xor #k x |
| 85 | //! ret #k a |
| 86 | //! tax |
| 87 | //! txa |
| 88 | //! ``` |
| 89 | //! |
| 90 | //! Finally, a note on program design. The lack of backwards jumps leads to a |
| 91 | //! "return early, return often" control flow. Take for example the program |
| 92 | //! generated from the tcpdump filter `ip`: |
| 93 | //! |
| 94 | //! ``` |
| 95 | //! (000) ldh [12] ; Ethernet Packet Type |
| 96 | //! (001) jeq #0x86dd, 2, 7 ; ETHERTYPE_IPV6 |
| 97 | //! (002) ldb [20] ; IPv6 Next Header |
| 98 | //! (003) jeq #0x6, 10, 4 ; TCP |
| 99 | //! (004) jeq #0x2c, 5, 11 ; IPv6 Fragment Header |
| 100 | //! (005) ldb [54] ; TCP Source Port |
| 101 | //! (006) jeq #0x6, 10, 11 ; IPPROTO_TCP |
| 102 | //! (007) jeq #0x800, 8, 11 ; ETHERTYPE_IP |
| 103 | //! (008) ldb [23] ; IPv4 Protocol |
| 104 | //! (009) jeq #0x6, 10, 11 ; IPPROTO_TCP |
| 105 | //! (010) ret #262144 ; copy 0x40000 |
| 106 | //! (011) ret #0 ; skip packet |
| 107 | //! ``` |
| 108 | //! |
| 109 | //! Here we can make a few observations: |
| 110 | //! |
| 111 | //! - The problem "filter only tcp packets" has essentially been transformed |
| 112 | //! into a series of layer checks. |
| 113 | //! - There are two distinct branches in the code, one for validating IPv4 |
| 114 | //! headers and one for IPv6 headers. |
| 115 | //! - Most conditional jumps in these branches lead directly to the last two |
| 116 | //! instructions, a pass or fail. Thus the goal of a program is to find the |
| 117 | //! fastest route to a pass/fail comparison. |
| 118 | //! |
| 119 | //! [1]: S. McCanne and V. Jacobson, "The BSD Packet Filter: A New Architecture |
| 120 | //! for User-level Packet Capture", Proceedings of the 1993 Winter USENIX. |
| 121 | //! [2]: https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/src/sys/net/bpf.h |
| 122 | const std = @import("std"); |
| 123 | const builtin = @import("builtin"); |
| 124 | const native_endian = builtin.target.cpu.arch.endian(); |
| 125 | const mem = std.mem; |
| 126 | const math = std.math; |
| 127 | const random = std.crypto.random; |
| 128 | const assert = std.debug.assert; |
| 129 | const expectEqual = std.testing.expectEqual; |
| 130 | const expectError = std.testing.expectError; |
| 131 | const expect = std.testing.expect; |
| 132 | |
| 133 | // instruction classes |
| 134 | /// ld, ldh, ldb: Load data into a. |
| 135 | pub const LD = 0x00; |
| 136 | /// ldx: Load data into x. |
| 137 | pub const LDX = 0x01; |
| 138 | /// st: Store into scratch memory the value of a. |
| 139 | pub const ST = 0x02; |
| 140 | /// st: Store into scratch memory the value of x. |
| 141 | pub const STX = 0x03; |
| 142 | /// alu: Wrapping arithmetic/bitwise operations on a using the value of k/x. |
| 143 | pub const ALU = 0x04; |
| 144 | /// jmp, jeq, jgt, je, jset: Increment the program counter based on a comparison |
| 145 | /// between k/x and the accumulator. |
| 146 | pub const JMP = 0x05; |
| 147 | /// ret: Return a verdict using the value of k/the accumulator. |
| 148 | pub const RET = 0x06; |
| 149 | /// tax, txa: Register value copying between X and a. |
| 150 | pub const MISC = 0x07; |
| 151 | |
| 152 | // Size of data to be loaded from the packet. |
| 153 | /// ld: 32-bit full word. |
| 154 | pub const W = 0x00; |
| 155 | /// ldh: 16-bit half word. |
| 156 | pub const H = 0x08; |
| 157 | /// ldb: Single byte. |
| 158 | pub const B = 0x10; |
| 159 | |
| 160 | // Addressing modes used for loads to a/x. |
| 161 | /// #k: The immediate value stored in k. |
| 162 | pub const IMM = 0x00; |
| 163 | /// [k]: The value at offset k in the packet. |
| 164 | pub const ABS = 0x20; |
| 165 | /// [x + k]: The value at offset x + k in the packet. |
| 166 | pub const IND = 0x40; |
| 167 | /// M[k]: The value of the k'th scratch memory register. |
| 168 | pub const MEM = 0x60; |
| 169 | /// #len: The size of the packet. |
| 170 | pub const LEN = 0x80; |
| 171 | /// 4 * ([k] & 0xf): Four times the low four bits of the byte at offset k in the |
| 172 | /// packet. This is used for efficiently loading the header length of an IP |
| 173 | /// packet. |
| 174 | pub const MSH = 0xa0; |
| 175 | /// arc4random: 32-bit integer generated from a CPRNG (see arc4random(3)) loaded into a. |
| 176 | /// EXTENSION. Defined for: |
| 177 | /// - OpenBSD. |
| 178 | pub const RND = 0xc0; |
| 179 | |
| 180 | // Modifiers for different instruction classes. |
| 181 | /// Use the value of k for alu operations (add #k). |
| 182 | /// Compare against the value of k for jumps (jeq #k, Lt, Lf). |
| 183 | /// Return the value of k for returns (ret #k). |
| 184 | pub const K = 0x00; |
| 185 | /// Use the value of x for alu operations (add x). |
| 186 | /// Compare against the value of X for jumps (jeq x, Lt, Lf). |
| 187 | pub const X = 0x08; |
| 188 | /// Return the value of a for returns (ret a). |
| 189 | pub const A = 0x10; |
| 190 | |
| 191 | // ALU Operations on a using the value of k/x. |
| 192 | // All arithmetic operations are defined to overflow the value of a. |
| 193 | /// add: a = a + k |
| 194 | /// a = a + x. |
| 195 | pub const ADD = 0x00; |
| 196 | /// sub: a = a - k |
| 197 | /// a = a - x. |
| 198 | pub const SUB = 0x10; |
| 199 | /// mul: a = a * k |
| 200 | /// a = a * x. |
| 201 | pub const MUL = 0x20; |
| 202 | /// div: a = a / k |
| 203 | /// a = a / x. |
| 204 | /// Truncated division. |
| 205 | pub const DIV = 0x30; |
| 206 | /// or: a = a | k |
| 207 | /// a = a | x. |
| 208 | pub const OR = 0x40; |
| 209 | /// and: a = a & k |
| 210 | /// a = a & x. |
| 211 | pub const AND = 0x50; |
| 212 | /// lsh: a = a << k |
| 213 | /// a = a << x. |
| 214 | /// a = a << k, a = a << x. |
| 215 | pub const LSH = 0x60; |
| 216 | /// rsh: a = a >> k |
| 217 | /// a = a >> x. |
| 218 | pub const RSH = 0x70; |
| 219 | /// neg: a = -a. |
| 220 | /// Note that this isn't a binary negation, rather the value of `~a + 1`. |
| 221 | pub const NEG = 0x80; |
| 222 | /// mod: a = a % k |
| 223 | /// a = a % x. |
| 224 | /// EXTENSION. Defined for: |
| 225 | /// - Linux. |
| 226 | /// - NetBSD + Minix 3. |
| 227 | /// - FreeBSD and derivitives. |
| 228 | pub const MOD = 0x90; |
| 229 | /// xor: a = a ^ k |
| 230 | /// a = a ^ x. |
| 231 | /// EXTENSION. Defined for: |
| 232 | /// - Linux. |
| 233 | /// - NetBSD + Minix 3. |
| 234 | /// - FreeBSD and derivitives. |
| 235 | pub const XOR = 0xa0; |
| 236 | |
| 237 | // Jump operations using a comparison between a and x/k. |
| 238 | /// jmp L: pc += k. |
| 239 | /// No comparison done here. |
| 240 | pub const JA = 0x00; |
| 241 | /// jeq #k, Lt, Lf: pc += (a == k) ? jt : jf. |
| 242 | /// jeq x, Lt, Lf: pc += (a == x) ? jt : jf. |
| 243 | pub const JEQ = 0x10; |
| 244 | /// jgt #k, Lt, Lf: pc += (a > k) ? jt : jf. |
| 245 | /// jgt x, Lt, Lf: pc += (a > x) ? jt : jf. |
| 246 | pub const JGT = 0x20; |
| 247 | /// jge #k, Lt, Lf: pc += (a >= k) ? jt : jf. |
| 248 | /// jge x, Lt, Lf: pc += (a >= x) ? jt : jf. |
| 249 | pub const JGE = 0x30; |
| 250 | /// jset #k, Lt, Lf: pc += (a & k > 0) ? jt : jf. |
| 251 | /// jset x, Lt, Lf: pc += (a & x > 0) ? jt : jf. |
| 252 | pub const JSET = 0x40; |
| 253 | |
| 254 | // Miscellaneous operations/register copy. |
| 255 | /// tax: x = a. |
| 256 | pub const TAX = 0x00; |
| 257 | /// txa: a = x. |
| 258 | pub const TXA = 0x80; |
| 259 | |
| 260 | /// The 16 registers in the scratch memory store as named enums. |
| 261 | pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 }; |
| 262 | pub const MEMWORDS = 16; |
| 263 | pub const MAXINSNS = switch (builtin.os.tag) { |
| 264 | .linux => 4096, |
| 265 | else => 512, |
| 266 | }; |
| 267 | pub const MINBUFSIZE = 32; |
| 268 | pub const MAXBUFSIZE = 1 << 21; |
| 269 | |
| 270 | pub const Insn = extern struct { |
| 271 | opcode: u16, |
| 272 | jt: u8, |
| 273 | jf: u8, |
| 274 | k: u32, |
| 275 | |
| 276 | /// Implements the `std.fmt.format` API. |
| 277 | /// The formatting is similar to the output of tcpdump -dd. |
| 278 | pub fn format( |
| 279 | self: Insn, |
| 280 | comptime layout: []const u8, |
| 281 | opts: std.fmt.FormatOptions, |
| 282 | writer: anytype, |
| 283 | ) !void { |
| 284 | _ = opts; |
| 285 | if (comptime layout.len != 0 and layout[0] != 's') |
| 286 | @compileError("Unsupported format specifier for BPF Insn type '" ++ layout ++ "'."); |
| 287 | |
| 288 | try std.fmt.format( |
| 289 | writer, |
| 290 | "Insn{{ 0x{X:0<2}, {d}, {d}, 0x{X:0<8} }}", |
| 291 | .{ self.opcode, self.jt, self.jf, self.k }, |
| 292 | ); |
| 293 | } |
| 294 | |
| 295 | const Size = enum(u8) { |
| 296 | word = W, |
| 297 | half_word = H, |
| 298 | byte = B, |
| 299 | }; |
| 300 | |
| 301 | fn stmt(opcode: u16, k: u32) Insn { |
| 302 | return .{ |
| 303 | .opcode = opcode, |
| 304 | .jt = 0, |
| 305 | .jf = 0, |
| 306 | .k = k, |
| 307 | }; |
| 308 | } |
| 309 | |
| 310 | pub fn ld_imm(value: u32) Insn { |
| 311 | return stmt(LD | IMM, value); |
| 312 | } |
| 313 | |
| 314 | pub fn ld_abs(size: Size, offset: u32) Insn { |
| 315 | return stmt(LD | ABS | @enumToInt(size), offset); |
| 316 | } |
| 317 | |
| 318 | pub fn ld_ind(size: Size, offset: u32) Insn { |
| 319 | return stmt(LD | IND | @enumToInt(size), offset); |
| 320 | } |
| 321 | |
| 322 | pub fn ld_mem(reg: Scratch) Insn { |
| 323 | return stmt(LD | MEM, @enumToInt(reg)); |
| 324 | } |
| 325 | |
| 326 | pub fn ld_len() Insn { |
| 327 | return stmt(LD | LEN | W, 0); |
| 328 | } |
| 329 | |
| 330 | pub fn ld_rnd() Insn { |
| 331 | return stmt(LD | RND | W, 0); |
| 332 | } |
| 333 | |
| 334 | pub fn ldx_imm(value: u32) Insn { |
| 335 | return stmt(LDX | IMM, value); |
| 336 | } |
| 337 | |
| 338 | pub fn ldx_mem(reg: Scratch) Insn { |
| 339 | return stmt(LDX | MEM, @enumToInt(reg)); |
| 340 | } |
| 341 | |
| 342 | pub fn ldx_len() Insn { |
| 343 | return stmt(LDX | LEN | W, 0); |
| 344 | } |
| 345 | |
| 346 | pub fn ldx_msh(offset: u32) Insn { |
| 347 | return stmt(LDX | MSH | B, offset); |
| 348 | } |
| 349 | |
| 350 | pub fn st(reg: Scratch) Insn { |
| 351 | return stmt(ST, @enumToInt(reg)); |
| 352 | } |
| 353 | pub fn stx(reg: Scratch) Insn { |
| 354 | return stmt(STX, @enumToInt(reg)); |
| 355 | } |
| 356 | |
| 357 | const AluOp = enum(u16) { |
| 358 | add = ADD, |
| 359 | sub = SUB, |
| 360 | mul = MUL, |
| 361 | div = DIV, |
| 362 | @"or" = OR, |
| 363 | @"and" = AND, |
| 364 | lsh = LSH, |
| 365 | rsh = RSH, |
| 366 | mod = MOD, |
| 367 | xor = XOR, |
| 368 | }; |
| 369 | |
| 370 | const Source = enum(u16) { |
| 371 | k = K, |
| 372 | x = X, |
| 373 | }; |
| 374 | const KOrX = union(Source) { |
| 375 | k: u32, |
| 376 | x: void, |
| 377 | }; |
| 378 | |
| 379 | pub fn alu_neg() Insn { |
| 380 | return stmt(ALU | NEG, 0); |
| 381 | } |
| 382 | |
| 383 | pub fn alu(op: AluOp, source: KOrX) Insn { |
| 384 | return stmt( |
| 385 | ALU | @enumToInt(op) | @enumToInt(source), |
| 386 | if (source == .k) source.k else 0, |
| 387 | ); |
| 388 | } |
| 389 | |
| 390 | const JmpOp = enum(u16) { |
| 391 | jeq = JEQ, |
| 392 | jgt = JGT, |
| 393 | jge = JGE, |
| 394 | jset = JSET, |
| 395 | }; |
| 396 | |
| 397 | pub fn jmp_ja(location: u32) Insn { |
| 398 | return stmt(JMP | JA, location); |
| 399 | } |
| 400 | |
| 401 | pub fn jmp(op: JmpOp, source: KOrX, jt: u8, jf: u8) Insn { |
| 402 | return Insn{ |
| 403 | .opcode = JMP | @enumToInt(op) | @enumToInt(source), |
| 404 | .jt = jt, |
| 405 | .jf = jf, |
| 406 | .k = if (source == .k) source.k else 0, |
| 407 | }; |
| 408 | } |
| 409 | |
| 410 | const Verdict = enum(u16) { |
| 411 | k = K, |
| 412 | a = A, |
| 413 | }; |
| 414 | const KOrA = union(Verdict) { |
| 415 | k: u32, |
| 416 | a: void, |
| 417 | }; |
| 418 | |
| 419 | pub fn ret(verdict: KOrA) Insn { |
| 420 | return stmt( |
| 421 | RET | @enumToInt(verdict), |
| 422 | if (verdict == .k) verdict.k else 0, |
| 423 | ); |
| 424 | } |
| 425 | |
| 426 | pub fn tax() Insn { |
| 427 | return stmt(MISC | TAX, 0); |
| 428 | } |
| 429 | |
| 430 | pub fn txa() Insn { |
| 431 | return stmt(MISC | TXA, 0); |
| 432 | } |
| 433 | }; |
| 434 | |
| 435 | fn opcodeEqual(opcode: u16, insn: Insn) !void { |
| 436 | try expectEqual(opcode, insn.opcode); |
| 437 | } |
| 438 | |
| 439 | test "opcodes" { |
| 440 | try opcodeEqual(0x00, Insn.ld_imm(0)); |
| 441 | try opcodeEqual(0x20, Insn.ld_abs(.word, 0)); |
| 442 | try opcodeEqual(0x28, Insn.ld_abs(.half_word, 0)); |
| 443 | try opcodeEqual(0x30, Insn.ld_abs(.byte, 0)); |
| 444 | try opcodeEqual(0x40, Insn.ld_ind(.word, 0)); |
| 445 | try opcodeEqual(0x48, Insn.ld_ind(.half_word, 0)); |
| 446 | try opcodeEqual(0x50, Insn.ld_ind(.byte, 0)); |
| 447 | try opcodeEqual(0x60, Insn.ld_mem(.m0)); |
| 448 | try opcodeEqual(0x80, Insn.ld_len()); |
| 449 | try opcodeEqual(0xc0, Insn.ld_rnd()); |
| 450 | |
| 451 | try opcodeEqual(0x01, Insn.ldx_imm(0)); |
| 452 | try opcodeEqual(0x61, Insn.ldx_mem(.m0)); |
| 453 | try opcodeEqual(0x81, Insn.ldx_len()); |
| 454 | try opcodeEqual(0xb1, Insn.ldx_msh(0)); |
| 455 | |
| 456 | try opcodeEqual(0x02, Insn.st(.m0)); |
| 457 | try opcodeEqual(0x03, Insn.stx(.m0)); |
| 458 | |
| 459 | try opcodeEqual(0x04, Insn.alu(.add, .{ .k = 0 })); |
| 460 | try opcodeEqual(0x14, Insn.alu(.sub, .{ .k = 0 })); |
| 461 | try opcodeEqual(0x24, Insn.alu(.mul, .{ .k = 0 })); |
| 462 | try opcodeEqual(0x34, Insn.alu(.div, .{ .k = 0 })); |
| 463 | try opcodeEqual(0x44, Insn.alu(.@"or", .{ .k = 0 })); |
| 464 | try opcodeEqual(0x54, Insn.alu(.@"and", .{ .k = 0 })); |
| 465 | try opcodeEqual(0x64, Insn.alu(.lsh, .{ .k = 0 })); |
| 466 | try opcodeEqual(0x74, Insn.alu(.rsh, .{ .k = 0 })); |
| 467 | try opcodeEqual(0x94, Insn.alu(.mod, .{ .k = 0 })); |
| 468 | try opcodeEqual(0xa4, Insn.alu(.xor, .{ .k = 0 })); |
| 469 | try opcodeEqual(0x84, Insn.alu_neg()); |
| 470 | try opcodeEqual(0x0c, Insn.alu(.add, .x)); |
| 471 | try opcodeEqual(0x1c, Insn.alu(.sub, .x)); |
| 472 | try opcodeEqual(0x2c, Insn.alu(.mul, .x)); |
| 473 | try opcodeEqual(0x3c, Insn.alu(.div, .x)); |
| 474 | try opcodeEqual(0x4c, Insn.alu(.@"or", .x)); |
| 475 | try opcodeEqual(0x5c, Insn.alu(.@"and", .x)); |
| 476 | try opcodeEqual(0x6c, Insn.alu(.lsh, .x)); |
| 477 | try opcodeEqual(0x7c, Insn.alu(.rsh, .x)); |
| 478 | try opcodeEqual(0x9c, Insn.alu(.mod, .x)); |
| 479 | try opcodeEqual(0xac, Insn.alu(.xor, .x)); |
| 480 | |
| 481 | try opcodeEqual(0x05, Insn.jmp_ja(0)); |
| 482 | try opcodeEqual(0x15, Insn.jmp(.jeq, .{ .k = 0 }, 0, 0)); |
| 483 | try opcodeEqual(0x25, Insn.jmp(.jgt, .{ .k = 0 }, 0, 0)); |
| 484 | try opcodeEqual(0x35, Insn.jmp(.jge, .{ .k = 0 }, 0, 0)); |
| 485 | try opcodeEqual(0x45, Insn.jmp(.jset, .{ .k = 0 }, 0, 0)); |
| 486 | try opcodeEqual(0x1d, Insn.jmp(.jeq, .x, 0, 0)); |
| 487 | try opcodeEqual(0x2d, Insn.jmp(.jgt, .x, 0, 0)); |
| 488 | try opcodeEqual(0x3d, Insn.jmp(.jge, .x, 0, 0)); |
| 489 | try opcodeEqual(0x4d, Insn.jmp(.jset, .x, 0, 0)); |
| 490 | |
| 491 | try opcodeEqual(0x06, Insn.ret(.{ .k = 0 })); |
| 492 | try opcodeEqual(0x16, Insn.ret(.a)); |
| 493 | |
| 494 | try opcodeEqual(0x07, Insn.tax()); |
| 495 | try opcodeEqual(0x87, Insn.txa()); |
| 496 | } |
| 497 | |
| 498 | pub const Error = error{ |
| 499 | InvalidOpcode, |
| 500 | InvalidOffset, |
| 501 | InvalidLocation, |
| 502 | DivisionByZero, |
| 503 | NoReturn, |
| 504 | }; |
| 505 | |
| 506 | /// A simple implementation of the BPF virtual-machine. |
| 507 | /// Use this to run/debug programs. |
| 508 | pub fn simulate( |
| 509 | packet: []const u8, |
| 510 | filter: []const Insn, |
| 511 | byte_order: std.builtin.Endian, |
| 512 | ) Error!u32 { |
| 513 | assert(filter.len > 0 and filter.len < MAXINSNS); |
| 514 | assert(packet.len < MAXBUFSIZE); |
| 515 | const len = @intCast(u32, packet.len); |
| 516 | |
| 517 | var a: u32 = 0; |
| 518 | var x: u32 = 0; |
| 519 | var m = mem.zeroes([MEMWORDS]u32); |
| 520 | var pc: usize = 0; |
| 521 | |
| 522 | while (pc < filter.len) : (pc += 1) { |
| 523 | const i = filter[pc]; |
| 524 | // Cast to a wider type to protect against overflow. |
| 525 | const k = @as(u64, i.k); |
| 526 | const remaining = filter.len - (pc + 1); |
| 527 | |
| 528 | // Do validation/error checking here to compress the second switch. |
| 529 | switch (i.opcode) { |
| 530 | LD | ABS | W => if (k + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset, |
| 531 | LD | ABS | H => if (k + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset, |
| 532 | LD | ABS | B => if (k >= packet.len) return error.InvalidOffset, |
| 533 | LD | IND | W => if (k + x + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset, |
| 534 | LD | IND | H => if (k + x + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset, |
| 535 | LD | IND | B => if (k + x >= packet.len) return error.InvalidOffset, |
| 536 | |
| 537 | LDX | MSH | B => if (k >= packet.len) return error.InvalidOffset, |
| 538 | ST, STX, LD | MEM, LDX | MEM => if (i.k >= MEMWORDS) return error.InvalidOffset, |
| 539 | |
| 540 | JMP | JA => if (remaining <= i.k) return error.InvalidOffset, |
| 541 | JMP | JEQ | K, |
| 542 | JMP | JGT | K, |
| 543 | JMP | JGE | K, |
| 544 | JMP | JSET | K, |
| 545 | JMP | JEQ | X, |
| 546 | JMP | JGT | X, |
| 547 | JMP | JGE | X, |
| 548 | JMP | JSET | X, |
| 549 | => if (remaining <= i.jt or remaining <= i.jf) return error.InvalidLocation, |
| 550 | else => {}, |
| 551 | } |
| 552 | switch (i.opcode) { |
| 553 | LD | IMM => a = i.k, |
| 554 | LD | MEM => a = m[i.k], |
| 555 | LD | LEN | W => a = len, |
| 556 | LD | RND | W => a = random.int(u32), |
| 557 | LD | ABS | W => a = mem.readInt(u32, packet[i.k..][0..@sizeOf(u32)], byte_order), |
| 558 | LD | ABS | H => a = mem.readInt(u16, packet[i.k..][0..@sizeOf(u16)], byte_order), |
| 559 | LD | ABS | B => a = packet[i.k], |
| 560 | LD | IND | W => a = mem.readInt(u32, packet[i.k + x ..][0..@sizeOf(u32)], byte_order), |
| 561 | LD | IND | H => a = mem.readInt(u16, packet[i.k + x ..][0..@sizeOf(u16)], byte_order), |
| 562 | LD | IND | B => a = packet[i.k + x], |
| 563 | |
| 564 | LDX | IMM => x = i.k, |
| 565 | LDX | MEM => x = m[i.k], |
| 566 | LDX | LEN | W => x = len, |
| 567 | LDX | MSH | B => x = @as(u32, @truncate(u4, packet[i.k])) << 2, |
| 568 | |
| 569 | ST => m[i.k] = a, |
| 570 | STX => m[i.k] = x, |
| 571 | |
| 572 | ALU | ADD | K => a +%= i.k, |
| 573 | ALU | SUB | K => a -%= i.k, |
| 574 | ALU | MUL | K => a *%= i.k, |
| 575 | ALU | DIV | K => a = try math.divTrunc(u32, a, i.k), |
| 576 | ALU | OR | K => a |= i.k, |
| 577 | ALU | AND | K => a &= i.k, |
| 578 | ALU | LSH | K => a = math.shl(u32, a, i.k), |
| 579 | ALU | RSH | K => a = math.shr(u32, a, i.k), |
| 580 | ALU | MOD | K => a = try math.mod(u32, a, i.k), |
| 581 | ALU | XOR | K => a ^= i.k, |
| 582 | ALU | ADD | X => a +%= x, |
| 583 | ALU | SUB | X => a -%= x, |
| 584 | ALU | MUL | X => a *%= x, |
| 585 | ALU | DIV | X => a = try math.divTrunc(u32, a, x), |
| 586 | ALU | OR | X => a |= x, |
| 587 | ALU | AND | X => a &= x, |
| 588 | ALU | LSH | X => a = math.shl(u32, a, x), |
| 589 | ALU | RSH | X => a = math.shr(u32, a, x), |
| 590 | ALU | MOD | X => a = try math.mod(u32, a, x), |
| 591 | ALU | XOR | X => a ^= x, |
| 592 | ALU | NEG => a = @bitCast(u32, -%@bitCast(i32, a)), |
| 593 | |
| 594 | JMP | JA => pc += i.k, |
| 595 | JMP | JEQ | K => pc += if (a == i.k) i.jt else i.jf, |
| 596 | JMP | JGT | K => pc += if (a > i.k) i.jt else i.jf, |
| 597 | JMP | JGE | K => pc += if (a >= i.k) i.jt else i.jf, |
| 598 | JMP | JSET | K => pc += if (a & i.k > 0) i.jt else i.jf, |
| 599 | JMP | JEQ | X => pc += if (a == x) i.jt else i.jf, |
| 600 | JMP | JGT | X => pc += if (a > x) i.jt else i.jf, |
| 601 | JMP | JGE | X => pc += if (a >= x) i.jt else i.jf, |
| 602 | JMP | JSET | X => pc += if (a & x > 0) i.jt else i.jf, |
| 603 | |
| 604 | RET | K => return i.k, |
| 605 | RET | A => return a, |
| 606 | |
| 607 | MISC | TAX => x = a, |
| 608 | MISC | TXA => a = x, |
| 609 | else => return error.InvalidOpcode, |
| 610 | } |
| 611 | } |
| 612 | |
| 613 | return error.NoReturn; |
| 614 | } |
| 615 | |
| 616 | // This program is the BPF form of the tcpdump filter: |
| 617 | // |
| 618 | // tcpdump -dd 'ip host mirror.internode.on.net and tcp port ftp-data' |
| 619 | // |
| 620 | // As of January 2022, mirror.internode.on.net resolves to 150.101.135.3 |
| 621 | // |
| 622 | // For reference, here's what it looks like in BPF assembler. |
| 623 | // Note that the jumps are used for TCP/IP layer checks. |
| 624 | // |
| 625 | // ``` |
| 626 | // ldh [12] (#proto) |
| 627 | // jeq #0x0800 (ETHERTYPE_IP), L1, fail |
| 628 | // L1: ld [26] |
| 629 | // jeq #150.101.135.3, L2, dest |
| 630 | // dest: ld [30] |
| 631 | // jeq #150.101.135.3, L2, fail |
| 632 | // L2: ldb [23] |
| 633 | // jeq #0x6 (IPPROTO_TCP), L3, fail |
| 634 | // L3: ldh [20] |
| 635 | // jset #0x1fff, fail, plen |
| 636 | // plen: ldx 4 * ([14] & 0xf) |
| 637 | // ldh [x + 14] |
| 638 | // jeq #0x14 (FTP), pass, dstp |
| 639 | // dstp: ldh [x + 16] |
| 640 | // jeq #0x14 (FTP), pass, fail |
| 641 | // pass: ret #0x40000 |
| 642 | // fail: ret #0 |
| 643 | // ``` |
| 644 | const tcpdump_filter = [_]Insn{ |
| 645 | Insn.ld_abs(.half_word, 12), |
| 646 | Insn.jmp(.jeq, .{ .k = 0x800 }, 0, 14), |
| 647 | Insn.ld_abs(.word, 26), |
| 648 | Insn.jmp(.jeq, .{ .k = 0x96658703 }, 2, 0), |
| 649 | Insn.ld_abs(.word, 30), |
| 650 | Insn.jmp(.jeq, .{ .k = 0x96658703 }, 0, 10), |
| 651 | Insn.ld_abs(.byte, 23), |
| 652 | Insn.jmp(.jeq, .{ .k = 0x6 }, 0, 8), |
| 653 | Insn.ld_abs(.half_word, 20), |
| 654 | Insn.jmp(.jset, .{ .k = 0x1fff }, 6, 0), |
| 655 | Insn.ldx_msh(14), |
| 656 | Insn.ld_ind(.half_word, 14), |
| 657 | Insn.jmp(.jeq, .{ .k = 0x14 }, 2, 0), |
| 658 | Insn.ld_ind(.half_word, 16), |
| 659 | Insn.jmp(.jeq, .{ .k = 0x14 }, 0, 1), |
| 660 | Insn.ret(.{ .k = 0x40000 }), |
| 661 | Insn.ret(.{ .k = 0 }), |
| 662 | }; |
| 663 | |
| 664 | // This packet is the output of `ls` on mirror.internode.on.net:/, captured |
| 665 | // using the filter above. |
| 666 | // |
| 667 | // zig fmt: off |
| 668 | const ftp_data = [_]u8{ |
| 669 | // ethernet - 14 bytes: IPv4(0x0800) from a4:71:74:ad:4b:f0 -> de:ad:be:ef:f0:0f |
| 670 | 0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0f, 0xa4, 0x71, 0x74, 0xad, 0x4b, 0xf0, 0x08, 0x00, |
| 671 | // IPv4 - 20 bytes: TCP data from 150.101.135.3 -> 192.168.1.3 |
| 672 | 0x45, 0x00, 0x01, 0xf2, 0x70, 0x3b, 0x40, 0x00, 0x37, 0x06, 0xf2, 0xb6, |
| 673 | 0x96, 0x65, 0x87, 0x03, 0xc0, 0xa8, 0x01, 0x03, |
| 674 | // TCP - 32 bytes: Source port: 20 (FTP). Payload = 446 bytes |
| 675 | 0x00, 0x14, 0x80, 0x6d, 0x35, 0x81, 0x2d, 0x40, 0x4f, 0x8a, 0x29, 0x9e, 0x80, 0x18, 0x00, 0x2e, |
| 676 | 0x88, 0x8d, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x0b, 0x59, 0x5d, 0x09, 0x32, 0x8b, 0x51, 0xa0 |
| 677 | } ++ |
| 678 | // Raw line-based FTP data - 446 bytes |
| 679 | "lrwxrwxrwx 1 root root 12 Feb 14 2012 debian -> .pub2/debian\r\n" ++ |
| 680 | "lrwxrwxrwx 1 root root 15 Feb 14 2012 debian-cd -> .pub2/debian-cd\r\n" ++ |
| 681 | "lrwxrwxrwx 1 root root 9 Mar 9 2018 linux -> pub/linux\r\n" ++ |
| 682 | "drwxr-xr-X 3 mirror mirror 4096 Sep 20 08:10 pub\r\n" ++ |
| 683 | "lrwxrwxrwx 1 root root 12 Feb 14 2012 ubuntu -> .pub2/ubuntu\r\n" ++ |
| 684 | "-rw-r--r-- 1 root root 1044 Jan 20 2015 welcome.msg\r\n"; |
| 685 | // zig fmt: on |
| 686 | |
| 687 | test "tcpdump filter" { |
| 688 | try expectEqual( |
| 689 | @as(u32, 0x40000), |
| 690 | try simulate(ftp_data, &tcpdump_filter, .Big), |
| 691 | ); |
| 692 | } |
| 693 | |
| 694 | fn expectPass(data: anytype, filter: []Insn) !void { |
| 695 | try expectEqual( |
| 696 | @as(u32, 0), |
| 697 | try simulate(mem.asBytes(data), filter, .Big), |
| 698 | ); |
| 699 | } |
| 700 | |
| 701 | fn expectFail(expected_error: anyerror, data: anytype, filter: []Insn) !void { |
| 702 | try expectError( |
| 703 | expected_error, |
| 704 | simulate(mem.asBytes(data), filter, native_endian), |
| 705 | ); |
| 706 | } |
| 707 | |
| 708 | test "simulator coverage" { |
| 709 | const some_data: packed struct { |
| 710 | foo: u32, |
| 711 | bar: u8, |
| 712 | } = .{ |
| 713 | .foo = mem.nativeToBig(u32, 0xaabbccdd), |
| 714 | .bar = 0x7f, |
| 715 | }; |
| 716 | |
| 717 | try expectPass(&some_data, &.{ |
| 718 | // ld #10 |
| 719 | // ldx #1 |
| 720 | // st M[0] |
| 721 | // stx M[1] |
| 722 | // fail if A != 10 |
| 723 | Insn.ld_imm(10), |
| 724 | Insn.ldx_imm(1), |
| 725 | Insn.st(.m0), |
| 726 | Insn.stx(.m1), |
| 727 | Insn.jmp(.jeq, .{ .k = 10 }, 1, 0), |
| 728 | Insn.ret(.{ .k = 1 }), |
| 729 | // ld [0] |
| 730 | // fail if A != 0xaabbccdd |
| 731 | Insn.ld_abs(.word, 0), |
| 732 | Insn.jmp(.jeq, .{ .k = 0xaabbccdd }, 1, 0), |
| 733 | Insn.ret(.{ .k = 2 }), |
| 734 | // ldh [0] |
| 735 | // fail if A != 0xaabb |
| 736 | Insn.ld_abs(.half_word, 0), |
| 737 | Insn.jmp(.jeq, .{ .k = 0xaabb }, 1, 0), |
| 738 | Insn.ret(.{ .k = 3 }), |
| 739 | // ldb [0] |
| 740 | // fail if A != 0xaa |
| 741 | Insn.ld_abs(.byte, 0), |
| 742 | Insn.jmp(.jeq, .{ .k = 0xaa }, 1, 0), |
| 743 | Insn.ret(.{ .k = 4 }), |
| 744 | // ld [x + 0] |
| 745 | // fail if A != 0xbbccdd7f |
| 746 | Insn.ld_ind(.word, 0), |
| 747 | Insn.jmp(.jeq, .{ .k = 0xbbccdd7f }, 1, 0), |
| 748 | Insn.ret(.{ .k = 5 }), |
| 749 | // ldh [x + 0] |
| 750 | // fail if A != 0xbbcc |
| 751 | Insn.ld_ind(.half_word, 0), |
| 752 | Insn.jmp(.jeq, .{ .k = 0xbbcc }, 1, 0), |
| 753 | Insn.ret(.{ .k = 6 }), |
| 754 | // ldb [x + 0] |
| 755 | // fail if A != 0xbb |
| 756 | Insn.ld_ind(.byte, 0), |
| 757 | Insn.jmp(.jeq, .{ .k = 0xbb }, 1, 0), |
| 758 | Insn.ret(.{ .k = 7 }), |
| 759 | // ld M[0] |
| 760 | // fail if A != 10 |
| 761 | Insn.ld_mem(.m0), |
| 762 | Insn.jmp(.jeq, .{ .k = 10 }, 1, 0), |
| 763 | Insn.ret(.{ .k = 8 }), |
| 764 | // ld #len |
| 765 | // fail if A != 5 |
| 766 | Insn.ld_len(), |
| 767 | Insn.jmp(.jeq, .{ .k = @sizeOf(@TypeOf(some_data)) }, 1, 0), |
| 768 | Insn.ret(.{ .k = 9 }), |
| 769 | // ld #0 |
| 770 | // ld arc4random() |
| 771 | // fail if A == 0 |
| 772 | Insn.ld_imm(0), |
| 773 | Insn.ld_rnd(), |
| 774 | Insn.jmp(.jgt, .{ .k = 0 }, 1, 0), |
| 775 | Insn.ret(.{ .k = 10 }), |
| 776 | // ld #3 |
| 777 | // ldx #10 |
| 778 | // st M[2] |
| 779 | // txa |
| 780 | // fail if a != x |
| 781 | Insn.ld_imm(3), |
| 782 | Insn.ldx_imm(10), |
| 783 | Insn.st(.m2), |
| 784 | Insn.txa(), |
| 785 | Insn.jmp(.jeq, .x, 1, 0), |
| 786 | Insn.ret(.{ .k = 11 }), |
| 787 | // ldx M[2] |
| 788 | // fail if A <= X |
| 789 | Insn.ldx_mem(.m2), |
| 790 | Insn.jmp(.jgt, .x, 1, 0), |
| 791 | Insn.ret(.{ .k = 12 }), |
| 792 | // ldx #len |
| 793 | // fail if a <= x |
| 794 | Insn.ldx_len(), |
| 795 | Insn.jmp(.jgt, .x, 1, 0), |
| 796 | Insn.ret(.{ .k = 13 }), |
| 797 | // a = 4 * (0x7f & 0xf) |
| 798 | // x = 4 * ([4] & 0xf) |
| 799 | // fail if a != x |
| 800 | Insn.ld_imm(4 * (0x7f & 0xf)), |
| 801 | Insn.ldx_msh(4), |
| 802 | Insn.jmp(.jeq, .x, 1, 0), |
| 803 | Insn.ret(.{ .k = 14 }), |
| 804 | // ld #(u32)-1 |
| 805 | // ldx #2 |
| 806 | // add #1 |
| 807 | // fail if a != 0 |
| 808 | Insn.ld_imm(0xffffffff), |
| 809 | Insn.ldx_imm(2), |
| 810 | Insn.alu(.add, .{ .k = 1 }), |
| 811 | Insn.jmp(.jeq, .{ .k = 0 }, 1, 0), |
| 812 | Insn.ret(.{ .k = 15 }), |
| 813 | // sub #1 |
| 814 | // fail if a != (u32)-1 |
| 815 | Insn.alu(.sub, .{ .k = 1 }), |
| 816 | Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0), |
| 817 | Insn.ret(.{ .k = 16 }), |
| 818 | // add x |
| 819 | // fail if a != 1 |
| 820 | Insn.alu(.add, .x), |
| 821 | Insn.jmp(.jeq, .{ .k = 1 }, 1, 0), |
| 822 | Insn.ret(.{ .k = 17 }), |
| 823 | // sub x |
| 824 | // fail if a != (u32)-1 |
| 825 | Insn.alu(.sub, .x), |
| 826 | Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0), |
| 827 | Insn.ret(.{ .k = 18 }), |
| 828 | // ld #16 |
| 829 | // mul #2 |
| 830 | // fail if a != 32 |
| 831 | Insn.ld_imm(16), |
| 832 | Insn.alu(.mul, .{ .k = 2 }), |
| 833 | Insn.jmp(.jeq, .{ .k = 32 }, 1, 0), |
| 834 | Insn.ret(.{ .k = 19 }), |
| 835 | // mul x |
| 836 | // fail if a != 64 |
| 837 | Insn.alu(.mul, .x), |
| 838 | Insn.jmp(.jeq, .{ .k = 64 }, 1, 0), |
| 839 | Insn.ret(.{ .k = 20 }), |
| 840 | // div #2 |
| 841 | // fail if a != 32 |
| 842 | Insn.alu(.div, .{ .k = 2 }), |
| 843 | Insn.jmp(.jeq, .{ .k = 32 }, 1, 0), |
| 844 | Insn.ret(.{ .k = 21 }), |
| 845 | // div x |
| 846 | // fail if a != 16 |
| 847 | Insn.alu(.div, .x), |
| 848 | Insn.jmp(.jeq, .{ .k = 16 }, 1, 0), |
| 849 | Insn.ret(.{ .k = 22 }), |
| 850 | // or #4 |
| 851 | // fail if a != 20 |
| 852 | Insn.alu(.@"or", .{ .k = 4 }), |
| 853 | Insn.jmp(.jeq, .{ .k = 20 }, 1, 0), |
| 854 | Insn.ret(.{ .k = 23 }), |
| 855 | // or x |
| 856 | // fail if a != 22 |
| 857 | Insn.alu(.@"or", .x), |
| 858 | Insn.jmp(.jeq, .{ .k = 22 }, 1, 0), |
| 859 | Insn.ret(.{ .k = 24 }), |
| 860 | // and #6 |
| 861 | // fail if a != 6 |
| 862 | Insn.alu(.@"and", .{ .k = 0b110 }), |
| 863 | Insn.jmp(.jeq, .{ .k = 6 }, 1, 0), |
| 864 | Insn.ret(.{ .k = 25 }), |
| 865 | // and x |
| 866 | // fail if a != 2 |
| 867 | Insn.alu(.@"and", .x), |
| 868 | Insn.jmp(.jeq, .x, 1, 0), |
| 869 | Insn.ret(.{ .k = 26 }), |
| 870 | // xor #15 |
| 871 | // fail if a != 13 |
| 872 | Insn.alu(.xor, .{ .k = 0b1111 }), |
| 873 | Insn.jmp(.jeq, .{ .k = 0b1101 }, 1, 0), |
| 874 | Insn.ret(.{ .k = 27 }), |
| 875 | // xor x |
| 876 | // fail if a != 15 |
| 877 | Insn.alu(.xor, .x), |
| 878 | Insn.jmp(.jeq, .{ .k = 0b1111 }, 1, 0), |
| 879 | Insn.ret(.{ .k = 28 }), |
| 880 | // rsh #1 |
| 881 | // fail if a != 7 |
| 882 | Insn.alu(.rsh, .{ .k = 1 }), |
| 883 | Insn.jmp(.jeq, .{ .k = 0b0111 }, 1, 0), |
| 884 | Insn.ret(.{ .k = 29 }), |
| 885 | // rsh x |
| 886 | // fail if a != 1 |
| 887 | Insn.alu(.rsh, .x), |
| 888 | Insn.jmp(.jeq, .{ .k = 0b0001 }, 1, 0), |
| 889 | Insn.ret(.{ .k = 30 }), |
| 890 | // lsh #1 |
| 891 | // fail if a != 2 |
| 892 | Insn.alu(.lsh, .{ .k = 1 }), |
| 893 | Insn.jmp(.jeq, .{ .k = 0b0010 }, 1, 0), |
| 894 | Insn.ret(.{ .k = 31 }), |
| 895 | // lsh x |
| 896 | // fail if a != 8 |
| 897 | Insn.alu(.lsh, .x), |
| 898 | Insn.jmp(.jeq, .{ .k = 0b1000 }, 1, 0), |
| 899 | Insn.ret(.{ .k = 32 }), |
| 900 | // mod 6 |
| 901 | // fail if a != 2 |
| 902 | Insn.alu(.mod, .{ .k = 6 }), |
| 903 | Insn.jmp(.jeq, .{ .k = 2 }, 1, 0), |
| 904 | Insn.ret(.{ .k = 33 }), |
| 905 | // mod x |
| 906 | // fail if a != 0 |
| 907 | Insn.alu(.mod, .x), |
| 908 | Insn.jmp(.jeq, .{ .k = 0 }, 1, 0), |
| 909 | Insn.ret(.{ .k = 34 }), |
| 910 | // tax |
| 911 | // neg |
| 912 | // fail if a != (u32)-2 |
| 913 | Insn.txa(), |
| 914 | Insn.alu_neg(), |
| 915 | Insn.jmp(.jeq, .{ .k = ~@as(u32, 2) + 1 }, 1, 0), |
| 916 | Insn.ret(.{ .k = 35 }), |
| 917 | // ja #1 (skip the next instruction) |
| 918 | Insn.jmp_ja(1), |
| 919 | Insn.ret(.{ .k = 36 }), |
| 920 | // ld #20 |
| 921 | // tax |
| 922 | // fail if a != 20 |
| 923 | // fail if a != x |
| 924 | Insn.ld_imm(20), |
| 925 | Insn.tax(), |
| 926 | Insn.jmp(.jeq, .{ .k = 20 }, 1, 0), |
| 927 | Insn.ret(.{ .k = 37 }), |
| 928 | Insn.jmp(.jeq, .x, 1, 0), |
| 929 | Insn.ret(.{ .k = 38 }), |
| 930 | // ld #19 |
| 931 | // fail if a == 20 |
| 932 | // fail if a == x |
| 933 | // fail if a >= 20 |
| 934 | // fail if a >= X |
| 935 | Insn.ld_imm(19), |
| 936 | Insn.jmp(.jeq, .{ .k = 20 }, 0, 1), |
| 937 | Insn.ret(.{ .k = 39 }), |
| 938 | Insn.jmp(.jeq, .x, 0, 1), |
| 939 | Insn.ret(.{ .k = 40 }), |
| 940 | Insn.jmp(.jgt, .{ .k = 20 }, 0, 1), |
| 941 | Insn.ret(.{ .k = 41 }), |
| 942 | Insn.jmp(.jgt, .x, 0, 1), |
| 943 | Insn.ret(.{ .k = 42 }), |
| 944 | // ld #21 |
| 945 | // fail if a < 20 |
| 946 | // fail if a < x |
| 947 | Insn.ld_imm(21), |
| 948 | Insn.jmp(.jgt, .{ .k = 20 }, 1, 0), |
| 949 | Insn.ret(.{ .k = 43 }), |
| 950 | Insn.jmp(.jgt, .x, 1, 0), |
| 951 | Insn.ret(.{ .k = 44 }), |
| 952 | // ldx #22 |
| 953 | // fail if a < 22 |
| 954 | // fail if a < x |
| 955 | Insn.ldx_imm(22), |
| 956 | Insn.jmp(.jge, .{ .k = 22 }, 0, 1), |
| 957 | Insn.ret(.{ .k = 45 }), |
| 958 | Insn.jmp(.jge, .x, 0, 1), |
| 959 | Insn.ret(.{ .k = 46 }), |
| 960 | // ld #23 |
| 961 | // fail if a >= 22 |
| 962 | // fail if a >= x |
| 963 | Insn.ld_imm(23), |
| 964 | Insn.jmp(.jge, .{ .k = 22 }, 1, 0), |
| 965 | Insn.ret(.{ .k = 47 }), |
| 966 | Insn.jmp(.jge, .x, 1, 0), |
| 967 | Insn.ret(.{ .k = 48 }), |
| 968 | // ldx #0b10100 |
| 969 | // fail if a & 0b10100 == 0 |
| 970 | // fail if a & x == 0 |
| 971 | Insn.ldx_imm(0b10100), |
| 972 | Insn.jmp(.jset, .{ .k = 0b10100 }, 1, 0), |
| 973 | Insn.ret(.{ .k = 47 }), |
| 974 | Insn.jmp(.jset, .x, 1, 0), |
| 975 | Insn.ret(.{ .k = 48 }), |
| 976 | // ldx #0 |
| 977 | // fail if a & 0 > 0 |
| 978 | // fail if a & x > 0 |
| 979 | Insn.ldx_imm(0), |
| 980 | Insn.jmp(.jset, .{ .k = 0 }, 0, 1), |
| 981 | Insn.ret(.{ .k = 49 }), |
| 982 | Insn.jmp(.jset, .x, 0, 1), |
| 983 | Insn.ret(.{ .k = 50 }), |
| 984 | Insn.ret(.{ .k = 0 }), |
| 985 | }); |
| 986 | try expectPass(&some_data, &.{ |
| 987 | Insn.ld_imm(35), |
| 988 | Insn.ld_imm(0), |
| 989 | Insn.ret(.a), |
| 990 | }); |
| 991 | |
| 992 | // Errors |
| 993 | try expectFail(error.NoReturn, &some_data, &.{ |
| 994 | Insn.ld_imm(10), |
| 995 | }); |
| 996 | try expectFail(error.InvalidOpcode, &some_data, &.{ |
| 997 | Insn.stmt(0x7f, 0xdeadbeef), |
| 998 | }); |
| 999 | try expectFail(error.InvalidOffset, &some_data, &.{ |
| 1000 | Insn.stmt(LD | ABS | W, 10), |
| 1001 | }); |
| 1002 | try expectFail(error.InvalidLocation, &some_data, &.{ |
| 1003 | Insn.jmp(.jeq, .{ .k = 0 }, 10, 0), |
| 1004 | }); |
| 1005 | try expectFail(error.InvalidLocation, &some_data, &.{ |
| 1006 | Insn.jmp(.jeq, .{ .k = 0 }, 0, 10), |
| 1007 | }); |
| 1008 | } |