├── .gitignore ├── src ├── main.zig ├── root.zig ├── helpers.zig ├── syscalls.zig ├── asm_parser.zig ├── assembler.zig ├── interpreter.zig ├── ebpf.zig └── disassembler.zig ├── LICENSE-MIT ├── README.md ├── tests ├── syscalls.zig ├── alu.zig ├── assembler.zig └── disassembler.zig └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out/ 2 | .zig-cache/ 3 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ebpf = @import("ebpf.zig"); 3 | const interpreter = @import("interpreter.zig"); 4 | const expect = std.testing.expect; 5 | const syscalls = @import("syscalls.zig"); 6 | const assembler = @import("assembler.zig"); 7 | pub fn main() !void {} 8 | -------------------------------------------------------------------------------- /src/root.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const ebpf = @import("ebpf.zig"); 3 | pub const interpreter = @import("interpreter.zig"); 4 | pub const syscalls = @import("syscalls.zig"); 5 | pub const disassembler = @import("disassembler.zig"); 6 | pub const assembler = @import("assembler.zig"); 7 | const testing = std.testing; 8 | -------------------------------------------------------------------------------- /src/helpers.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ebpf = @import("ebpf.zig"); 3 | 4 | pub fn debug_print_ix(ix: *const ebpf.Instruction) void { 5 | std.log.warn("Instruction: op = {x}, dst = {d}, src = {d}, offset = {d}, imm = {d}\n", .{ ix.op, ix.dst, ix.src, ix.offset, ix.imm }); 6 | } 7 | 8 | pub fn debug_print_vm_state(reg: []const u64, pc: usize, src: u8, dst: u8) void { 9 | std.log.warn("VM State: pc = {}\n", .{pc}); 10 | std.log.warn("Registers:\n", .{}); 11 | for (reg, 0..) |value, index| { 12 | std.log.warn(" reg[{}] = {d}\n", .{ index, value }); 13 | } 14 | std.log.warn("src = {d}, dst = {d}\n", .{ src, dst }); 15 | } 16 | -------------------------------------------------------------------------------- /src/syscalls.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const BPF_KTIME_GETNS_IDX: u32 = 5; 4 | pub const BPF_TRACE_PRINTK_IDX: u32 = 6; 5 | 6 | pub fn bpf_ktime_get_ns(_u1: u64, _u2: u64, _u3: u64, _u4: u64, _u5: u64) u64 { 7 | _ = _u5; 8 | _ = _u4; 9 | _ = _u3; 10 | _ = _u2; 11 | _ = _u1; 12 | 13 | return @as(u64, @intCast(std.time.nanoTimestamp())); 14 | } 15 | pub fn bpf_trace_printk(_u1: u64, _u2: u64, a3: u64, a4: u64, a5: u64) u64 { 16 | _ = _u2; 17 | _ = _u1; 18 | std.log.info("bpf_trace_printk: 0x{x}, 0x{x}, 0x{x}", .{ a3, a4, a5 }); 19 | return "bpf_trace_printk: 0x, 0x, 0x\n".len + size_arg(a3) + size_arg(a4) + size_arg(a5); 20 | } 21 | 22 | fn size_arg(x: u64) u64 { 23 | if (x == 0) { 24 | return 1; 25 | } else { 26 | return @as(u64, @intFromFloat(@floor(std.math.log(f64, 16.0, @as(f64, @floatFromInt(x)))))) + 1; 27 | } 28 | } 29 | 30 | pub fn gather_bytes(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) u64 { 31 | return (arg1 << 32) | 32 | (arg2 << 24) | 33 | (arg3 << 16) | 34 | (arg4 << 8) | 35 | arg5; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Anoushk Kharangate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eBPF in Zig ⚡️ 2 | 3 | This is a wip implementation of eBPF in native Zig inspired by Quentin Monnet's [rbpf](https://github.com/qmonnet/rbpf/). This is different from existing zig eBPF libraries as it implements the ISA natively in zig without depending on libbpf or any C modules. 4 | 5 | ## What works 6 | - [x] 64-bit ALU operations 7 | - [x] Memory operations 8 | - [x] Byteswap operations 9 | - [x] Branch instructions (needs more testing) 10 | - [x] Syscalls 11 | - [ ] JIT Compiler 12 | - [x] Assembler (needs more testing) 13 | - [x] Disassembler 14 | - [ ] Unit Tests & Fuzzing 15 | 16 | ## Why 17 | Short answer: I was bored 18 | 19 | Long answer: I wanted to work on something low level and complex, and also I really like Zig and wanted an excuse to write a large-ish project in it. I was inspired by Quentin Monnet and Solana Labs's work in rbpf and thought there should be a native Zig eBPF implementation. So I wanted to learn, experiment and have some fun in open source. 20 | 21 | ## Contribution and Feedback 22 | The author of this repo is new to Zig so if you feel there can be some improvements in making the code more idiomatic then PRs are welcome! 23 | 24 | Following in the footsteps of rbpf, this project expects new commits to be coveryed by the [Developer's Ceritificate of Origin](https://wiki.linuxfoundation.org/dco). 25 | 26 | ## License 27 | zig-ebpf is distributed under both MIT License and Apache License(Version 2.0). 28 | 29 | -------------------------------------------------------------------------------- /tests/syscalls.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const interpreter = @import("zig-ebpf").interpreter; 3 | const expect = std.testing.expect; 4 | const syscalls = @import("zig-ebpf").syscalls; 5 | const ebpf = @import("zig-ebpf").ebpf; 6 | 7 | test "simple_syscall_time" { 8 | var buffer: [512]u8 = undefined; 9 | var fba = std.heap.FixedBufferAllocator.init(&buffer); 10 | const allocator = fba.allocator(); 11 | 12 | var syscalls_map = std.AutoHashMap(usize, ebpf.Syscall).init(std.testing.allocator); 13 | defer syscalls_map.deinit(); 14 | 15 | var prog = [_]u8{ 16 | 0xb7, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // mov r1, 1 17 | 0xb7, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov r2, 2 18 | 0xb7, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // mov r3, 3 19 | 0xb7, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // mov r4, 4 20 | 0xb7, 0x05, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // mov r5, 5 21 | 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // call 0 22 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit 23 | }; 24 | 25 | const mem = [_]u8{ 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd }; 26 | const mbuff = [_]u8{0} ** 32; 27 | 28 | try syscalls_map.put(0, &syscalls.gather_bytes); 29 | 30 | const result = try interpreter.execute_program(allocator, &prog, &mem, &mbuff, &syscalls_map); 31 | 32 | // std.log.warn("simple_syscall_time: {d}", .{result}); 33 | try expect(result == 0x0102030405); 34 | } 35 | -------------------------------------------------------------------------------- /tests/alu.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const interpreter = @import("zig-ebpf").interpreter; 4 | const expect = std.testing.expect; 5 | const ebpf = @import("zig-ebpf").ebpf; 6 | 7 | test "simple_alu64_add" { 8 | var buffer: [512]u8 = undefined; 9 | var fba = std.heap.FixedBufferAllocator.init(&buffer); 10 | const allocator = fba.allocator(); 11 | 12 | var syscalls_map = std.AutoHashMap(usize, ebpf.Syscall).init(std.testing.allocator); 13 | defer syscalls_map.deinit(); 14 | var prog = [_]u8{ 183, 0, 0, 0, 0, 0, 0, 0, 183, 1, 0, 0, 1, 0, 0, 0, 183, 2, 0, 0, 2, 0, 0, 0, 183, 3, 0, 0, 3, 0, 0, 0, 183, 4, 0, 0, 4, 0, 0, 0, 183, 5, 0, 0, 5, 0, 0, 0, 183, 6, 0, 0, 6, 0, 0, 0, 183, 7, 0, 0, 7, 0, 0, 0, 183, 8, 0, 0, 8, 0, 0, 0, 183, 9, 0, 0, 9, 0, 0, 0, 7, 0, 0, 0, 23, 0, 0, 0, 15, 112, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 13, 0, 0, 0, 31, 16, 0, 0, 0, 0, 0, 0, 39, 0, 0, 0, 7, 0, 0, 0, 47, 48, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 2, 0, 0, 0, 63, 64, 0, 0, 0, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0 }; 15 | const mem = [_]u8{ 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd }; 16 | const mbuff = [_]u8{0} ** 32; 17 | 18 | const result = try interpreter.execute_program(allocator, &prog, &mem, &mbuff, &syscalls_map); 19 | 20 | // std.log.warn("aluadd: {d}", .{result}); 21 | try expect(result == 0x2a); 22 | } 23 | 24 | test "simple_jsle" { 25 | var buffer: [512]u8 = undefined; 26 | var fba = std.heap.FixedBufferAllocator.init(&buffer); 27 | const allocator = fba.allocator(); 28 | 29 | var syscalls_map = std.AutoHashMap(usize, ebpf.Syscall).init(std.testing.allocator); 30 | defer syscalls_map.deinit(); 31 | var prog = [_]u8{ 183, 0, 0, 0, 0, 0, 0, 0, 183, 1, 0, 0, 254, 255, 255, 255, 101, 1, 4, 0, 255, 255, 255, 255, 183, 0, 0, 0, 1, 0, 0, 0, 183, 1, 0, 0, 0, 0, 0, 0, 101, 1, 1, 0, 255, 255, 255, 255, 183, 0, 0, 0, 2, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0 }; 32 | 33 | const mem = [_]u8{ 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd }; 34 | const mbuff = [_]u8{0} ** 32; 35 | 36 | const result = try interpreter.execute_program(allocator, &prog, &mem, &mbuff, &syscalls_map); 37 | 38 | // std.log.warn("jsgt_imm: {d}", .{result}); 39 | try expect(result == 0x1); 40 | } 41 | 42 | test "simple_jeq64_reg" { 43 | var buffer: [512]u8 = undefined; 44 | var fba = std.heap.FixedBufferAllocator.init(&buffer); 45 | const allocator = fba.allocator(); 46 | 47 | var syscalls_map = std.AutoHashMap(usize, ebpf.Syscall).init(std.testing.allocator); 48 | defer syscalls_map.deinit(); 49 | 50 | var prog = [_]u8{ 183, 9, 0, 0, 1, 0, 0, 0, 103, 9, 0, 0, 32, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 183, 1, 0, 0, 10, 0, 0, 0, 183, 2, 0, 0, 11, 0, 0, 0, 29, 33, 5, 0, 0, 0, 0, 0, 183, 0, 0, 0, 1, 0, 0, 0, 183, 1, 0, 0, 11, 0, 0, 0, 79, 145, 0, 0, 0, 0, 0, 0, 29, 33, 1, 0, 0, 0, 0, 0, 183, 0, 0, 0, 2, 0, 0, 0, 149, 0, 0, 0, 0, 0, 0, 0 }; 51 | const mem = [_]u8{ 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd }; 52 | const mbuff = [_]u8{0} ** 32; 53 | 54 | const result = try interpreter.execute_program(allocator, prog[0..], &mem, &mbuff, &syscalls_map); 55 | 56 | std.log.warn("jeq_reg: {d}", .{result}); 57 | try expect(result == 0x2); 58 | } 59 | 60 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/assembler.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const assembler = @import("zig-ebpf").assembler; 4 | 5 | /// Helper function to compare byte slices and provide detailed error messages 6 | fn expectEqualBytes(expected: []const u8, actual: []const u8) !void { 7 | try testing.expectEqual(expected.len, actual.len); 8 | for (expected, 0..) |exp_byte, i| { 9 | if (exp_byte != actual[i]) { 10 | std.debug.print("Mismatch at index {}: expected 0x{X:0>2}, found 0x{X:0>2}\n", .{ i, exp_byte, actual[i] }); 11 | return error.TestExpectedEqual; 12 | } 13 | } 14 | } 15 | 16 | test "assembler - add64" { 17 | const src = "add64 r1, 0x5"; 18 | const expected = &[_]u8{ 0x07, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00 }; 19 | const result = try assembler.assemble(src); 20 | defer std.heap.page_allocator.free(result); 21 | try expectEqualBytes(expected, result); 22 | } 23 | 24 | test "assembler - mov64" { 25 | const src = "mov64 r2, 0x32"; 26 | const expected = &[_]u8{ 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00 }; 27 | const result = try assembler.assemble(src); 28 | defer std.heap.page_allocator.free(result); 29 | try expectEqualBytes(expected, result); 30 | } 31 | 32 | test "assembler - mov64 reg" { 33 | const src = "mov64 r1, r0"; 34 | const expected = &[_]u8{ 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 35 | const result = try assembler.assemble(src); 36 | defer std.heap.page_allocator.free(result); 37 | try expectEqualBytes(expected, result); 38 | } 39 | 40 | test "assembler - be16" { 41 | const src = "be16 r0"; 42 | const expected = &[_]u8{ 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00 }; 43 | const result = try assembler.assemble(src); 44 | defer std.heap.page_allocator.free(result); 45 | try expectEqualBytes(expected, result); 46 | } 47 | 48 | test "assembler - neg64" { 49 | const src = "neg64 r2"; 50 | const expected = &[_]u8{ 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 51 | const result = try assembler.assemble(src); 52 | defer std.heap.page_allocator.free(result); 53 | try expectEqualBytes(expected, result); 54 | } 55 | 56 | test "assembler - exit" { 57 | const src = "exit"; 58 | const expected = &[_]u8{ 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 59 | const result = try assembler.assemble(src); 60 | defer std.heap.page_allocator.free(result); 61 | try expectEqualBytes(expected, result); 62 | } 63 | 64 | test "assembler - full program" { 65 | const src = 66 | \\add64 r1, 0x5 67 | \\mov64 r2, 0x32 68 | \\mov64 r1, r0 69 | \\be16 r0 70 | \\neg64 r2 71 | \\exit 72 | ; 73 | 74 | const expected = &[_]u8{ 75 | 0x07, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 76 | 0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 77 | 0xbf, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0xdc, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 79 | 0x87, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | }; 82 | const result = try assembler.assemble(src); 83 | defer std.heap.page_allocator.free(result); 84 | try expectEqualBytes(expected, result); 85 | } 86 | 87 | test "assembler - invalid instruction" { 88 | const src = "invalid r1, 0x5"; 89 | const result = assembler.assemble(src); 90 | try testing.expectError(assembler.AssemblerError.InvalidInstruction, result); 91 | } 92 | 93 | test "assembler - ja (jump always)" { 94 | const src = "ja +5"; 95 | const expected = &[_]u8{ 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; 96 | const result = try assembler.assemble(src); 97 | defer std.heap.page_allocator.free(result); 98 | try expectEqualBytes(expected, result); 99 | } 100 | 101 | test "assembler - jeq (jump if equal)" { 102 | const src = "jeq r1, 0x10, +3"; 103 | const expected = &[_]u8{ 0x15, 0x01, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00 }; 104 | const result = try assembler.assemble(src); 105 | defer std.heap.page_allocator.free(result); 106 | try expectEqualBytes(expected, result); 107 | } 108 | 109 | test "assembler - call" { 110 | const src = "call 5"; 111 | const expected = &[_]u8{ 0x85, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00 }; 112 | const result = try assembler.assemble(src); 113 | defer std.heap.page_allocator.free(result); 114 | try expectEqualBytes(expected, result); 115 | } 116 | 117 | test "assembler - ldxw (load word)" { 118 | const src = "ldxw r0, [r1+0x10]"; 119 | const expected = &[_]u8{ 0x61, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }; 120 | const result = try assembler.assemble(src); 121 | defer std.heap.page_allocator.free(result); 122 | try expectEqualBytes(expected, result); 123 | } 124 | 125 | test "assembler - stxb (store byte)" { 126 | const src = "stxb [r2+0x5], r1"; 127 | const expected = &[_]u8{ 0x73, 0x12, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; 128 | const result = try assembler.assemble(src); 129 | defer std.heap.page_allocator.free(result); 130 | try expectEqualBytes(expected, result); 131 | } 132 | 133 | test "assembler - invalid register" { 134 | const src = "mov64 r11, 0x5"; 135 | const result = assembler.assemble(src); 136 | try testing.expectError(assembler.AssemblerError.InvalidRegister, result); 137 | } 138 | 139 | test "assembler - mismatched operands" { 140 | const src = "add64 r1"; 141 | const result = assembler.assemble(src); 142 | try testing.expectError(assembler.AssemblerError.InvalidOperands, result); 143 | } 144 | 145 | test "assembler - complex program" { 146 | const src = 147 | \\mov64 r6, r1 148 | \\ldxw r1, [r6+0] 149 | \\ldxw r2, [r6+4] 150 | \\ldxw r3, [r6+8] 151 | \\ldxw r4, [r6+12] 152 | \\add64 r3, r1 153 | \\add64 r3, r2 154 | \\add64 r3, r4 155 | \\stxw [r6+8], r3 156 | \\mov64 r0, 0 157 | \\exit 158 | ; 159 | 160 | const expected = &[_]u8{ 161 | 0xbf, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 162 | 0x61, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 163 | 0x61, 0x62, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 164 | 0x61, 0x63, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 165 | 0x61, 0x64, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 166 | 0x0f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 167 | 0x0f, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 168 | 0x0f, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 169 | 0x63, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 170 | 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | }; 173 | const result = try assembler.assemble(src); 174 | defer std.heap.page_allocator.free(result); 175 | try expectEqualBytes(expected, result); 176 | } 177 | 178 | test "assembler - sub64" { 179 | const src = "sub64 r1, 0x5"; 180 | const expected = &[_]u8{ 0x17, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00 }; 181 | const result = try assembler.assemble(src); 182 | defer std.heap.page_allocator.free(result); 183 | try expectEqualBytes(expected, result); 184 | } 185 | 186 | test "assembler - mul64" { 187 | const src = "mul64 r2, r3"; 188 | const expected = &[_]u8{ 0x2f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 189 | const result = try assembler.assemble(src); 190 | defer std.heap.page_allocator.free(result); 191 | try expectEqualBytes(expected, result); 192 | } 193 | 194 | test "assembler - div64" { 195 | const src = "div64 r4, 0x10"; 196 | const expected = &[_]u8{ 0x37, 0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00 }; 197 | const result = try assembler.assemble(src); 198 | defer std.heap.page_allocator.free(result); 199 | try expectEqualBytes(expected, result); 200 | } 201 | 202 | test "assembler - or64" { 203 | const src = "or64 r5, r6"; 204 | const expected = &[_]u8{ 0x4f, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 205 | const result = try assembler.assemble(src); 206 | defer std.heap.page_allocator.free(result); 207 | try expectEqualBytes(expected, result); 208 | } 209 | 210 | test "assembler - and64" { 211 | const src = "and64 r7, 0xff"; 212 | const expected = &[_]u8{ 0x57, 0x07, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00 }; 213 | const result = try assembler.assemble(src); 214 | defer std.heap.page_allocator.free(result); 215 | try expectEqualBytes(expected, result); 216 | } 217 | 218 | test "assembler - lsh64" { 219 | const src = "lsh64 r8, 4"; 220 | const expected = &[_]u8{ 0x67, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 }; 221 | const result = try assembler.assemble(src); 222 | defer std.heap.page_allocator.free(result); 223 | try expectEqualBytes(expected, result); 224 | } 225 | 226 | test "assembler - rsh64" { 227 | const src = "rsh64 r9, r10"; 228 | const expected = &[_]u8{ 0x7f, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 229 | const result = try assembler.assemble(src); 230 | defer std.heap.page_allocator.free(result); 231 | try expectEqualBytes(expected, result); 232 | } 233 | 234 | test "assembler - mod64" { 235 | const src = "mod64 r1, 0x3"; 236 | const expected = &[_]u8{ 0x97, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }; 237 | const result = try assembler.assemble(src); 238 | defer std.heap.page_allocator.free(result); 239 | try expectEqualBytes(expected, result); 240 | } 241 | 242 | test "assembler - xor64" { 243 | const src = "xor64 r2, r3"; 244 | const expected = &[_]u8{ 0xaf, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 245 | const result = try assembler.assemble(src); 246 | defer std.heap.page_allocator.free(result); 247 | try expectEqualBytes(expected, result); 248 | } 249 | 250 | test "assembler - arsh64" { 251 | const src = "arsh64 r4, 2"; 252 | const expected = &[_]u8{ 0xc7, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }; 253 | const result = try assembler.assemble(src); 254 | defer std.heap.page_allocator.free(result); 255 | try expectEqualBytes(expected, result); 256 | } 257 | 258 | test "assembler - ldabsw" { 259 | const src = "ldabsw 0x10"; 260 | const expected = &[_]u8{ 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00 }; 261 | const result = try assembler.assemble(src); 262 | defer std.heap.page_allocator.free(result); 263 | try expectEqualBytes(expected, result); 264 | } 265 | 266 | test "assembler - ldindw" { 267 | const src = "ldindw r1, 0x20"; 268 | const expected = &[_]u8{ 0x40, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 }; 269 | const result = try assembler.assemble(src); 270 | defer std.heap.page_allocator.free(result); 271 | try expectEqualBytes(expected, result); 272 | } 273 | 274 | test "assembler - ldxdw" { 275 | const src = "ldxdw r2, [r3+0x8]"; 276 | const expected = &[_]u8{ 0x79, 0x32, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }; 277 | const result = try assembler.assemble(src); 278 | defer std.heap.page_allocator.free(result); 279 | try expectEqualBytes(expected, result); 280 | } 281 | 282 | test "assembler - jeq" { 283 | const src = "jeq r1, 0x10, +5"; 284 | const expected = &[_]u8{ 0x15, 0x01, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00 }; 285 | const result = try assembler.assemble(src); 286 | defer std.heap.page_allocator.free(result); 287 | try expectEqualBytes(expected, result); 288 | } 289 | 290 | test "assembler - jgt" { 291 | const src = "jgt r2, r3, +10"; 292 | const expected = &[_]u8{ 0x2d, 0x32, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00 }; 293 | const result = try assembler.assemble(src); 294 | defer std.heap.page_allocator.free(result); 295 | try expectEqualBytes(expected, result); 296 | } 297 | 298 | test "assembler - jge" { 299 | const src = "jge r4, 0x20, +15"; 300 | const expected = &[_]u8{ 0x35, 0x04, 0x0f, 0x00, 0x20, 0x00, 0x00, 0x00 }; 301 | const result = try assembler.assemble(src); 302 | defer std.heap.page_allocator.free(result); 303 | try expectEqualBytes(expected, result); 304 | } 305 | 306 | test "assembler - jset" { 307 | const src = "jset r5, 0x1, +20"; 308 | const expected = &[_]u8{ 0x45, 0x05, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00 }; 309 | const result = try assembler.assemble(src); 310 | defer std.heap.page_allocator.free(result); 311 | try expectEqualBytes(expected, result); 312 | } 313 | 314 | test "assembler - jsgt" { 315 | const src = "jsgt r6, r7, +25"; 316 | const expected = &[_]u8{ 0x6d, 0x76, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00 }; 317 | const result = try assembler.assemble(src); 318 | defer std.heap.page_allocator.free(result); 319 | try expectEqualBytes(expected, result); 320 | } 321 | 322 | test "assembler - jsge" { 323 | const src = "jsge r8, 0x30, +30"; 324 | const expected = &[_]u8{ 0x75, 0x08, 0x1e, 0x00, 0x30, 0x00, 0x00, 0x00 }; 325 | const result = try assembler.assemble(src); 326 | defer std.heap.page_allocator.free(result); 327 | try expectEqualBytes(expected, result); 328 | } 329 | -------------------------------------------------------------------------------- /src/asm_parser.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const expectEqual = std.testing.expectEqual; 3 | const expect = std.testing.expect; 4 | const expectEqualSlices = std.testing.expectEqualSlices; 5 | 6 | /// Represents an operand in an eBPF instruction 7 | pub const Operand = union(enum) { 8 | Register: u8, 9 | Integer: i64, 10 | Memory: struct { 11 | base: u8, 12 | offset: i64, 13 | }, 14 | Nil, 15 | 16 | /// Compares two operands for equality 17 | pub fn eq(a: Operand, b: Operand) bool { 18 | switch (a) { 19 | .Register => |a_val| { 20 | if (b == .Register) { 21 | return a_val == b.Register; 22 | } 23 | }, 24 | .Integer => |a_val| { 25 | if (b == .Integer) { 26 | return a_val == b.Integer; 27 | } 28 | }, 29 | .Memory => |a_val| { 30 | if (b == .Memory) { 31 | return a_val.base == b.Memory.base and a_val.offset == b.Memory.offset; 32 | } 33 | }, 34 | .Nil => { 35 | if (b == .Nil) { 36 | return true; 37 | } 38 | }, 39 | } 40 | return false; // If tags do not match 41 | } 42 | }; 43 | 44 | /// Represents a parsed eBPF instruction 45 | pub const Instruction = struct { 46 | name: []const u8, 47 | operands: [3]Operand, 48 | 49 | /// Compares two instructions for equality 50 | pub fn eq(a: Instruction, b: Instruction) bool { 51 | return std.mem.eql(u8, a.name, b.name) and 52 | a.operands[0].eq(b.operands[0]) and 53 | a.operands[1].eq(b.operands[1]) and 54 | a.operands[2].eq(b.operands[2]); 55 | } 56 | }; 57 | 58 | /// Parses an identifier from the input 59 | fn parseIdent(input: []const u8) ![]const u8 { 60 | var i: usize = 0; 61 | while (i < input.len and (std.ascii.isAlphanumeric(input[i]) or std.ascii.isDigit(input[i]) or input[i] == '_')) : (i += 1) {} 62 | if (i == 0) return error.InvalidIdentifier; 63 | return input[0..i]; 64 | } 65 | 66 | /// Parses an integer from the input 67 | fn parseInteger(input: []const u8) !i64 { 68 | return std.fmt.parseInt(i64, input, 0); 69 | } 70 | 71 | /// Parses a register from the input 72 | fn parseRegister(input: []const u8) !u8 { 73 | if (input.len < 2 or input[0] != 'r') return error.InvalidRegister; 74 | return std.fmt.parseInt(u8, input[1..], 0); 75 | } 76 | 77 | /// Parses an operand from the input 78 | fn parseOperand(input: []const u8) !Operand { 79 | if (input.len == 0) return Operand.Nil; 80 | if (input[0] == 'r') return Operand{ .Register = try parseRegister(input) }; 81 | if (input[0] == '[') { 82 | const end = std.mem.indexOf(u8, input, "]") orelse return error.InvalidMemoryOperand; 83 | const base_end = std.mem.indexOf(u8, input[1..end], &[_]u8{'+'}) orelse (std.mem.indexOf(u8, input[1..end], &[_]u8{'-'}) orelse (std.mem.indexOf(u8, input[1..end], "]") orelse end) - 1); 84 | const base = try parseRegister(input[1 .. 1 + base_end]); 85 | var offset: i64 = 0; 86 | if (base_end + 1 < end) { 87 | const sign: i64 = if (input[1 + base_end] == '-') -1 else 1; 88 | offset = sign * try parseInteger(input[1 + base_end + 1 .. end]); 89 | } 90 | 91 | return Operand{ .Memory = .{ .base = base, .offset = offset } }; 92 | } 93 | return Operand{ .Integer = try parseInteger(input) }; 94 | } 95 | 96 | /// Parses a single instruction from the input 97 | fn parseInstruction(input: []const u8) !Instruction { 98 | var tokens = std.mem.splitSequence(u8, input, " "); 99 | const name_token = tokens.next() orelse return error.InvalidToken; 100 | const name = try parseIdent(name_token); 101 | var operands: [3]Operand = .{ Operand.Nil, Operand.Nil, Operand.Nil }; 102 | var i: usize = 0; 103 | 104 | // Get the remaining part of the input after the instruction name 105 | const remaining_input = std.mem.trim(u8, input[name.len..], " "); 106 | if (remaining_input.len > 0) { 107 | var operand_tokens = std.mem.splitSequence(u8, remaining_input, ","); 108 | while (operand_tokens.next()) |token| { 109 | if (i >= operands.len) break; 110 | operands[i] = try parseOperand(std.mem.trim(u8, token, " ")); 111 | i += 1; 112 | } 113 | } 114 | 115 | return Instruction{ .name = name, .operands = operands }; 116 | } 117 | 118 | /// Parses eBPF assembly source code into a list of instructions 119 | pub fn parse(input: []const u8) ![]Instruction { 120 | var instructions = std.ArrayList(Instruction).init(std.heap.page_allocator); 121 | defer instructions.deinit(); 122 | 123 | var lines = std.mem.splitSequence(u8, input, "\n"); 124 | while (lines.next()) |line| { 125 | const trimmed_line = std.mem.trim(u8, line, " \t"); 126 | if (trimmed_line.len == 0) continue; 127 | const instruction = try parseInstruction(trimmed_line); 128 | try instructions.append(instruction); 129 | } 130 | return instructions.toOwnedSlice(); 131 | } 132 | 133 | /// Possible parsing errors 134 | pub const errors = error{InvalidToken}; 135 | 136 | test "test_ident" { 137 | try expectEqual(parseIdent("nop"), "nop"); 138 | try expectEqual(parseIdent("add32"), "add32"); 139 | try expectEqualSlices(u8, try parseIdent("add32*"), "add32"); 140 | } 141 | 142 | test "test_integer" { 143 | try expectEqual(parseInteger("0"), 0); 144 | try expectEqual(parseInteger("42"), 42); 145 | try expectEqual(parseInteger("+42"), 42); 146 | try expectEqual(parseInteger("-42"), -42); 147 | try expectEqual(parseInteger("0x0"), 0); 148 | try expectEqual(parseInteger("-0x1f"), -31); 149 | } 150 | 151 | test "test_register" { 152 | try expectEqual(parseRegister("r0"), 0); 153 | try expectEqual(parseRegister("r15"), 15); 154 | } 155 | 156 | test "test_operand" { 157 | try expectEqual(parseOperand("r0"), Operand{ .Register = 0 }); 158 | try expectEqual(parseOperand("r15"), Operand{ .Register = 15 }); 159 | try expectEqual(parseOperand("0"), Operand{ .Integer = 0 }); 160 | try expectEqual(parseOperand("42"), Operand{ .Integer = 42 }); 161 | try expectEqual(parseOperand("[r1+5]"), Operand{ .Memory = .{ .base = 1, .offset = 5 } }); 162 | try expectEqual(parseOperand("[r3+0x1f]"), Operand{ .Memory = .{ .base = 3, .offset = 31 } }); 163 | try expectEqual(parseOperand("[r3-0x1f]"), Operand{ .Memory = .{ .base = 3, .offset = -31 } }); 164 | } 165 | 166 | test "test_instruction" { 167 | try expect(Instruction.eq(try parseInstruction("exit"), Instruction{ .name = "exit", .operands = .{ Operand.Nil, Operand.Nil, Operand.Nil } })); 168 | try expect(Instruction.eq(try parseInstruction("call 2"), Instruction{ .name = "call", .operands = .{ Operand{ .Integer = 2 }, Operand.Nil, Operand.Nil } })); 169 | try expect(Instruction.eq(try parseInstruction("addi r1, 2"), Instruction{ .name = "addi", .operands = .{ Operand{ .Register = 1 }, Operand{ .Integer = 2 }, Operand.Nil } })); 170 | try expect(Instruction.eq(try parseInstruction("ldxb r2, [r1+12]"), Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 2 }, Operand{ .Memory = .{ .base = 1, .offset = 12 } }, Operand.Nil } })); 171 | try expect(Instruction.eq(try parseInstruction("lsh r3, 0x8"), Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 8 }, Operand.Nil } })); 172 | try expect(Instruction.eq(try parseInstruction("jne r3, 0x8, +37"), Instruction{ .name = "jne", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 8 }, Operand{ .Integer = 37 } } })); 173 | try expect(Instruction.eq(try parseInstruction("jne r3,0x8,+37"), Instruction{ .name = "jne", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 8 }, Operand{ .Integer = 37 } } })); 174 | } 175 | 176 | test "test_empty" { 177 | try expectEqual(parse(""), &[_]Instruction{}); 178 | } 179 | 180 | test "test_lsh" { 181 | try expect(Instruction.eq(try parseInstruction("lsh r3, 0x20"), Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 0x20 }, Operand.Nil } })); 182 | } 183 | 184 | test "test_ja" { 185 | try expect(Instruction.eq(try parseInstruction("ja +1"), Instruction{ .name = "ja", .operands = .{ Operand{ .Integer = 1 }, Operand.Nil, Operand.Nil } })); 186 | } 187 | 188 | test "test_ldxh" { 189 | try expect(Instruction.eq(try parseInstruction("ldxh r4, [r1+12]"), Instruction{ .name = "ldxh", .operands = .{ Operand{ .Register = 4 }, Operand{ .Memory = .{ .base = 1, .offset = 12 } }, Operand.Nil } })); 190 | } 191 | 192 | test "test_tcp_sack" { 193 | const src = 194 | \\ldxb r2, [r1+12] 195 | \\ldxb r3, [r1+13] 196 | \\lsh r3, 0x8 197 | \\or r3, r2 198 | \\mov r0, 0x0 199 | \\jne r3, 0x8, +37 200 | \\ldxb r2, [r1+23] 201 | \\jne r2, 0x6, +35 202 | \\ldxb r2, [r1+14] 203 | \\add r1, 0xe 204 | \\and r2, 0xf 205 | \\lsh r2, 0x2 206 | \\add r1, r2 207 | \\mov r0, 0x0 208 | \\ldxh r4, [r1+12] 209 | \\add r1, 0x14 210 | \\rsh r4, 0x2 211 | \\and r4, 0x3c 212 | \\mov r2, r4 213 | \\add r2, 0xffffffec 214 | \\mov r5, 0x15 215 | \\mov r3, 0x0 216 | \\jgt r5, r4, +20 217 | \\mov r5, r3 218 | \\lsh r5, 0x20 219 | \\arsh r5, 0x20 220 | \\mov r4, r1 221 | \\add r4, r5 222 | \\ldxb r5, [r4] 223 | \\jeq r5, 0x1, +4 224 | \\jeq r5, 0x0, +12 225 | \\mov r6, r3 226 | \\jeq r5, 0x5, +9 227 | \\ja +2 228 | \\add r3, 0x1 229 | \\mov r6, r3 230 | \\ldxb r3, [r4+1] 231 | \\add r3, r6 232 | \\lsh r3, 0x20 233 | \\arsh r3, 0x20 234 | \\jsgt r2, r3, -18 235 | \\ja +1 236 | \\mov r0, 0x1 237 | \\exit 238 | ; 239 | const expected = &[_]Instruction{ 240 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 2 }, Operand{ .Memory = .{ .base = 1, .offset = 12 } }, Operand.Nil } }, 241 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 3 }, Operand{ .Memory = .{ .base = 1, .offset = 13 } }, Operand.Nil } }, 242 | Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 8 }, Operand.Nil } }, 243 | Instruction{ .name = "or", .operands = .{ Operand{ .Register = 3 }, Operand{ .Register = 2 }, Operand.Nil } }, 244 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 0 }, Operand{ .Integer = 0 }, Operand.Nil } }, 245 | Instruction{ .name = "jne", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 8 }, Operand{ .Integer = 37 } } }, 246 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 2 }, Operand{ .Memory = .{ .base = 1, .offset = 23 } }, Operand.Nil } }, 247 | Instruction{ .name = "jne", .operands = .{ Operand{ .Register = 2 }, Operand{ .Integer = 6 }, Operand{ .Integer = 35 } } }, 248 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 2 }, Operand{ .Memory = .{ .base = 1, .offset = 14 } }, Operand.Nil } }, 249 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 1 }, Operand{ .Integer = 14 }, Operand.Nil } }, 250 | Instruction{ .name = "and", .operands = .{ Operand{ .Register = 2 }, Operand{ .Integer = 15 }, Operand.Nil } }, 251 | Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 2 }, Operand{ .Integer = 2 }, Operand.Nil } }, 252 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 1 }, Operand{ .Register = 2 }, Operand.Nil } }, 253 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 0 }, Operand{ .Integer = 0 }, Operand.Nil } }, 254 | Instruction{ .name = "ldxh", .operands = .{ Operand{ .Register = 4 }, Operand{ .Memory = .{ .base = 1, .offset = 12 } }, Operand.Nil } }, 255 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 1 }, Operand{ .Integer = 20 }, Operand.Nil } }, 256 | Instruction{ .name = "rsh", .operands = .{ Operand{ .Register = 4 }, Operand{ .Integer = 2 }, Operand.Nil } }, 257 | Instruction{ .name = "and", .operands = .{ Operand{ .Register = 4 }, Operand{ .Integer = 60 }, Operand.Nil } }, 258 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 2 }, Operand{ .Register = 4 }, Operand.Nil } }, 259 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 2 }, Operand{ .Integer = 4294967276 }, Operand.Nil } }, 260 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 21 }, Operand.Nil } }, 261 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 0 }, Operand.Nil } }, 262 | Instruction{ .name = "jgt", .operands = .{ Operand{ .Register = 5 }, Operand{ .Register = 4 }, Operand{ .Integer = 20 } } }, 263 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 5 }, Operand{ .Register = 3 }, Operand.Nil } }, 264 | Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 32 }, Operand.Nil } }, 265 | Instruction{ .name = "arsh", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 32 }, Operand.Nil } }, 266 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 4 }, Operand{ .Register = 1 }, Operand.Nil } }, 267 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 4 }, Operand{ .Register = 5 }, Operand.Nil } }, 268 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 5 }, Operand{ .Memory = .{ .base = 4, .offset = 0 } }, Operand.Nil } }, 269 | Instruction{ .name = "jeq", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 1 }, Operand{ .Integer = 4 } } }, 270 | Instruction{ .name = "jeq", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 0 }, Operand{ .Integer = 12 } } }, 271 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 6 }, Operand{ .Register = 3 }, Operand.Nil } }, 272 | Instruction{ .name = "jeq", .operands = .{ Operand{ .Register = 5 }, Operand{ .Integer = 5 }, Operand{ .Integer = 9 } } }, 273 | Instruction{ .name = "ja", .operands = .{ Operand{ .Integer = 2 }, Operand.Nil, Operand.Nil } }, 274 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 1 }, Operand.Nil } }, 275 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 6 }, Operand{ .Register = 3 }, Operand.Nil } }, 276 | Instruction{ .name = "ldxb", .operands = .{ Operand{ .Register = 3 }, Operand{ .Memory = .{ .base = 4, .offset = 1 } }, Operand.Nil } }, 277 | Instruction{ .name = "add", .operands = .{ Operand{ .Register = 3 }, Operand{ .Register = 6 }, Operand.Nil } }, 278 | Instruction{ .name = "lsh", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 32 }, Operand.Nil } }, 279 | Instruction{ .name = "arsh", .operands = .{ Operand{ .Register = 3 }, Operand{ .Integer = 32 }, Operand.Nil } }, 280 | Instruction{ .name = "jsgt", .operands = .{ Operand{ .Register = 2 }, Operand{ .Register = 3 }, Operand{ .Integer = -18 } } }, 281 | Instruction{ .name = "ja", .operands = .{ Operand{ .Integer = 1 }, Operand.Nil, Operand.Nil } }, 282 | Instruction{ .name = "mov", .operands = .{ Operand{ .Register = 0 }, Operand{ .Integer = 1 }, Operand.Nil } }, 283 | Instruction{ .name = "exit", .operands = .{ Operand.Nil, Operand.Nil, Operand.Nil } }, 284 | }; 285 | const actual = try parse(src); 286 | try expectEqual(actual.len, expected.len); 287 | var index: u32 = 0; 288 | for (actual) |ins| { 289 | try expect(Instruction.eq(ins, expected[index])); 290 | index += 1; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/assembler.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ebpf = @import("ebpf.zig"); 3 | const parse = @import("asm_parser.zig").parse; 4 | 5 | /// Represents the type of eBPF instruction 6 | const InstructionType = enum { 7 | AluBinary, 8 | AluUnary, 9 | LoadAbs, 10 | LoadInd, 11 | LoadImm, 12 | LoadReg, 13 | StoreImm, 14 | StoreReg, 15 | JumpUnconditional, 16 | JumpConditional, 17 | Call, 18 | Endian, 19 | NoOperand, 20 | }; 21 | 22 | const Operand = @import("asm_parser.zig").Operand; 23 | const Instruction = @import("asm_parser.zig").Instruction; 24 | 25 | const InstructionMapEntry = struct { instType: InstructionType, opcode: u8 }; 26 | const InstructionMap = std.StringHashMap(InstructionMapEntry); 27 | 28 | /// Creates a map of instruction names to their types and opcodes 29 | fn makeInstructionMap() !InstructionMap { 30 | var result = InstructionMap.init(std.heap.page_allocator); 31 | 32 | const aluBinaryOps = [_]struct { name: []const u8, opc: u8 }{ 33 | .{ .name = "add", .opc = ebpf.BPF_ADD }, 34 | .{ .name = "sub", .opc = ebpf.BPF_SUB }, 35 | .{ .name = "mul", .opc = ebpf.BPF_MUL }, 36 | .{ .name = "div", .opc = ebpf.BPF_DIV }, 37 | .{ .name = "or", .opc = ebpf.BPF_OR }, 38 | .{ .name = "and", .opc = ebpf.BPF_AND }, 39 | .{ .name = "lsh", .opc = ebpf.BPF_LSH }, 40 | .{ .name = "rsh", .opc = ebpf.BPF_RSH }, 41 | .{ .name = "mod", .opc = ebpf.BPF_MOD }, 42 | .{ .name = "xor", .opc = ebpf.BPF_XOR }, 43 | .{ .name = "mov", .opc = ebpf.BPF_MOV }, 44 | .{ .name = "arsh", .opc = ebpf.BPF_ARSH }, 45 | }; 46 | 47 | const memSizes = [_]struct { suffix: []const u8, size: u8 }{ 48 | .{ .suffix = "w", .size = ebpf.BPF_W }, 49 | .{ .suffix = "h", .size = ebpf.BPF_H }, 50 | .{ .suffix = "b", .size = ebpf.BPF_B }, 51 | .{ .suffix = "dw", .size = ebpf.BPF_DW }, 52 | }; 53 | 54 | const jumpConditions = [_]struct { name: []const u8, condition: u8 }{ 55 | .{ .name = "jeq", .condition = ebpf.BPF_JEQ }, 56 | .{ .name = "jgt", .condition = ebpf.BPF_JGT }, 57 | .{ .name = "jge", .condition = ebpf.BPF_JGE }, 58 | .{ .name = "jlt", .condition = ebpf.BPF_JLT }, 59 | .{ .name = "jle", .condition = ebpf.BPF_JLE }, 60 | .{ .name = "jset", .condition = ebpf.BPF_JSET }, 61 | .{ .name = "jne", .condition = ebpf.BPF_JNE }, 62 | .{ .name = "jsgt", .condition = ebpf.BPF_JSGT }, 63 | .{ .name = "jsge", .condition = ebpf.BPF_JSGE }, 64 | .{ .name = "jslt", .condition = ebpf.BPF_JSLT }, 65 | .{ .name = "jsle", .condition = ebpf.BPF_JSLE }, 66 | }; 67 | 68 | // Miscellaneous 69 | try result.put("exit", InstructionMapEntry{ .instType = .NoOperand, .opcode = ebpf.EXIT }); 70 | try result.put("ja", InstructionMapEntry{ .instType = .JumpUnconditional, .opcode = ebpf.JA }); 71 | try result.put("call", InstructionMapEntry{ .instType = .Call, .opcode = ebpf.CALL }); 72 | try result.put("lddw", InstructionMapEntry{ .instType = .LoadImm, .opcode = ebpf.LD_DW_IMM }); 73 | 74 | // AluUnary 75 | try result.put("neg", InstructionMapEntry{ .instType = .AluUnary, .opcode = ebpf.NEG64 }); 76 | try result.put("neg32", InstructionMapEntry{ .instType = .AluUnary, .opcode = ebpf.NEG32 }); 77 | try result.put("neg64", InstructionMapEntry{ .instType = .AluUnary, .opcode = ebpf.NEG64 }); 78 | 79 | // AluBinary 80 | for (aluBinaryOps) |op| { 81 | try result.put(op.name, InstructionMapEntry{ .instType = .AluBinary, .opcode = ebpf.BPF_ALU64 | op.opc }); 82 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "{s}32", .{op.name}), InstructionMapEntry{ .instType = .AluBinary, .opcode = ebpf.BPF_ALU | op.opc }); 83 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "{s}64", .{op.name}), InstructionMapEntry{ .instType = .AluBinary, .opcode = ebpf.BPF_ALU64 | op.opc }); 84 | } 85 | 86 | // LoadAbs, LoadInd, LoadReg, StoreImm, and StoreReg 87 | for (memSizes) |size| { 88 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "ldabs{s}", .{size.suffix}), InstructionMapEntry{ .instType = .LoadAbs, .opcode = ebpf.BPF_ABS | ebpf.BPF_LD | size.size }); 89 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "ldind{s}", .{size.suffix}), InstructionMapEntry{ .instType = .LoadInd, .opcode = ebpf.BPF_IND | ebpf.BPF_LD | size.size }); 90 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "ldx{s}", .{size.suffix}), InstructionMapEntry{ .instType = .LoadReg, .opcode = ebpf.BPF_MEM | ebpf.BPF_LDX | size.size }); 91 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "st{s}", .{size.suffix}), InstructionMapEntry{ .instType = .StoreImm, .opcode = ebpf.BPF_MEM | ebpf.BPF_ST | size.size }); 92 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "stx{s}", .{size.suffix}), InstructionMapEntry{ .instType = .StoreReg, .opcode = ebpf.BPF_STX | ebpf.BPF_MEM | size.size }); 93 | } 94 | 95 | // JumpConditional 96 | for (jumpConditions) |jmp| { 97 | try result.put(jmp.name, InstructionMapEntry{ .instType = .JumpConditional, .opcode = ebpf.BPF_JMP | jmp.condition }); 98 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "{s}32", .{jmp.name}), InstructionMapEntry{ .instType = .JumpConditional, .opcode = ebpf.BPF_JMP32 | jmp.condition }); 99 | } 100 | 101 | // Endian 102 | for ([_]u8{ 16, 32, 64 }) |sz| { 103 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "be{d}", .{sz}), InstructionMapEntry{ .instType = .Endian, .opcode = ebpf.BE }); 104 | try result.put(try std.fmt.allocPrint(std.heap.page_allocator, "le{d}", .{sz}), InstructionMapEntry{ .instType = .Endian, .opcode = ebpf.LE }); 105 | } 106 | 107 | return result; 108 | } 109 | 110 | /// Creates an eBPF instruction from its components 111 | fn insn(opc: u8, dst: u8, src: u8, off: i16, imm: i32) ebpf.Instruction { 112 | return ebpf.Instruction{ 113 | .op = opc, 114 | .dst = dst, 115 | .src = src, 116 | .offset = off, 117 | .imm = imm, 118 | }; 119 | } 120 | 121 | /// Encodes a parsed instruction into an eBPF instruction 122 | fn encode(instType: InstructionType, opc: u8, instruction: Instruction) !ebpf.Instruction { 123 | const operands = instruction.operands; 124 | switch (instType) { 125 | .AluBinary => switch (operands[0]) { 126 | .Register => |reg| { 127 | if (reg > 10) return AssemblerError.InvalidRegister; 128 | switch (operands[1]) { 129 | .Register => |src_reg| { 130 | if (src_reg > 10) return AssemblerError.InvalidRegister; 131 | return insn(opc | ebpf.BPF_X, reg, src_reg, 0, 0); 132 | }, 133 | .Integer => |imm| { 134 | return insn(opc | ebpf.BPF_K, reg, 0, 0, @intCast(imm)); 135 | }, 136 | else => return AssemblerError.InvalidOperands, 137 | } 138 | }, 139 | else => return AssemblerError.InvalidOperands, 140 | }, 141 | .AluUnary => switch (operands[0]) { 142 | .Register => |reg| { 143 | if (reg > 10) return AssemblerError.InvalidRegister; 144 | return insn(opc, reg, 0, 0, 0); 145 | }, 146 | else => return AssemblerError.InvalidOperands, 147 | }, 148 | .LoadAbs => switch (operands[0]) { 149 | .Integer => |imm| { 150 | return insn(opc, 0, 0, 0, @intCast(imm)); 151 | }, 152 | else => return AssemblerError.InvalidOperands, 153 | }, 154 | .LoadInd => switch (operands[0]) { 155 | .Register => |reg| { 156 | if (reg > 10) return AssemblerError.InvalidRegister; 157 | switch (operands[1]) { 158 | .Integer => |off| { 159 | return insn(opc, 0, reg, 0, @intCast(off)); 160 | }, 161 | else => return AssemblerError.InvalidOperands, 162 | } 163 | }, 164 | else => return AssemblerError.InvalidOperands, 165 | }, 166 | .LoadImm => switch (operands[0]) { 167 | .Register => |reg| { 168 | if (reg > 10) return AssemblerError.InvalidRegister; 169 | switch (operands[1]) { 170 | .Integer => |imm| { 171 | return insn(opc, reg, 0, 0, @intCast(imm)); 172 | }, 173 | else => return AssemblerError.InvalidOperands, 174 | } 175 | }, 176 | else => return AssemblerError.InvalidOperands, 177 | }, 178 | .LoadReg => switch (operands[0]) { 179 | .Register => |dst| { 180 | if (dst > 10) return AssemblerError.InvalidRegister; 181 | switch (operands[1]) { 182 | .Memory => |mem| { 183 | if (mem.base > 10) return AssemblerError.InvalidRegister; 184 | return insn(opc, dst, mem.base, @intCast(mem.offset), 0); 185 | }, 186 | else => return AssemblerError.InvalidOperands, 187 | } 188 | }, 189 | else => return AssemblerError.InvalidOperands, 190 | }, 191 | .StoreImm => switch (operands[0]) { 192 | .Memory => |mem| { 193 | if (mem.base > 10) return AssemblerError.InvalidRegister; 194 | switch (operands[1]) { 195 | .Integer => |imm| { 196 | return insn(opc, mem.base, 0, @intCast(mem.offset), @intCast(imm)); 197 | }, 198 | else => return AssemblerError.InvalidOperands, 199 | } 200 | }, 201 | else => return AssemblerError.InvalidOperands, 202 | }, 203 | .StoreReg => switch (operands[0]) { 204 | .Memory => |mem| { 205 | if (mem.base > 10) return AssemblerError.InvalidRegister; 206 | switch (operands[1]) { 207 | .Register => |src| { 208 | if (src > 10) return AssemblerError.InvalidRegister; 209 | return insn(opc, mem.base, src, @intCast(mem.offset), 0); 210 | }, 211 | else => return AssemblerError.InvalidOperands, 212 | } 213 | }, 214 | else => return AssemblerError.InvalidOperands, 215 | }, 216 | .JumpUnconditional => switch (operands[0]) { 217 | .Integer => |off| { 218 | return insn(opc, 0, 0, @intCast(off), 0); 219 | }, 220 | else => return AssemblerError.InvalidOperands, 221 | }, 222 | .JumpConditional => switch (operands[0]) { 223 | .Register => |reg| { 224 | if (reg > 10) return AssemblerError.InvalidRegister; 225 | switch (operands[1]) { 226 | .Register => |src_reg| { 227 | if (src_reg > 10) return AssemblerError.InvalidRegister; 228 | switch (operands[2]) { 229 | .Integer => |off| { 230 | return insn(opc | ebpf.BPF_X, reg, src_reg, @intCast(off), 0); 231 | }, 232 | else => return AssemblerError.InvalidOperands, 233 | } 234 | }, 235 | .Integer => |imm| { 236 | switch (operands[2]) { 237 | .Integer => |off| { 238 | return insn(opc | ebpf.BPF_K, reg, 0, @intCast(off), @intCast(imm)); 239 | }, 240 | else => return AssemblerError.InvalidOperands, 241 | } 242 | }, 243 | else => return AssemblerError.InvalidOperands, 244 | } 245 | }, 246 | else => return AssemblerError.InvalidOperands, 247 | }, 248 | .Call => switch (operands[0]) { 249 | .Integer => |imm| { 250 | return insn(opc, 0, 0, 0, @intCast(imm)); 251 | }, 252 | else => return AssemblerError.InvalidOperands, 253 | }, 254 | .Endian => switch (operands[0]) { 255 | .Register => |reg| { 256 | if (reg > 10) return AssemblerError.InvalidRegister; 257 | const size: i32 = switch (opc) { 258 | ebpf.BE => blk: { 259 | if (std.mem.eql(u8, instruction.name, "be16")) { 260 | break :blk 16; 261 | } else if (std.mem.eql(u8, instruction.name, "be32")) { 262 | break :blk 32; 263 | } else if (std.mem.eql(u8, instruction.name, "be64")) { 264 | break :blk 64; 265 | } else { 266 | return AssemblerError.InvalidOperands; 267 | } 268 | }, 269 | ebpf.LE => blk: { 270 | if (std.mem.eql(u8, instruction.name, "le16")) { 271 | break :blk 16; 272 | } else if (std.mem.eql(u8, instruction.name, "le32")) { 273 | break :blk 32; 274 | } else if (std.mem.eql(u8, instruction.name, "le64")) { 275 | break :blk 64; 276 | } else { 277 | return AssemblerError.InvalidOperands; 278 | } 279 | }, 280 | else => return AssemblerError.InvalidOperands, 281 | }; 282 | return insn(opc, reg, 0, 0, size); 283 | }, 284 | else => return AssemblerError.InvalidOperands, 285 | }, 286 | .NoOperand => return insn(opc, 0, 0, 0, 0), 287 | } 288 | } 289 | 290 | /// Assembles a list of parsed instructions into eBPF bytecode 291 | fn assembleInternal(parsed: []Instruction) ![]ebpf.Instruction { 292 | var insns = std.ArrayList(ebpf.Instruction).init(std.heap.page_allocator); 293 | const instructionMap = try makeInstructionMap(); 294 | 295 | for (parsed) |instruction| { 296 | const entry = instructionMap.get(instruction.name) orelse return AssemblerError.InvalidInstruction; 297 | const inst = try encode(entry.instType, entry.opcode, instruction); 298 | try insns.append(inst); 299 | 300 | // Special case for lddw 301 | if (entry.instType == .LoadImm and instruction.operands[1] != Operand.Nil) { 302 | if (instruction.operands[1] == Operand.Integer) { 303 | try insns.append(insn(0, 0, 0, 0, @intCast(instruction.operands[1].Integer >> 32))); 304 | } 305 | } 306 | } 307 | 308 | return insns.toOwnedSlice(); 309 | } 310 | 311 | /// Assembles eBPF assembly source code into bytecode 312 | pub fn assemble(src: []const u8) ![]const u8 { 313 | const parsed = try parse(src); 314 | const insns = try assembleInternal(parsed); 315 | 316 | var result = std.ArrayList(u8).init(std.heap.page_allocator); 317 | for (insns) |instruction| { 318 | const instr_array = instruction.to_array(); 319 | try result.appendSlice(&instr_array); 320 | } 321 | 322 | return result.toOwnedSlice(); 323 | } 324 | 325 | /// Possible errors during assembly 326 | pub const AssemblerError = error{ 327 | InvalidInstruction, 328 | InvalidOperand, 329 | OutOfMemory, 330 | InvalidOperands, 331 | InvalidRegister, 332 | MismatchedOperands, 333 | }; 334 | -------------------------------------------------------------------------------- /tests/disassembler.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const disassembler = @import("zig-ebpf").disassembler; 4 | const ebpf = @import("zig-ebpf").ebpf; 5 | 6 | // Helper function to disassemble and compare results 7 | pub fn disasm(allocator: std.mem.Allocator, bytecode: []const u8, expected: []const u8) !void { 8 | const insn = try disassembler.to_insn_vec(allocator, bytecode); 9 | defer { 10 | for (insn) |item| { 11 | allocator.free(item.description); 12 | } 13 | allocator.free(insn); 14 | } 15 | 16 | var reasm = std.ArrayList(u8).init(allocator); 17 | defer reasm.deinit(); 18 | 19 | for (insn) |ins| { 20 | try reasm.appendSlice(ins.description); 21 | try reasm.append('\n'); 22 | } 23 | 24 | // Trim the trailing newline 25 | if (reasm.items.len > 0 and reasm.items[reasm.items.len - 1] == '\n') { 26 | _ = reasm.pop(); 27 | } 28 | 29 | try testing.expectEqualStrings(expected, reasm.items); 30 | } 31 | 32 | // Test cases 33 | 34 | test "disassembler - empty" { 35 | try disasm(testing.allocator, &[_]u8{}, ""); 36 | } 37 | 38 | test "disassembler - tail" { 39 | const bytecode = &[_]u8{ 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 40 | try disasm(testing.allocator, bytecode, "tail_call"); 41 | } 42 | 43 | test "disassembler - exit" { 44 | const bytecode = &[_]u8{ 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 45 | try disasm(testing.allocator, bytecode, "exit"); 46 | } 47 | test "disassembler - add64" { 48 | const bytecode1 = &[_]u8{ 0x0f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 49 | try disasm(testing.allocator, bytecode1, "add64 r3, r1"); 50 | 51 | const bytecode2 = &[_]u8{ 0x07, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00 }; 52 | try disasm(testing.allocator, bytecode2, "add64 r1, 0x5"); 53 | } 54 | 55 | test "disassembler - neg64" { 56 | const bytecode = &[_]u8{ 0x87, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 57 | try disasm(testing.allocator, bytecode, "neg64 r1"); 58 | } 59 | 60 | test "disassembler - ldxw" { 61 | const bytecode = &[_]u8{ 0x61, 0x12, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; 62 | try disasm(testing.allocator, bytecode, "ldxw r2, [r1+0x5]"); 63 | } 64 | 65 | test "disassembler - stw" { 66 | const bytecode = &[_]u8{ 0x62, 0x02, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00 }; 67 | try disasm(testing.allocator, bytecode, "stw [r2+0x5], 0x7"); 68 | } 69 | 70 | test "disassembler - stxw" { 71 | const bytecode = &[_]u8{ 0x63, 0x28, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; 72 | try disasm(testing.allocator, bytecode, "stxw [r8+0x5], r2"); 73 | } 74 | 75 | test "disassembler - ja" { 76 | const bytecode = &[_]u8{ 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }; 77 | try disasm(testing.allocator, bytecode, "ja +0x8"); 78 | } 79 | 80 | test "disassembler - jeq" { 81 | const bytecode1 = &[_]u8{ 0x15, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00 }; 82 | try disasm(testing.allocator, bytecode1, "jeq r1, 0x4, +0x8"); 83 | 84 | const bytecode2 = &[_]u8{ 0x1d, 0x13, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }; 85 | try disasm(testing.allocator, bytecode2, "jeq r3, r1, +0x8"); 86 | } 87 | 88 | test "disassembler - call" { 89 | const bytecode = &[_]u8{ 0x85, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }; 90 | try disasm(testing.allocator, bytecode, "call 0x3"); 91 | } 92 | 93 | test "disassembler - be32" { 94 | const bytecode = &[_]u8{ 0xdc, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00 }; 95 | try disasm(testing.allocator, bytecode, "be32 r1"); 96 | } 97 | 98 | test "disassembler - lddw" { 99 | const bytecode1 = &[_]u8{ 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, 0x00, 0x00, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11 }; 100 | try disasm(testing.allocator, bytecode1, "lddw r0, 0x1122334455667788"); 101 | 102 | const bytecode2 = &[_]u8{ 0x18, 0x01, 0x00, 0x00, 0x44, 0xcc, 0x33, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x22, 0xee, 0x11, 0xff }; 103 | try disasm(testing.allocator, bytecode2, "lddw r1, 0xff11ee22dd33cc44"); 104 | } 105 | 106 | test "disassembler - ldabsw" { 107 | const bytecode = &[_]u8{ 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; 108 | try disasm(testing.allocator, bytecode, "ldabsb 0x1"); 109 | } 110 | 111 | test "disassembler - ldindw" { 112 | const bytecode = &[_]u8{ 0x50, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 }; 113 | try disasm(testing.allocator, bytecode, "ldindb r1, 0x2"); 114 | } 115 | 116 | test "disassembler - ldxdw" { 117 | const bytecode = &[_]u8{ 0x79, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }; 118 | try disasm(testing.allocator, bytecode, "ldxdw r2, [r1+0x3]"); 119 | } 120 | 121 | test "disassembler - sth" { 122 | const bytecode = &[_]u8{ 0x6a, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00 }; 123 | try disasm(testing.allocator, bytecode, "sth [r1+0x2], 0x3"); 124 | } 125 | 126 | test "disassembler - stxh" { 127 | const bytecode = &[_]u8{ 0x6b, 0x13, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }; 128 | try disasm(testing.allocator, bytecode, "stxh [r3+0x2], r1"); 129 | } 130 | 131 | test "disassembler - alu_binary" { 132 | const bytecode1 = &[_]u8{ 133 | 0x0f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add64 r2, r1 134 | 0x1f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub64 r2, r1 135 | 0x2f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mul64 r2, r1 136 | 0x3f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // div64 r2, r1 137 | 0x4f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // or64 r2, r1 138 | 0x5f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and64 r2, r1 139 | 0x6f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // lsh64 r2, r1 140 | 0x7f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rsh64 r2, r1 141 | 0x9f, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mod64 r2, r1 142 | 0xaf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xor64 r2, r1 143 | 0xbf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r2, r1 144 | 0xcf, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // arsh64 r2, r1 145 | }; 146 | try disasm(testing.allocator, bytecode1, 147 | \\add64 r2, r1 148 | \\sub64 r2, r1 149 | \\mul64 r2, r1 150 | \\div64 r2, r1 151 | \\or64 r2, r1 152 | \\and64 r2, r1 153 | \\lsh64 r2, r1 154 | \\rsh64 r2, r1 155 | \\mod64 r2, r1 156 | \\xor64 r2, r1 157 | \\mov64 r2, r1 158 | \\arsh64 r2, r1 159 | ); 160 | 161 | const bytecode2 = &[_]u8{ 162 | 0x07, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // add64 r1, 0x2 163 | 0x17, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // sub64 r1, 0x2 164 | 0x27, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mul64 r1, 0x2 165 | 0x37, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // div64 r1, 0x2 166 | 0x47, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // or64 r1, 0x2 167 | 0x57, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // and64 r1, 0x2 168 | 0x67, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // lsh64 r1, 0x2 169 | 0x77, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // rsh64 r1, 0x2 170 | 0x97, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mod64 r1, 0x2 171 | 0xa7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // xor64 r1, 0x2 172 | 0xb7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mov64 r1, 0x2 173 | 0xc7, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // arsh64 r1, 0x2 174 | }; 175 | try disasm(testing.allocator, bytecode2, 176 | \\add64 r1, 0x2 177 | \\sub64 r1, 0x2 178 | \\mul64 r1, 0x2 179 | \\div64 r1, 0x2 180 | \\or64 r1, 0x2 181 | \\and64 r1, 0x2 182 | \\lsh64 r1, 0x2 183 | \\rsh64 r1, 0x2 184 | \\mod64 r1, 0x2 185 | \\xor64 r1, 0x2 186 | \\mov64 r1, 0x2 187 | \\arsh64 r1, 0x2 188 | ); 189 | 190 | const bytecode3 = &[_]u8{ 191 | 0x04, 0x12, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // add32 r2, 0x2 192 | 0x0c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r2, r1 193 | 0x14, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // sub32 r2, 0x1 194 | 0x1c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub32 r2, r1 195 | 0x24, 0x12, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // mul32 r2, 0x3 196 | 0x2c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mul32 r2, r1 197 | 0x34, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // div32 r2, 0x1 198 | 0x3c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // div32 r2, r1 199 | 0x44, 0x12, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // or32 r2, 0x4 200 | 0x4c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // or32 r2, r1 201 | 0x54, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and32 r2, 0x0 202 | 0x5c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and32 r2, r1 203 | }; 204 | try disasm(testing.allocator, bytecode3, 205 | \\add32 r2, 0x2 206 | \\add32 r2, r1 207 | \\sub32 r2, 0x1 208 | \\sub32 r2, r1 209 | \\mul32 r2, 0x3 210 | \\mul32 r2, r1 211 | \\div32 r2, 0x1 212 | \\div32 r2, r1 213 | \\or32 r2, 0x4 214 | \\or32 r2, r1 215 | \\and32 r2, 0x0 216 | \\and32 r2, r1 217 | ); 218 | 219 | const bytecode4 = &[_]u8{ 220 | 0x04, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // add32 r1, 0x2 221 | 0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // add32 r0, r1 222 | 0x14, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // sub32 r1, 0x2 223 | 0x1c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub32 r0, r1 224 | 0x24, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // mul32 r1, 0x2 225 | 0x2c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mul32 r0, r1 226 | 0x34, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // div32 r1, 0x2 227 | 0x3c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // div32 r0, r1 228 | 0x44, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // or32 r1, 0x2 229 | 0x4c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // or32 r0, r1 230 | 0x54, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // and32 r1, 0x2 231 | 0x5c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and32 r0, r1 232 | }; 233 | try disasm(testing.allocator, bytecode4, 234 | \\add32 r1, 0x2 235 | \\add32 r0, r1 236 | \\sub32 r1, 0x2 237 | \\sub32 r0, r1 238 | \\mul32 r1, 0x2 239 | \\mul32 r0, r1 240 | \\div32 r1, 0x2 241 | \\div32 r0, r1 242 | \\or32 r1, 0x2 243 | \\or32 r0, r1 244 | \\and32 r1, 0x2 245 | \\and32 r0, r1 246 | ); 247 | } 248 | 249 | test "disassembler - alu_unary" { 250 | const bytecode = &[_]u8{ 251 | 0x87, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // neg64 r1 252 | 0x84, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // neg32 r1 253 | }; 254 | try disasm(testing.allocator, bytecode, 255 | \\neg64 r1 256 | \\neg32 r1 257 | ); 258 | } 259 | 260 | test "disassembler - load_abs" { 261 | const bytecode = &[_]u8{ 262 | 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ldabsb 0x1 263 | 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ldabsh 0x1 264 | 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ldabsw 0x1 265 | 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ldabsdw 0x1 266 | }; 267 | try disasm(testing.allocator, bytecode, 268 | \\ldabsb 0x1 269 | \\ldabsh 0x1 270 | \\ldabsw 0x1 271 | \\ldabsdw 0x1 272 | ); 273 | } 274 | 275 | test "disassembler - load_ind" { 276 | const bytecode = &[_]u8{ 277 | 0x50, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // ldindw r1, 0x2 278 | 0x48, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // ldindh r1, 0x2 279 | 0x40, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // ldindb r1, 0x2 280 | 0x58, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // ldinddw r1, 0x2 281 | }; 282 | try disasm(testing.allocator, bytecode, 283 | \\ldindb r1, 0x2 284 | \\ldindh r1, 0x2 285 | \\ldindw r1, 0x2 286 | \\ldinddw r1, 0x2 287 | ); 288 | } 289 | 290 | test "disassembler - load_reg" { 291 | const bytecode = &[_]u8{ 292 | 0x61, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxw r2, [r1+0x3] 293 | 0x69, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxh r2, [r1+0x3] 294 | 0x71, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxb r2, [r1+0x3] 295 | 0x79, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxdw r2, [r1+0x3] 296 | }; 297 | try disasm(testing.allocator, bytecode, 298 | \\ldxw r2, [r1+0x3] 299 | \\ldxh r2, [r1+0x3] 300 | \\ldxb r2, [r1+0x3] 301 | \\ldxdw r2, [r1+0x3] 302 | ); 303 | } 304 | 305 | test "disassembler - store_imm" { 306 | const bytecode = &[_]u8{ 307 | 0x62, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, // stw [r1+0x2], 0x3 308 | 0x6a, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, // sth [r1+0x2], 0x3 309 | 0x72, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, // stb [r1+0x2], 0x3 310 | 0x7a, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, // stdw [r1+0x2], 0x3 311 | }; 312 | try disasm(testing.allocator, bytecode, 313 | \\stw [r1+0x2], 0x3 314 | \\sth [r1+0x2], 0x3 315 | \\stb [r1+0x2], 0x3 316 | \\stdw [r1+0x2], 0x3 317 | ); 318 | } 319 | 320 | test "disassembler - store_reg" { 321 | const bytecode = &[_]u8{ 322 | 0x63, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // stxw [r8+0x2], r2 323 | 0x6b, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // stxh [r8+0x2], r2 324 | 0x73, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // stxb [r8+0x2], r2 325 | 0x7b, 0x28, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // stxdw [r8+0x2], r2 326 | }; 327 | try disasm(testing.allocator, bytecode, 328 | \\stxw [r8+0x2], r2 329 | \\stxh [r8+0x2], r2 330 | \\stxb [r8+0x2], r2 331 | \\stxdw [r8+0x2], r2 332 | ); 333 | } 334 | 335 | test "disassembler - jump_conditional" { 336 | const bytecode = &[_]u8{ 337 | 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // ja +0x8 338 | 0x15, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, // jeq r1, 0x4, +0x8 339 | 0x55, 0x12, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, // jne r2, 0x0, -0x8 340 | 0x1d, 0x23, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, // jeq r3, r2, +0x5 341 | 0x35, 0x04, 0x03, 0x00, 0xff, 0xff, 0xff, 0xff, // jge r4, 0xffffffff, +0x3 342 | 0x2d, 0x15, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, // jgt r5, r1, -0x6 343 | 0xa5, 0x06, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, // jlt r6, 0x10, +0x2 344 | 0xbd, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // jle r7, r6, +0x1 345 | 0xb5, 0x08, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, // jle r8, 0x20, +0x4 346 | 0x6d, 0x89, 0xfc, 0xff, 0x00, 0x00, 0x00, 0x00, // jsgt r9, r8, -0x4 347 | 0x45, 0x01, 0x07, 0x00, 0x08, 0x00, 0x00, 0x00, // jset r1, 0x8, +0x7 348 | 0x5d, 0x23, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // jne r3, r2, +0x6 349 | 0x65, 0x04, 0x05, 0x00, 0x80, 0x00, 0x00, 0x00, // jsgt r4, 0x80, +0x5 350 | 0x7d, 0x56, 0xf9, 0xff, 0x00, 0x00, 0x00, 0x00, // jsge r6, r5, -0x7 351 | 0x75, 0x07, 0x03, 0x00, 0xff, 0xff, 0xff, 0xff, // jsge r7, 0xffffffff, +0x3 352 | 0x6d, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // jsgt r8, r7, +0x2 353 | 0xd5, 0x09, 0x01, 0x00, 0x40, 0x00, 0x00, 0x00, // jsle r9, 0x40, +0x1 354 | 0xcd, 0x9a, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, // jslt r10, r9, -0x2 355 | }; 356 | try disasm(testing.allocator, bytecode, 357 | \\ja +0x8 358 | \\jeq r1, 0x4, +0x8 359 | \\jne r2, 0x0, -0x8 360 | \\jeq r3, r2, +0x5 361 | \\jge r4, 0xffffffff, +0x3 362 | \\jgt r5, r1, -0x6 363 | \\jlt r6, 0x10, +0x2 364 | \\jle r7, r6, +0x1 365 | \\jle r8, 0x20, +0x4 366 | \\jsgt r9, r8, -0x4 367 | \\jset r1, 0x8, +0x7 368 | \\jne r3, r2, +0x6 369 | \\jsgt r4, 0x80, +0x5 370 | \\jsge r6, r5, -0x7 371 | \\jsge r7, 0xffffffff, +0x3 372 | \\jsgt r8, r7, +0x2 373 | \\jsle r9, 0x40, +0x1 374 | \\jslt r10, r9, -0x2 375 | ); 376 | } 377 | 378 | test "disassembler - jump_unconditional" { 379 | const bytecode = &[_]u8{ 380 | 0x05, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, // ja +0x5 381 | 0x05, 0x00, 0xfb, 0xff, 0x00, 0x00, 0x00, 0x00, // ja -0x5 382 | }; 383 | try disasm(testing.allocator, bytecode, 384 | \\ja +0x5 385 | \\ja -0x5 386 | ); 387 | } 388 | 389 | test "disassembler - endian" { 390 | const bytecode = &[_]u8{ 391 | 0xd4, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // be16 r1 392 | 0xd4, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // be32 r1 393 | 0xd4, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // be64 r1 394 | 0xdc, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // le16 r1 395 | 0xdc, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, // le32 r1 396 | 0xdc, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // le64 r1 397 | }; 398 | try disasm(testing.allocator, bytecode, 399 | \\le16 r1 400 | \\le32 r1 401 | \\le64 r1 402 | \\be16 r1 403 | \\be32 r1 404 | \\be64 r1 405 | ); 406 | } 407 | 408 | test "disassembler - large_immediate" { 409 | const bytecode = &[_]u8{ 410 | 0x07, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f, // add64 r1, 0x7fffffff 411 | 0x07, 0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f, // add64 r1, 0x7fffffff 412 | }; 413 | try disasm(testing.allocator, bytecode, 414 | \\add64 r1, 0x7fffffff 415 | \\add64 r1, 0x7fffffff 416 | ); 417 | } 418 | -------------------------------------------------------------------------------- /src/interpreter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ebpf = @import("ebpf.zig"); 3 | const helpers = @import("helpers.zig"); 4 | const builtin = @import("builtin"); 5 | const syscalls = @import("syscalls.zig"); 6 | 7 | const MemAccessType = enum { store, load }; 8 | const SHIFT_MASK_64: u64 = 0x3f; 9 | 10 | pub fn execute_program(alloc: std.mem.Allocator, program: []u8, mem: []const u8, mbuff: []const u8, syscalls_map: *std.AutoHashMap(usize, ebpf.Syscall)) !u64 { 11 | const stack: []u8 = try alloc.alloc(u8, ebpf.STACK_SIZE); 12 | defer alloc.free(stack); 13 | 14 | // R1 -> mem, R10 -> stack 15 | var reg = [11]u64{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @intFromPtr(stack.ptr + stack.len) }; 16 | var pc: usize = 0; 17 | 18 | if (mbuff.len == 0) { 19 | reg[1] = @as(u64, @intFromPtr(mbuff.ptr)); 20 | } else if (mem.len == 0) { 21 | reg[1] = @as(u64, @intFromPtr(mem.ptr)); 22 | } 23 | 24 | if (syscalls_map.count() == 0) { 25 | try syscalls_map.put(syscalls.BPF_KTIME_GETNS_IDX, &syscalls.bpf_ktime_get_ns); 26 | try syscalls_map.put(syscalls.BPF_TRACE_PRINTK_IDX, &syscalls.bpf_trace_printk); 27 | } 28 | while (pc * ebpf.INSN_SIZE < program.len) { 29 | // load ix 30 | const ix = try ebpf.Instruction.get_ix(program, pc); 31 | pc += 1; 32 | 33 | const dst = ix.dst; 34 | const src = ix.src; 35 | if (builtin.is_test) { 36 | // helpers.debug_print_vm_state(®, pc, src, dst); 37 | helpers.debug_print_ix(&ix); 38 | } 39 | switch (ix.op) { 40 | ebpf.LD_ABS_B => { 41 | // load data ptr 42 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + @as(u64, @intCast(ix.imm)); 43 | // check mem bounds 44 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 8, stack); 45 | // return data ptr 46 | reg[0] = dst; 47 | }, 48 | ebpf.LD_ABS_H => { 49 | // load data ptr 50 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + @as(u64, @intCast(ix.imm)); 51 | // check mem bounds 52 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 16, stack); 53 | // return data ptr 54 | reg[0] = d; 55 | }, 56 | ebpf.LD_ABS_W => { 57 | // load data ptr 58 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + @as(u64, @intCast(ix.imm)); 59 | // check mem bounds 60 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 32, stack); 61 | // return data ptr 62 | reg[0] = d; 63 | }, 64 | ebpf.LD_ABS_DW => { 65 | // load data ptr 66 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + @as(u64, @intCast(ix.imm)); 67 | // check mem bounds 68 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 64, stack); 69 | // return data ptr 70 | reg[0] = d; 71 | }, 72 | ebpf.LD_IND_B => { 73 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + reg[src] + @as(u64, @intCast(ix.imm)); 74 | // const d = mem.ptr + reg[src] + ix.imm; 75 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 8, stack); 76 | reg[0] = d; 77 | }, 78 | ebpf.LD_IND_H => { 79 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + reg[src] + @as(u64, @intCast(ix.imm)); 80 | // const d = mem.ptr + reg[src] + ix.imm; 81 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 16, stack); 82 | reg[0] = d; 83 | }, 84 | ebpf.LD_IND_W => { 85 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + reg[src] + @as(u64, @intCast(ix.imm)); 86 | // const d = mem.ptr + reg[src] + ix.imm; 87 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 32, stack); 88 | reg[0] = d; 89 | }, 90 | ebpf.LD_IND_DW => { 91 | const d = @as(u64, @intCast(@intFromPtr(mem.ptr))) + reg[src] + @as(u64, @intCast(ix.imm)); 92 | // const d = mem.ptr + reg[src] + ix.imm; 93 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 64, stack); 94 | reg[0] = d; 95 | }, 96 | ebpf.LD_DW_IMM => { 97 | // Ix ptr already incremented at start of loop 98 | const next_ix = try ebpf.Instruction.get_ix(program, pc); 99 | pc += 1; 100 | 101 | reg[dst] = @as(u64, @intCast(ix.imm)) + ((@as(u64, @intCast(next_ix.imm))) << 32); 102 | }, 103 | ebpf.LD_B_REG => { 104 | const d = reg[src] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8))); 105 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 1, stack); 106 | reg[dst] = d; 107 | }, 108 | 109 | ebpf.LD_H_REG => { 110 | const d = reg[src] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8))); 111 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 2, stack); 112 | reg[dst] = d; 113 | }, 114 | ebpf.LD_W_REG => { 115 | const d = reg[src] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8))); 116 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 4, stack); 117 | reg[dst] = d; 118 | }, 119 | ebpf.LD_DW_REG => { 120 | const d = reg[src] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8))); 121 | try check_mem(d, mbuff, mem, pc, MemAccessType.load, 8, stack); 122 | reg[dst] = d; 123 | }, 124 | 125 | // Store 126 | ebpf.ST_B_IMM => { 127 | const d: *u8 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8)))); 128 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 1, stack); 129 | d.* = @as(u8, @intCast(ix.imm)); 130 | }, 131 | ebpf.ST_H_IMM => { 132 | const d: *u16 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u16)))); 133 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 2, stack); 134 | d.* = @as(u16, @intCast(ix.imm)); 135 | }, 136 | ebpf.ST_W_IMM => { 137 | const d: *u32 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u32)))); 138 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 4, stack); 139 | d.* = @as(u32, @intCast(ix.imm)); 140 | }, 141 | ebpf.ST_DW_IMM => { 142 | const d: *u64 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u64)))); 143 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 8, stack); 144 | d.* = @as(u64, @intCast(ix.imm)); 145 | }, 146 | 147 | ebpf.ST_B_REG => { 148 | const d: *u8 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u8)))); 149 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 1, stack); 150 | d.* = @as(u8, @intCast(reg[src])); 151 | }, 152 | ebpf.ST_H_REG => { 153 | const d: *u16 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u16)))); 154 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 2, stack); 155 | d.* = @as(u16, @intCast(reg[src])); 156 | }, 157 | ebpf.ST_W_REG => { 158 | const d: *u32 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u32)))); 159 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 4, stack); 160 | d.* = @as(u32, @intCast(reg[src])); 161 | }, 162 | ebpf.ST_DW_REG => { 163 | const d: *u64 = @ptrFromInt(reg[dst] + @as(u64, @intCast(@as(isize, @intCast(ix.offset)) * @sizeOf(u64)))); 164 | try check_mem(@intFromPtr(d), mbuff, mem, pc, MemAccessType.store, 8, stack); 165 | d.* = @as(u64, @intCast(reg[src])); 166 | }, 167 | // ALU-64 168 | ebpf.ADD64_IMM => reg[dst] = @addWithOverflow(reg[dst], @as(u64, @intCast(ix.imm)))[0], 169 | ebpf.ADD64_REG => reg[dst] = @addWithOverflow(reg[dst], reg[src])[0], 170 | ebpf.SUB64_IMM => reg[dst] = @subWithOverflow(reg[dst], @as(u64, @intCast(ix.imm)))[0], 171 | ebpf.SUB64_REG => reg[dst] = @subWithOverflow(reg[dst], reg[src])[0], 172 | ebpf.MUL64_IMM => reg[dst] = @mulWithOverflow(reg[dst], @as(u64, @intCast(ix.imm)))[0], 173 | ebpf.MUL64_REG => reg[dst] = @mulWithOverflow(reg[dst], reg[src])[0], 174 | ebpf.DIV64_IMM => { 175 | if (ix.imm == 0) { 176 | reg[dst] = 0; 177 | } else { 178 | reg[dst] /= @as(u64, @intCast(ix.imm)); 179 | } 180 | }, 181 | ebpf.DIV64_REG => { 182 | if (reg[src] == 0) { 183 | reg[dst] = 0; 184 | } else { 185 | reg[dst] /= reg[src]; 186 | } 187 | }, 188 | ebpf.OR64_IMM => reg[dst] |= @as(u64, @intCast(ix.imm)), 189 | ebpf.OR64_REG => reg[dst] |= reg[src], 190 | ebpf.AND64_IMM => reg[dst] &= @as(u64, @intCast(ix.imm)), 191 | ebpf.AND64_REG => reg[dst] &= reg[src], 192 | ebpf.LSH64_IMM => { 193 | if (ix.imm < 0 or ix.imm >= @bitSizeOf(u64)) { 194 | return VmError.OperandExceedingBitsize; 195 | } 196 | 197 | reg[dst] <<= 198 | @as(u6, @intCast((@as(u64, @intCast(ix.imm)) & SHIFT_MASK_64))); 199 | }, 200 | ebpf.LSH64_REG => { 201 | if (reg[src] < 0 or reg[src] >= @bitSizeOf(u64)) { 202 | return VmError.OperandExceedingBitsize; 203 | } 204 | 205 | reg[dst] <<= 206 | @as(u6, @intCast((@as(u64, @intCast(reg[src])) & SHIFT_MASK_64))); 207 | }, 208 | ebpf.RSH64_IMM => { 209 | if (ix.imm < 0 or ix.imm >= @bitSizeOf(u64)) { 210 | return VmError.OperandExceedingBitsize; 211 | } 212 | 213 | reg[dst] >>= 214 | @as(u6, @intCast((@as(u64, @intCast(ix.imm)) & SHIFT_MASK_64))); 215 | }, 216 | ebpf.RSH64_REG => { 217 | if (reg[src] < 0 or reg[src] >= @bitSizeOf(u64)) { 218 | return VmError.OperandExceedingBitsize; 219 | } 220 | 221 | reg[dst] >>= 222 | @as(u6, @intCast((@as(u64, @intCast(reg[src])) & SHIFT_MASK_64))); 223 | }, 224 | ebpf.NEG64 => reg[dst] = -%@as(u64, @intCast(@as(i64, @intCast(reg[dst])))), 225 | ebpf.MOD64_IMM => { 226 | if (@as(u64, @intCast(ix.imm)) != 0) { 227 | reg[dst] %= @as(u64, @intCast(ix.imm)); 228 | } 229 | }, 230 | ebpf.MOD64_REG => { 231 | if (reg[src] != 0) { 232 | reg[dst] %= reg[src]; 233 | } 234 | }, 235 | ebpf.XOR64_IMM => reg[dst] ^= @as(u64, @intCast(ix.imm)), 236 | ebpf.XOR64_REG => reg[dst] ^= reg[src], 237 | ebpf.MOV64_IMM => { 238 | if (ix.imm >= 0) { 239 | reg[dst] = @as(u64, @intCast(ix.imm)); 240 | } else { 241 | reg[dst] = std.math.maxInt(u64); 242 | } 243 | }, 244 | ebpf.MOV64_REG => reg[dst] = reg[src], 245 | ebpf.ARSH64_IMM => { 246 | if (ix.imm < 0 or ix.imm >= @bitSizeOf(u64)) { 247 | return VmError.OperandExceedingBitsize; 248 | } 249 | 250 | reg[dst] >>= @as(u6, @intCast((@as(u64, @intCast(ix.imm)) & SHIFT_MASK_64))); 251 | }, 252 | //reg[dst] = (reg[dst] >> (@as(u64, @intCast(ix.imm)) & SHIFT_MASK_64)), 253 | ebpf.ARSH64_REG => { 254 | if (reg[src] < 0 or reg[src] >= @bitSizeOf(u64)) { 255 | return VmError.OperandExceedingBitsize; 256 | } 257 | 258 | reg[dst] >>= 259 | @as(u6, @intCast((@as(u64, @intCast(reg[src])) & SHIFT_MASK_64))); 260 | }, 261 | 262 | ebpf.LE => { 263 | switch (@as(u64, @intCast(ix.imm))) { 264 | 16 => reg[dst] = std.mem.nativeToLittle(u64, @as(u16, @intCast(reg[dst]))), 265 | 32 => reg[dst] = std.mem.nativeToLittle(u64, @as(u32, @intCast(reg[dst]))), 266 | 64 => reg[dst] = std.mem.nativeToLittle(u64, reg[dst]), 267 | else => { 268 | std.log.err("InvalidImm: {d}", .{ix.imm}); 269 | return VmError.InvalidImm; 270 | }, 271 | } 272 | }, 273 | ebpf.BE => { 274 | switch (@as(u64, @intCast(ix.imm))) { 275 | 16 => reg[dst] = std.mem.nativeToBig(u64, @as(u16, @intCast(reg[dst]))), 276 | 32 => reg[dst] = std.mem.nativeToBig(u64, @as(u32, @intCast(reg[dst]))), 277 | 64 => reg[dst] = std.mem.nativeToBig(u64, reg[dst]), 278 | else => { 279 | std.log.err("InvalidImm: {d}", .{ix.imm}); 280 | return VmError.InvalidImm; 281 | }, 282 | } 283 | }, 284 | 285 | // Branch 286 | ebpf.JA => jump(&pc, &ix), 287 | ebpf.JEQ_IMM => if (reg[dst] == @as(u64, @intCast(ix.imm))) { 288 | jump(&pc, &ix); 289 | }, 290 | ebpf.JEQ_REG => if (reg[dst] == reg[src]) { 291 | jump(&pc, &ix); 292 | }, 293 | ebpf.JGT_IMM => if (reg[dst] > @as(u64, @intCast(ix.imm))) { 294 | jump(&pc, &ix); 295 | }, 296 | ebpf.JGT_REG => if (reg[dst] > reg[src]) { 297 | jump(&pc, &ix); 298 | }, 299 | ebpf.JGE_IMM => if (reg[dst] >= @as(u64, @intCast(ix.imm))) { 300 | jump(&pc, &ix); 301 | }, 302 | ebpf.JGE_REG => if (reg[dst] >= reg[src]) { 303 | jump(&pc, &ix); 304 | }, 305 | ebpf.JLT_IMM => if (reg[dst] < @as(u64, @intCast(ix.imm))) { 306 | jump(&pc, &ix); 307 | }, 308 | ebpf.JLT_REG => if (reg[dst] < reg[src]) { 309 | jump(&pc, &ix); 310 | }, 311 | ebpf.JLE_IMM => if (reg[dst] <= @as(u64, @intCast(ix.imm))) { 312 | jump(&pc, &ix); 313 | }, 314 | ebpf.JLE_REG => if (reg[dst] <= reg[src]) { 315 | jump(&pc, &ix); 316 | }, 317 | ebpf.JSET_IMM => if (reg[dst] & @as(u64, @intCast(ix.imm)) != 0) { 318 | jump(&pc, &ix); 319 | }, 320 | ebpf.JSET_REG => if (reg[dst] & reg[src] != 0) { 321 | jump(&pc, &ix); 322 | }, 323 | ebpf.JNE_IMM => if (reg[dst] != @as(u64, @intCast(ix.imm))) { 324 | jump(&pc, &ix); 325 | }, 326 | ebpf.JNE_REG => if (reg[dst] != reg[src]) { 327 | jump(&pc, &ix); 328 | }, 329 | ebpf.JSGT_IMM => if (@as(i64, @bitCast(reg[dst])) > @as(i64, @intCast(ix.imm))) { 330 | jump(&pc, &ix); 331 | }, 332 | ebpf.JSGT_REG => if (@as(i64, @bitCast(reg[dst])) > @as(i64, @intCast(reg[src]))) { 333 | jump(&pc, &ix); 334 | }, 335 | ebpf.JSGE_IMM => if (@as(i64, @bitCast(reg[dst])) >= @as(i64, @intCast(ix.imm))) { 336 | jump(&pc, &ix); 337 | }, 338 | ebpf.JSGE_REG => if (@as(i64, @bitCast(reg[dst])) >= @as(i64, @intCast(reg[src]))) { 339 | jump(&pc, &ix); 340 | }, 341 | ebpf.JSLT_IMM => if (@as(i64, @bitCast(reg[dst])) < @as(i64, @intCast(ix.imm))) { 342 | jump(&pc, &ix); 343 | }, 344 | ebpf.JSLT_REG => if (@as(i64, @intCast(reg[dst])) < @as(i64, @intCast(reg[src]))) { 345 | jump(&pc, &ix); 346 | }, 347 | ebpf.JSLE_IMM => if (@as(i64, @intCast(reg[dst])) <= @as(i64, @intCast(ix.imm))) { 348 | jump(&pc, &ix); 349 | }, 350 | ebpf.JSLE_REG => if (@as(i64, @bitCast(reg[dst])) <= @as(i64, @intCast(reg[src]))) { 351 | jump(&pc, &ix); 352 | }, 353 | ebpf.CALL => { 354 | if (syscalls_map.contains(@as(usize, @intCast(ix.imm)))) { 355 | const call = syscalls_map.get(@as(usize, @intCast(ix.imm))).?; 356 | reg[0] = call(reg[1], reg[2], reg[3], reg[4], reg[5]); 357 | } else { 358 | std.log.err("UnknownSyscall: {d}", .{ix.imm}); 359 | return VmError.UnknownSyscall; 360 | } 361 | }, 362 | ebpf.EXIT => return reg[0], 363 | else => { 364 | std.log.err("InvalidOpCode: {d}", .{ix.op}); 365 | return VmError.InvalidOpCode; 366 | }, 367 | } 368 | 369 | if (builtin.is_test) { 370 | helpers.debug_print_vm_state(®, pc, src, dst); 371 | // std.time.sleep(10000000000); 372 | } 373 | } 374 | return 0; 375 | } 376 | const VmError = error{ OutOfBoundsMemoryAccess, InvalidInstructionAddress, InvalidOpCode, OperandExceedingBitsize, InvalidImm, UnknownSyscall }; 377 | 378 | fn check_mem(addr: u64, mbuf: []const u8, mem: []const u8, inst_ptr: u64, op_type: MemAccessType, len: u64, stack: []const u8) !void { 379 | _ = op_type; 380 | _ = inst_ptr; 381 | // Check if new memory being loaded or stored is trying to access memory out of bounds. 382 | if (addr + len <= @intFromPtr(mem.ptr) + mem.len and addr <= @intFromPtr(mem.ptr)) { 383 | return; 384 | } 385 | if (addr + len <= @intFromPtr(mbuf.ptr + mbuf.len) and addr <= @intFromPtr(mbuf.ptr)) { 386 | return; 387 | } 388 | if (@intFromPtr(stack.ptr) <= addr and addr + len <= @intFromPtr(stack.ptr) + stack.len) { 389 | return; 390 | } 391 | return VmError.OutOfBoundsMemoryAccess; 392 | } 393 | 394 | pub fn jump(pc: *usize, ix: *const ebpf.Instruction) void { 395 | pc.* = @as(usize, @intCast(@as(i16, @intCast(pc.*)) + ix.offset)); 396 | } 397 | 398 | // fn nextLine(reader: anytype, buffer: []u8) !?[]const u8 { 399 | // } 400 | -------------------------------------------------------------------------------- /src/ebpf.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | /// Reference: https://github.com/iovisor/bpf-docs/blob/master/md 4 | /// Maximum number of instructions in an program. 5 | pub const PROG_MAX_INSNS: usize = 1000000; 6 | /// Size of an instructions, in bytes. 7 | pub const INSN_SIZE: usize = 8; 8 | /// Maximum size of an program, in bytes. 9 | pub const PROG_MAX_SIZE: usize = PROG_MAX_INSNS * INSN_SIZE; 10 | /// Stack for the stack, in bytes. 11 | pub const STACK_SIZE: usize = 512; 12 | 13 | // op codes. 14 | // See also https://www.kernel.org/doc/Documentation/networking/filter.txt 15 | 16 | // Three least significant bits are operation class: 17 | /// BPF operation class: load from immediate. 18 | pub const BPF_LD: u8 = 0x00; 19 | /// BPF operation class: load from register. 20 | pub const BPF_LDX: u8 = 0x01; 21 | /// BPF operation class: store immediate. 22 | pub const BPF_ST: u8 = 0x02; 23 | /// BPF operation class: store value from register. 24 | pub const BPF_STX: u8 = 0x03; 25 | /// BPF operation class: 32 bits arithmetic operation. 26 | pub const BPF_ALU: u8 = 0x04; 27 | /// BPF operation class: jump (64-bit wide operands for comparisons). 28 | pub const BPF_JMP: u8 = 0x05; 29 | /// BPF operation class: jump (32-bit wide operands for comparisons). 30 | pub const BPF_JMP32: u8 = 0x06; 31 | // [ class 6 unused, reserved for future use ] 32 | /// BPF operation class: 64 bits arithmetic operation. 33 | pub const BPF_ALU64: u8 = 0x07; 34 | 35 | // For load and store instructions: 36 | // +------------+--------+------------+ 37 | // | 3 bits | 2 bits | 3 bits | 38 | // | mode | size | insn class | 39 | // +------------+--------+------------+ 40 | // (MSB) (LSB) 41 | 42 | // Size modifiers: 43 | /// BPF size modifier: word (4 bytes). 44 | pub const BPF_W: u8 = 0x00; 45 | /// BPF size modifier: half-word (2 bytes). 46 | pub const BPF_H: u8 = 0x08; 47 | /// BPF size modifier: byte (1 byte). 48 | pub const BPF_B: u8 = 0x10; 49 | /// BPF size modifier: double word (8 bytes). 50 | pub const BPF_DW: u8 = 0x18; 51 | 52 | // Mode modifiers: 53 | /// BPF mode modifier: immediate value. 54 | pub const BPF_IMM: u8 = 0x00; 55 | /// BPF mode modifier: absolute load. 56 | pub const BPF_ABS: u8 = 0x20; 57 | /// BPF mode modifier: indirect load. 58 | pub const BPF_IND: u8 = 0x40; 59 | /// BPF mode modifier: load from / store to memory. 60 | pub const BPF_MEM: u8 = 0x60; 61 | // [ 0x80 reserved ] 62 | // [ 0xa0 reserved ] 63 | /// BPF mode modifier: exclusive add. 64 | pub const BPF_XADD: u8 = 0xc0; 65 | 66 | // For arithmetic (BPF_ALU/BPF_ALU64) and jump (BPF_JMP) instructions: 67 | // +----------------+--------+--------+ 68 | // | 4 bits |1 b.| 3 bits | 69 | // | operation code | src| insn class | 70 | // +----------------+----+------------+ 71 | // (MSB) (LSB) 72 | 73 | // Source modifiers: 74 | /// BPF source operand modifier: 32-bit immediate value. 75 | pub const BPF_K: u8 = 0x00; 76 | /// BPF source operand modifier: `src` register. 77 | pub const BPF_X: u8 = 0x08; 78 | 79 | // Operation codes -- BPF_ALU or BPF_ALU64 classes: 80 | /// BPF ALU/ALU64 operation code: addition. 81 | pub const BPF_ADD: u8 = 0x00; 82 | /// BPF ALU/ALU64 operation code: subtraction. 83 | pub const BPF_SUB: u8 = 0x10; 84 | /// BPF ALU/ALU64 operation code: multiplication. 85 | pub const BPF_MUL: u8 = 0x20; 86 | /// BPF ALU/ALU64 operation code: division. 87 | pub const BPF_DIV: u8 = 0x30; 88 | /// BPF ALU/ALU64 operation code: or. 89 | pub const BPF_OR: u8 = 0x40; 90 | /// BPF ALU/ALU64 operation code: and. 91 | pub const BPF_AND: u8 = 0x50; 92 | /// BPF ALU/ALU64 operation code: left shift. 93 | pub const BPF_LSH: u8 = 0x60; 94 | /// BPF ALU/ALU64 operation code: right shift. 95 | pub const BPF_RSH: u8 = 0x70; 96 | /// BPF ALU/ALU64 operation code: negation. 97 | pub const BPF_NEG: u8 = 0x80; 98 | /// BPF ALU/ALU64 operation code: modulus. 99 | pub const BPF_MOD: u8 = 0x90; 100 | /// BPF ALU/ALU64 operation code: exclusive or. 101 | pub const BPF_XOR: u8 = 0xa0; 102 | /// BPF ALU/ALU64 operation code: move. 103 | pub const BPF_MOV: u8 = 0xb0; 104 | /// BPF ALU/ALU64 operation code: sign extending right shift. 105 | pub const BPF_ARSH: u8 = 0xc0; 106 | /// BPF ALU/ALU64 operation code: endianness conversion. 107 | pub const BPF_END: u8 = 0xd0; 108 | 109 | // Operation codes -- BPF_JMP or BPF_JMP32 classes: 110 | /// BPF JMP operation code: jump. 111 | pub const BPF_JA: u8 = 0x00; 112 | /// BPF JMP operation code: jump if equal. 113 | pub const BPF_JEQ: u8 = 0x10; 114 | /// BPF JMP operation code: jump if greater than. 115 | pub const BPF_JGT: u8 = 0x20; 116 | /// BPF JMP operation code: jump if greater or equal. 117 | pub const BPF_JGE: u8 = 0x30; 118 | /// BPF JMP operation code: jump if `src` & `reg`. 119 | pub const BPF_JSET: u8 = 0x40; 120 | /// BPF JMP operation code: jump if not equal. 121 | pub const BPF_JNE: u8 = 0x50; 122 | /// BPF JMP operation code: jump if greater than (signed). 123 | pub const BPF_JSGT: u8 = 0x60; 124 | /// BPF JMP operation code: jump if greater or equal (signed). 125 | pub const BPF_JSGE: u8 = 0x70; 126 | /// BPF JMP operation code: helper function call. 127 | pub const BPF_CALL: u8 = 0x80; 128 | /// BPF JMP operation code: return from program. 129 | pub const BPF_EXIT: u8 = 0x90; 130 | /// BPF JMP operation code: jump if lower than. 131 | pub const BPF_JLT: u8 = 0xa0; 132 | /// BPF JMP operation code: jump if lower or equal. 133 | pub const BPF_JLE: u8 = 0xb0; 134 | /// BPF JMP operation code: jump if lower than (signed). 135 | pub const BPF_JSLT: u8 = 0xc0; 136 | /// BPF JMP operation code: jump if lower or equal (signed). 137 | pub const BPF_JSLE: u8 = 0xd0; 138 | 139 | // Op codes 140 | // (Following operation names are not “official”, but may be proper to rbpf; Linux kernel only 141 | // combines above flags and does not attribute a name per operation.) 142 | 143 | /// BPF opcode: `ldabsb src, dst, imm`. 144 | pub const LD_ABS_B: u8 = BPF_LD | BPF_ABS | BPF_B; 145 | /// BPF opcode: `ldabsh src, dst, imm`. 146 | pub const LD_ABS_H: u8 = BPF_LD | BPF_ABS | BPF_H; 147 | /// BPF opcode: `ldabsw src, dst, imm`. 148 | pub const LD_ABS_W: u8 = BPF_LD | BPF_ABS | BPF_W; 149 | /// BPF opcode: `ldabsdw src, dst, imm`. 150 | pub const LD_ABS_DW: u8 = BPF_LD | BPF_ABS | BPF_DW; 151 | /// BPF opcode: `ldindb src, dst, imm`. 152 | pub const LD_IND_B: u8 = BPF_LD | BPF_IND | BPF_B; 153 | /// BPF opcode: `ldindh src, dst, imm`. 154 | pub const LD_IND_H: u8 = BPF_LD | BPF_IND | BPF_H; 155 | /// BPF opcode: `ldindw src, dst, imm`. 156 | pub const LD_IND_W: u8 = BPF_LD | BPF_IND | BPF_W; 157 | /// BPF opcode: `ldinddw src, dst, imm`. 158 | pub const LD_IND_DW: u8 = BPF_LD | BPF_IND | BPF_DW; 159 | 160 | /// BPF opcode: `lddw dst, imm` /// `dst = imm`. 161 | pub const LD_DW_IMM: u8 = BPF_LD | BPF_IMM | BPF_DW; 162 | /// BPF opcode: `ldxb dst, [src + off]` /// `dst = (src + off) as u8`. 163 | pub const LD_B_REG: u8 = BPF_LDX | BPF_MEM | BPF_B; 164 | /// BPF opcode: `ldxh dst, [src + off]` /// `dst = (src + off) as u16`. 165 | pub const LD_H_REG: u8 = BPF_LDX | BPF_MEM | BPF_H; 166 | /// BPF opcode: `ldxw dst, [src + off]` /// `dst = (src + off) as u32`. 167 | pub const LD_W_REG: u8 = BPF_LDX | BPF_MEM | BPF_W; 168 | /// BPF opcode: `ldxdw dst, [src + off]` /// `dst = (src + off) as u64`. 169 | pub const LD_DW_REG: u8 = BPF_LDX | BPF_MEM | BPF_DW; 170 | /// BPF opcode: `stb [dst + off], imm` /// `(dst + offset) as u8 = imm`. 171 | pub const ST_B_IMM: u8 = BPF_ST | BPF_MEM | BPF_B; 172 | /// BPF opcode: `sth [dst + off], imm` /// `(dst + offset) as u16 = imm`. 173 | pub const ST_H_IMM: u8 = BPF_ST | BPF_MEM | BPF_H; 174 | /// BPF opcode: `stw [dst + off], imm` /// `(dst + offset) as u32 = imm`. 175 | pub const ST_W_IMM: u8 = BPF_ST | BPF_MEM | BPF_W; 176 | /// BPF opcode: `stdw [dst + off], imm` /// `(dst + offset) as u64 = imm`. 177 | pub const ST_DW_IMM: u8 = BPF_ST | BPF_MEM | BPF_DW; 178 | /// BPF opcode: `stxb [dst + off], src` /// `(dst + offset) as u8 = src`. 179 | pub const ST_B_REG: u8 = BPF_STX | BPF_MEM | BPF_B; 180 | /// BPF opcode: `stxh [dst + off], src` /// `(dst + offset) as u16 = src`. 181 | pub const ST_H_REG: u8 = BPF_STX | BPF_MEM | BPF_H; 182 | /// BPF opcode: `stxw [dst + off], src` /// `(dst + offset) as u32 = src`. 183 | pub const ST_W_REG: u8 = BPF_STX | BPF_MEM | BPF_W; 184 | /// BPF opcode: `stxdw [dst + off], src` /// `(dst + offset) as u64 = src`. 185 | pub const ST_DW_REG: u8 = BPF_STX | BPF_MEM | BPF_DW; 186 | 187 | /// BPF opcode: `stxxaddw [dst + off], src`. 188 | pub const ST_W_XADD: u8 = BPF_STX | BPF_XADD | BPF_W; 189 | /// BPF opcode: `stxxadddw [dst + off], src`. 190 | pub const ST_DW_XADD: u8 = BPF_STX | BPF_XADD | BPF_DW; 191 | 192 | /// BPF opcode: `add32 dst, imm` /// `dst += imm`. 193 | pub const ADD32_IMM: u8 = BPF_ALU | BPF_K | BPF_ADD; 194 | /// BPF opcode: `add32 dst, src` /// `dst += src`. 195 | pub const ADD32_REG: u8 = BPF_ALU | BPF_X | BPF_ADD; 196 | /// BPF opcode: `sub32 dst, imm` /// `dst -= imm`. 197 | pub const SUB32_IMM: u8 = BPF_ALU | BPF_K | BPF_SUB; 198 | /// BPF opcode: `sub32 dst, src` /// `dst -= src`. 199 | pub const SUB32_REG: u8 = BPF_ALU | BPF_X | BPF_SUB; 200 | /// BPF opcode: `mul32 dst, imm` /// `dst *= imm`. 201 | pub const MUL32_IMM: u8 = BPF_ALU | BPF_K | BPF_MUL; 202 | /// BPF opcode: `mul32 dst, src` /// `dst *= src`. 203 | pub const MUL32_REG: u8 = BPF_ALU | BPF_X | BPF_MUL; 204 | /// BPF opcode: `div32 dst, imm` /// `dst /= imm`. 205 | pub const DIV32_IMM: u8 = BPF_ALU | BPF_K | BPF_DIV; 206 | /// BPF opcode: `div32 dst, src` /// `dst /= src`. 207 | pub const DIV32_REG: u8 = BPF_ALU | BPF_X | BPF_DIV; 208 | /// BPF opcode: `or32 dst, imm` /// `dst |= imm`. 209 | pub const OR32_IMM: u8 = BPF_ALU | BPF_K | BPF_OR; 210 | /// BPF opcode: `or32 dst, src` /// `dst |= src`. 211 | pub const OR32_REG: u8 = BPF_ALU | BPF_X | BPF_OR; 212 | /// BPF opcode: `and32 dst, imm` /// `dst &= imm`. 213 | pub const AND32_IMM: u8 = BPF_ALU | BPF_K | BPF_AND; 214 | /// BPF opcode: `and32 dst, src` /// `dst &= src`. 215 | pub const AND32_REG: u8 = BPF_ALU | BPF_X | BPF_AND; 216 | /// BPF opcode: `lsh32 dst, imm` /// `dst <<= imm`. 217 | pub const LSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_LSH; 218 | /// BPF opcode: `lsh32 dst, src` /// `dst <<= src`. 219 | pub const LSH32_REG: u8 = BPF_ALU | BPF_X | BPF_LSH; 220 | /// BPF opcode: `rsh32 dst, imm` /// `dst >>= imm`. 221 | pub const RSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_RSH; 222 | /// BPF opcode: `rsh32 dst, src` /// `dst >>= src`. 223 | pub const RSH32_REG: u8 = BPF_ALU | BPF_X | BPF_RSH; 224 | /// BPF opcode: `neg32 dst` /// `dst = -dst`. 225 | pub const NEG32: u8 = BPF_ALU | BPF_NEG; 226 | /// BPF opcode: `mod32 dst, imm` /// `dst %= imm`. 227 | pub const MOD32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOD; 228 | /// BPF opcode: `mod32 dst, src` /// `dst %= src`. 229 | pub const MOD32_REG: u8 = BPF_ALU | BPF_X | BPF_MOD; 230 | /// BPF opcode: `xor32 dst, imm` /// `dst ^= imm`. 231 | pub const XOR32_IMM: u8 = BPF_ALU | BPF_K | BPF_XOR; 232 | /// BPF opcode: `xor32 dst, src` /// `dst ^= src`. 233 | pub const XOR32_REG: u8 = BPF_ALU | BPF_X | BPF_XOR; 234 | /// BPF opcode: `mov32 dst, imm` /// `dst = imm`. 235 | pub const MOV32_IMM: u8 = BPF_ALU | BPF_K | BPF_MOV; 236 | /// BPF opcode: `mov32 dst, src` /// `dst = src`. 237 | pub const MOV32_REG: u8 = BPF_ALU | BPF_X | BPF_MOV; 238 | /// BPF opcode: `arsh32 dst, imm` /// `dst >>= imm (arithmetic)`. 239 | /// 240 | /// 241 | pub const ARSH32_IMM: u8 = BPF_ALU | BPF_K | BPF_ARSH; 242 | /// BPF opcode: `arsh32 dst, src` /// `dst >>= src (arithmetic)`. 243 | /// 244 | /// 245 | pub const ARSH32_REG: u8 = BPF_ALU | BPF_X | BPF_ARSH; 246 | 247 | /// BPF opcode: `le dst` /// `dst = htole(dst), with imm in {16, 32, 64}`. 248 | pub const LE: u8 = BPF_ALU | BPF_K | BPF_END; 249 | /// BPF opcode: `be dst` /// `dst = htobe(dst), with imm in {16, 32, 64}`. 250 | pub const BE: u8 = BPF_ALU | BPF_X | BPF_END; 251 | 252 | /// BPF opcode: `add64 dst, imm` /// `dst += imm`. 253 | pub const ADD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ADD; 254 | /// BPF opcode: `add64 dst, src` /// `dst += src`. 255 | pub const ADD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ADD; 256 | /// BPF opcode: `sub64 dst, imm` /// `dst -= imm`. 257 | pub const SUB64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_SUB; 258 | /// BPF opcode: `sub64 dst, src` /// `dst -= src`. 259 | pub const SUB64_REG: u8 = BPF_ALU64 | BPF_X | BPF_SUB; 260 | /// BPF opcode: `div64 dst, imm` /// `dst /= imm`. 261 | pub const MUL64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MUL; 262 | /// BPF opcode: `div64 dst, src` /// `dst /= src`. 263 | pub const MUL64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MUL; 264 | /// BPF opcode: `div64 dst, imm` /// `dst /= imm`. 265 | pub const DIV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_DIV; 266 | /// BPF opcode: `div64 dst, src` /// `dst /= src`. 267 | pub const DIV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_DIV; 268 | /// BPF opcode: `or64 dst, imm` /// `dst |= imm`. 269 | pub const OR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_OR; 270 | /// BPF opcode: `or64 dst, src` /// `dst |= src`. 271 | pub const OR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_OR; 272 | /// BPF opcode: `and64 dst, imm` /// `dst &= imm`. 273 | pub const AND64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_AND; 274 | /// BPF opcode: `and64 dst, src` /// `dst &= src`. 275 | pub const AND64_REG: u8 = BPF_ALU64 | BPF_X | BPF_AND; 276 | /// BPF opcode: `lsh64 dst, imm` /// `dst <<= imm`. 277 | pub const LSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_LSH; 278 | /// BPF opcode: `lsh64 dst, src` /// `dst <<= src`. 279 | pub const LSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_LSH; 280 | /// BPF opcode: `rsh64 dst, imm` /// `dst >>= imm`. 281 | pub const RSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_RSH; 282 | /// BPF opcode: `rsh64 dst, src` /// `dst >>= src`. 283 | pub const RSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_RSH; 284 | /// BPF opcode: `neg64 dst, imm` /// `dst = -dst`. 285 | pub const NEG64: u8 = BPF_ALU64 | BPF_NEG; 286 | /// BPF opcode: `mod64 dst, imm` /// `dst %= imm`. 287 | pub const MOD64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOD; 288 | /// BPF opcode: `mod64 dst, src` /// `dst %= src`. 289 | pub const MOD64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOD; 290 | /// BPF opcode: `xor64 dst, imm` /// `dst ^= imm`. 291 | pub const XOR64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_XOR; 292 | /// BPF opcode: `xor64 dst, src` /// `dst ^= src`. 293 | pub const XOR64_REG: u8 = BPF_ALU64 | BPF_X | BPF_XOR; 294 | /// BPF opcode: `mov64 dst, imm` /// `dst = imm`. 295 | pub const MOV64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_MOV; 296 | /// BPF opcode: `mov64 dst, src` /// `dst = src`. 297 | pub const MOV64_REG: u8 = BPF_ALU64 | BPF_X | BPF_MOV; 298 | /// BPF opcode: `arsh64 dst, imm` /// `dst >>= imm (arithmetic)`. 299 | /// 300 | /// 301 | pub const ARSH64_IMM: u8 = BPF_ALU64 | BPF_K | BPF_ARSH; 302 | /// BPF opcode: `arsh64 dst, src` /// `dst >>= src (arithmetic)`. 303 | /// 304 | /// 305 | pub const ARSH64_REG: u8 = BPF_ALU64 | BPF_X | BPF_ARSH; 306 | 307 | /// BPF opcode: `ja +off` /// `PC += off`. 308 | pub const JA: u8 = BPF_JMP | BPF_JA; 309 | /// BPF opcode: `jeq dst, imm, +off` /// `PC += off if dst == imm`. 310 | pub const JEQ_IMM: u8 = BPF_JMP | BPF_K | BPF_JEQ; 311 | /// BPF opcode: `jeq dst, src, +off` /// `PC += off if dst == src`. 312 | pub const JEQ_REG: u8 = BPF_JMP | BPF_X | BPF_JEQ; 313 | /// BPF opcode: `jgt dst, imm, +off` /// `PC += off if dst > imm`. 314 | pub const JGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JGT; 315 | /// BPF opcode: `jgt dst, src, +off` /// `PC += off if dst > src`. 316 | pub const JGT_REG: u8 = BPF_JMP | BPF_X | BPF_JGT; 317 | /// BPF opcode: `jge dst, imm, +off` /// `PC += off if dst >= imm`. 318 | pub const JGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JGE; 319 | /// BPF opcode: `jge dst, src, +off` /// `PC += off if dst >= src`. 320 | pub const JGE_REG: u8 = BPF_JMP | BPF_X | BPF_JGE; 321 | /// BPF opcode: `jlt dst, imm, +off` /// `PC += off if dst < imm`. 322 | pub const JLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JLT; 323 | /// BPF opcode: `jlt dst, src, +off` /// `PC += off if dst < src`. 324 | pub const JLT_REG: u8 = BPF_JMP | BPF_X | BPF_JLT; 325 | /// BPF opcode: `jle dst, imm, +off` /// `PC += off if dst <= imm`. 326 | pub const JLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JLE; 327 | /// BPF opcode: `jle dst, src, +off` /// `PC += off if dst <= src`. 328 | pub const JLE_REG: u8 = BPF_JMP | BPF_X | BPF_JLE; 329 | /// BPF opcode: `jset dst, imm, +off` /// `PC += off if dst & imm`. 330 | pub const JSET_IMM: u8 = BPF_JMP | BPF_K | BPF_JSET; 331 | /// BPF opcode: `jset dst, src, +off` /// `PC += off if dst & src`. 332 | pub const JSET_REG: u8 = BPF_JMP | BPF_X | BPF_JSET; 333 | /// BPF opcode: `jne dst, imm, +off` /// `PC += off if dst != imm`. 334 | pub const JNE_IMM: u8 = BPF_JMP | BPF_K | BPF_JNE; 335 | /// BPF opcode: `jne dst, src, +off` /// `PC += off if dst != src`. 336 | pub const JNE_REG: u8 = BPF_JMP | BPF_X | BPF_JNE; 337 | /// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if dst > imm (signed)`. 338 | pub const JSGT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGT; 339 | /// BPF opcode: `jsgt dst, src, +off` /// `PC += off if dst > src (signed)`. 340 | pub const JSGT_REG: u8 = BPF_JMP | BPF_X | BPF_JSGT; 341 | /// BPF opcode: `jsge dst, imm, +off` /// `PC += off if dst >= imm (signed)`. 342 | pub const JSGE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSGE; 343 | /// BPF opcode: `jsge dst, src, +off` /// `PC += off if dst >= src (signed)`. 344 | pub const JSGE_REG: u8 = BPF_JMP | BPF_X | BPF_JSGE; 345 | /// BPF opcode: `jslt dst, imm, +off` /// `PC += off if dst < imm (signed)`. 346 | pub const JSLT_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLT; 347 | /// BPF opcode: `jslt dst, src, +off` /// `PC += off if dst < src (signed)`. 348 | pub const JSLT_REG: u8 = BPF_JMP | BPF_X | BPF_JSLT; 349 | /// BPF opcode: `jsle dst, imm, +off` /// `PC += off if dst <= imm (signed)`. 350 | pub const JSLE_IMM: u8 = BPF_JMP | BPF_K | BPF_JSLE; 351 | /// BPF opcode: `jsle dst, src, +off` /// `PC += off if dst <= src (signed)`. 352 | pub const JSLE_REG: u8 = BPF_JMP | BPF_X | BPF_JSLE; 353 | 354 | /// BPF opcode: `jeq dst, imm, +off` /// `PC += off if (dst as u32) == imm`. 355 | pub const JEQ_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JEQ; 356 | /// BPF opcode: `jeq dst, src, +off` /// `PC += off if (dst as u32) == (src as u32)`. 357 | pub const JEQ_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JEQ; 358 | /// BPF opcode: `jgt dst, imm, +off` /// `PC += off if (dst as u32) > imm`. 359 | pub const JGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGT; 360 | /// BPF opcode: `jgt dst, src, +off` /// `PC += off if (dst as u32) > (src as u32)`. 361 | pub const JGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGT; 362 | /// BPF opcode: `jge dst, imm, +off` /// `PC += off if (dst as u32) >= imm`. 363 | pub const JGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JGE; 364 | /// BPF opcode: `jge dst, src, +off` /// `PC += off if (dst as u32) >= (src as u32)`. 365 | pub const JGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JGE; 366 | /// BPF opcode: `jlt dst, imm, +off` /// `PC += off if (dst as u32) < imm`. 367 | pub const JLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLT; 368 | /// BPF opcode: `jlt dst, src, +off` /// `PC += off if (dst as u32) < (src as u32)`. 369 | pub const JLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLT; 370 | /// BPF opcode: `jle dst, imm, +off` /// `PC += off if (dst as u32) <= imm`. 371 | pub const JLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JLE; 372 | /// BPF opcode: `jle dst, src, +off` /// `PC += off if (dst as u32) <= (src as u32)`. 373 | pub const JLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JLE; 374 | /// BPF opcode: `jset dst, imm, +off` /// `PC += off if (dst as u32) & imm`. 375 | pub const JSET_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSET; 376 | /// BPF opcode: `jset dst, src, +off` /// `PC += off if (dst as u32) & (src as u32)`. 377 | pub const JSET_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSET; 378 | /// BPF opcode: `jne dst, imm, +off` /// `PC += off if (dst as u32) != imm`. 379 | pub const JNE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JNE; 380 | /// BPF opcode: `jne dst, src, +off` /// `PC += off if (dst as u32) != (src as u32)`. 381 | pub const JNE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JNE; 382 | /// BPF opcode: `jsgt dst, imm, +off` /// `PC += off if (dst as i32) > imm (signed)`. 383 | pub const JSGT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGT; 384 | /// BPF opcode: `jsgt dst, src, +off` /// `PC += off if (dst as i32) > (src as i32) (signed)`. 385 | pub const JSGT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGT; 386 | /// BPF opcode: `jsge dst, imm, +off` /// `PC += off if (dst as i32) >= imm (signed)`. 387 | pub const JSGE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSGE; 388 | /// BPF opcode: `jsge dst, src, +off` /// `PC += off if (dst as i32) >= (src as i32) (signed)`. 389 | pub const JSGE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSGE; 390 | /// BPF opcode: `jslt dst, imm, +off` /// `PC += off if (dst as i32) < imm (signed)`. 391 | pub const JSLT_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLT; 392 | /// BPF opcode: `jslt dst, src, +off` /// `PC += off if (dst as i32) < (src as i32) (signed)`. 393 | pub const JSLT_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLT; 394 | /// BPF opcode: `jsle dst, imm, +off` /// `PC += off if (dst as i32) <= imm (signed)`. 395 | pub const JSLE_IMM32: u8 = BPF_JMP32 | BPF_K | BPF_JSLE; 396 | /// BPF opcode: `jsle dst, src, +off` /// `PC += off if (dst as i32) <= (src as i32) (signed)`. 397 | pub const JSLE_REG32: u8 = BPF_JMP32 | BPF_X | BPF_JSLE; 398 | 399 | /// BPF opcode: `call imm` /// helper function call to helper with key `imm`. 400 | pub const CALL: u8 = BPF_JMP | BPF_CALL; 401 | /// BPF opcode: tail call. 402 | pub const TAIL_CALL: u8 = BPF_JMP | BPF_X | BPF_CALL; 403 | /// BPF opcode: `exit` /// `return r0`. 404 | pub const EXIT: u8 = BPF_JMP | BPF_EXIT; 405 | 406 | // Used in JIT 407 | /// Mask to extract the operation class from an operation code. 408 | pub const BPF_CLS_MASK: u8 = 0x07; 409 | /// Mask to extract the arithmetic operation code from an instruction operation code. 410 | pub const BPF_ALU_OP_MASK: u8 = 0xf0; 411 | 412 | pub const Syscall = *const fn (a1: u64, a2: u64, a3: u64, a4: u64, a5: u64) u64; 413 | 414 | pub const Instruction = packed struct { 415 | op: u8, 416 | dst: u8, 417 | src: u8, 418 | offset: i16, 419 | // imm stores the data that needs to be stored in reg for ex. 420 | imm: i32, 421 | pub fn get_ix(program: []const u8, pc: usize) !Instruction { 422 | if ((pc + 1) * INSN_SIZE > program.len) { 423 | return InstructionError.InvalidInstructionSize; 424 | } 425 | return Instruction{ 426 | .op = program[INSN_SIZE * pc], 427 | .dst = program[INSN_SIZE * pc + 1] & 0x0F, 428 | .src = (program[INSN_SIZE * pc + 1] & 0xF0) >> 4, 429 | .offset = std.mem.nativeToLittle(i16, std.mem.bytesToValue(i16, program[(INSN_SIZE * pc + 2)..])), 430 | .imm = std.mem.nativeToLittle(i32, std.mem.bytesToValue(i32, program[(INSN_SIZE * pc + 4)..])), 431 | }; 432 | } 433 | 434 | pub fn to_array(self: *const Instruction) [INSN_SIZE]u8 { 435 | return [_]u8{ 436 | self.op, 437 | @as(u8, self.src) << 4 | self.dst, 438 | @intCast(self.offset & 0xFF), 439 | @intCast((self.offset >> 8) & 0xFF), 440 | @intCast(self.imm & 0xFF), 441 | @intCast((self.imm >> 8) & 0xFF), 442 | @intCast((self.imm >> 16) & 0xFF), 443 | @intCast((self.imm >> 24) & 0xFF), 444 | }; 445 | } 446 | }; 447 | 448 | pub const InstructionError = error{ 449 | InvalidInstructionSize, 450 | }; 451 | -------------------------------------------------------------------------------- /src/disassembler.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ebpf = @import("ebpf.zig"); 3 | 4 | // Possible errors that can occur during disassembly 5 | pub const DisassemblerError = error{ 6 | InvalidProgram, 7 | UnknownOpcode, 8 | InvalidByteswapSize, 9 | OutOfMemory, 10 | InvalidInstructionSize, 11 | }; 12 | 13 | // Struct representing high-level eBPF instruction 14 | pub const HLInsn = struct { 15 | opcode: u8, 16 | name: []const u8, 17 | description: []const u8, 18 | dst: u8, 19 | src: u8, 20 | off: i16, 21 | imm: i64, 22 | }; 23 | 24 | // Helper functions to format instruction strings 25 | 26 | fn ldabs_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 27 | return try std.fmt.allocPrint(allocator, "{s} 0x{x}", .{ name, @as(u32, @bitCast(insn.imm)) }); 28 | } 29 | 30 | fn ldind_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 31 | return try std.fmt.allocPrint(allocator, "{s} r{d}, 0x{x}", .{ name, insn.dst, @as(u32, @bitCast(insn.imm)) }); 32 | } 33 | 34 | fn ld_reg_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 35 | return try std.fmt.allocPrint(allocator, "{s} r{d}, [r{d}+0x{x}]", .{ 36 | name, 37 | insn.dst, 38 | insn.src, 39 | @as(u16, @bitCast(insn.offset)), 40 | }); 41 | } 42 | 43 | fn ld_st_imm_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 44 | return try std.fmt.allocPrint(allocator, "{s} [r{d}+0x{x}], 0x{x}", .{ 45 | name, 46 | insn.dst, 47 | @as(u16, @bitCast(insn.offset)), 48 | @as(u32, @bitCast(insn.imm)), 49 | }); 50 | } 51 | 52 | fn st_reg_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 53 | return try std.fmt.allocPrint(allocator, "{s} [r{d}+0x{x}], r{d}", .{ 54 | name, 55 | insn.dst, 56 | @as(u16, @bitCast(insn.offset)), 57 | insn.src, 58 | }); 59 | } 60 | 61 | fn alu_imm_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 62 | const imm = @as(u32, @bitCast(insn.imm)); 63 | return try std.fmt.allocPrint(allocator, "{s} r{d}, 0x{x}", .{ name, insn.dst, imm }); 64 | } 65 | 66 | fn alu_reg_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 67 | return try std.fmt.allocPrint(allocator, "{s} r{d}, r{d}", .{ name, insn.dst, insn.src }); 68 | } 69 | 70 | fn byteswap_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 71 | const size: u32 = switch (insn.imm) { 72 | 16 => 16, 73 | 32 => 32, 74 | 64 => 64, 75 | else => return DisassemblerError.InvalidByteswapSize, 76 | }; 77 | return try std.fmt.allocPrint(allocator, "{s}{d} r{d}", .{ name, size, insn.dst }); 78 | } 79 | 80 | fn jmp_imm_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 81 | return try std.fmt.allocPrint(allocator, "{s} r{d}, 0x{x}, {s}0x{x}", .{ 82 | name, 83 | insn.dst, 84 | @as(u32, @bitCast(insn.imm)), 85 | if (insn.offset >= 0) "+" else "-", 86 | @abs(insn.offset), 87 | }); 88 | } 89 | 90 | fn jmp_reg_str(allocator: std.mem.Allocator, name: []const u8, insn: ebpf.Instruction) ![]const u8 { 91 | return try std.fmt.allocPrint(allocator, "{s} r{d}, r{d}, {s}0x{x}", .{ 92 | name, 93 | insn.dst, 94 | insn.src, 95 | if (insn.offset >= 0) "+" else "-", 96 | @abs(insn.offset), 97 | }); 98 | } 99 | 100 | // Main function to disassemble eBPF bytecode 101 | pub fn to_insn_vec(allocator: std.mem.Allocator, prog: []const u8) DisassemblerError![]HLInsn { 102 | if (prog.len % ebpf.INSN_SIZE != 0) { 103 | return DisassemblerError.InvalidProgram; 104 | } 105 | 106 | var res = std.ArrayList(HLInsn).init(allocator); 107 | defer { 108 | for (res.items) |item| { 109 | allocator.free(item.description); 110 | } 111 | res.deinit(); 112 | } 113 | 114 | var insn_ptr: usize = 0; 115 | while (insn_ptr * ebpf.INSN_SIZE < prog.len) : (insn_ptr += 1) { 116 | const insn = try ebpf.Instruction.get_ix(prog, insn_ptr); 117 | 118 | var name: []const u8 = undefined; 119 | var desc: []const u8 = undefined; 120 | var imm: i64 = @intCast(insn.imm); 121 | 122 | // Decode instruction based on opcode 123 | switch (insn.op) { 124 | // BPF_LD class 125 | ebpf.LD_ABS_W => { // 0x20 126 | name = "ldabsw"; 127 | desc = try ldabs_str(allocator, name, insn); 128 | }, 129 | ebpf.LD_ABS_H => { // 0x28 130 | name = "ldabsh"; 131 | desc = try ldabs_str(allocator, name, insn); 132 | }, 133 | ebpf.LD_ABS_B => { // 0x30 134 | name = "ldabsb"; 135 | desc = try ldabs_str(allocator, name, insn); 136 | }, 137 | ebpf.LD_ABS_DW => { // 0x38 138 | name = "ldabsdw"; 139 | desc = try ldabs_str(allocator, name, insn); 140 | }, 141 | ebpf.LD_IND_W => { // 0x40 142 | name = "ldindw"; 143 | desc = try ldind_str(allocator, name, insn); 144 | }, 145 | ebpf.LD_IND_H => { // 0x48 146 | name = "ldindh"; 147 | desc = try ldind_str(allocator, name, insn); 148 | }, 149 | ebpf.LD_IND_B => { // 0x50 150 | name = "ldindb"; 151 | desc = try ldind_str(allocator, name, insn); 152 | }, 153 | ebpf.LD_IND_DW => { // 0x58 154 | name = "ldinddw"; 155 | desc = try ldind_str(allocator, name, insn); 156 | }, 157 | 158 | ebpf.LD_DW_IMM => { // 0x18 159 | if (insn_ptr + 1 >= prog.len / ebpf.INSN_SIZE) { 160 | return DisassemblerError.InvalidProgram; 161 | } 162 | insn_ptr += 1; 163 | const next_insn = try ebpf.Instruction.get_ix(prog, insn_ptr); 164 | const imm_u64 = @as(u64, @as(u32, @bitCast(insn.imm))) | (@as(u64, @as(u32, @bitCast(next_insn.imm))) << 32); 165 | imm = @bitCast(imm_u64); 166 | name = "lddw"; 167 | desc = try std.fmt.allocPrint(allocator, "{s} r{d}, 0x{x:0>16}", .{ name, insn.dst, imm_u64 }); 168 | }, 169 | 170 | // BPF_LDX class 171 | ebpf.LD_B_REG => { // 0x71 172 | name = "ldxb"; 173 | desc = try ld_reg_str(allocator, name, insn); 174 | }, 175 | ebpf.LD_H_REG => { // 0x69 176 | name = "ldxh"; 177 | desc = try ld_reg_str(allocator, name, insn); 178 | }, 179 | ebpf.LD_W_REG => { // 0x61 180 | name = "ldxw"; 181 | desc = try ld_reg_str(allocator, name, insn); 182 | }, 183 | ebpf.LD_DW_REG => { // 0x79 184 | name = "ldxdw"; 185 | desc = try ld_reg_str(allocator, name, insn); 186 | }, 187 | 188 | // BPF_ST class 189 | ebpf.ST_B_IMM => { // 0x72 190 | name = "stb"; 191 | desc = try ld_st_imm_str(allocator, name, insn); 192 | }, 193 | ebpf.ST_H_IMM => { // 0x6a 194 | name = "sth"; 195 | desc = try ld_st_imm_str(allocator, name, insn); 196 | }, 197 | ebpf.ST_W_IMM => { // 0x62 198 | name = "stw"; 199 | desc = try ld_st_imm_str(allocator, name, insn); 200 | }, 201 | ebpf.ST_DW_IMM => { // 0x7a 202 | name = "stdw"; 203 | desc = try ld_st_imm_str(allocator, name, insn); 204 | }, 205 | 206 | // BPF_STX class 207 | ebpf.ST_B_REG => { // 0x73 208 | name = "stxb"; 209 | desc = try st_reg_str(allocator, name, insn); 210 | }, 211 | ebpf.ST_H_REG => { // 0x6b 212 | name = "stxh"; 213 | desc = try st_reg_str(allocator, name, insn); 214 | }, 215 | ebpf.ST_W_REG => { // 0x63 216 | name = "stxw"; 217 | desc = try st_reg_str(allocator, name, insn); 218 | }, 219 | ebpf.ST_DW_REG => { // 0x7b 220 | name = "stxdw"; 221 | desc = try st_reg_str(allocator, name, insn); 222 | }, 223 | ebpf.ST_W_XADD => { // 0xc3 224 | name = "stxxaddw"; 225 | desc = try st_reg_str(allocator, name, insn); 226 | }, 227 | ebpf.ST_DW_XADD => { // 0xdb 228 | name = "stxxadddw"; 229 | desc = try st_reg_str(allocator, name, insn); 230 | }, 231 | 232 | // BPF_ALU class 233 | ebpf.ADD32_IMM => { // 0x04 234 | name = "add32"; 235 | desc = try alu_imm_str(allocator, name, insn); 236 | }, 237 | ebpf.ADD32_REG => { // 0x0c 238 | name = "add32"; 239 | desc = try alu_reg_str(allocator, name, insn); 240 | }, 241 | ebpf.SUB32_IMM => { // 0x14 242 | name = "sub32"; 243 | desc = try alu_imm_str(allocator, name, insn); 244 | }, 245 | ebpf.SUB32_REG => { // 0x1c 246 | name = "sub32"; 247 | desc = try alu_reg_str(allocator, name, insn); 248 | }, 249 | ebpf.MUL32_IMM => { // 0x24 250 | name = "mul32"; 251 | desc = try alu_imm_str(allocator, name, insn); 252 | }, 253 | ebpf.MUL32_REG => { // 0x2c 254 | name = "mul32"; 255 | desc = try alu_reg_str(allocator, name, insn); 256 | }, 257 | ebpf.DIV32_IMM => { // 0x34 258 | name = "div32"; 259 | desc = try alu_imm_str(allocator, name, insn); 260 | }, 261 | ebpf.DIV32_REG => { // 0x3c 262 | name = "div32"; 263 | desc = try alu_reg_str(allocator, name, insn); 264 | }, 265 | ebpf.OR32_IMM => { // 0x44 266 | name = "or32"; 267 | desc = try alu_imm_str(allocator, name, insn); 268 | }, 269 | ebpf.OR32_REG => { // 0x4c 270 | name = "or32"; 271 | desc = try alu_reg_str(allocator, name, insn); 272 | }, 273 | ebpf.AND32_IMM => { // 0x54 274 | name = "and32"; 275 | desc = try alu_imm_str(allocator, name, insn); 276 | }, 277 | ebpf.AND32_REG => { // 0x5c 278 | name = "and32"; 279 | desc = try alu_reg_str(allocator, name, insn); 280 | }, 281 | ebpf.LSH32_IMM => { // 0x64 282 | name = "lsh32"; 283 | desc = try alu_imm_str(allocator, name, insn); 284 | }, 285 | ebpf.LSH32_REG => { // 0x6c 286 | name = "lsh32"; 287 | desc = try alu_reg_str(allocator, name, insn); 288 | }, 289 | ebpf.RSH32_IMM => { // 0x74 290 | name = "rsh32"; 291 | desc = try alu_imm_str(allocator, name, insn); 292 | }, 293 | ebpf.RSH32_REG => { // 0x7c 294 | name = "rsh32"; 295 | desc = try alu_reg_str(allocator, name, insn); 296 | }, 297 | ebpf.NEG32 => { // 0x84 298 | name = "neg32"; 299 | desc = try std.fmt.allocPrint(allocator, "{s} r{d}", .{ name, insn.dst }); 300 | }, 301 | ebpf.MOD32_IMM => { // 0x94 302 | name = "mod32"; 303 | desc = try alu_imm_str(allocator, name, insn); 304 | }, 305 | ebpf.MOD32_REG => { // 0x9c 306 | name = "mod32"; 307 | desc = try alu_reg_str(allocator, name, insn); 308 | }, 309 | ebpf.XOR32_IMM => { // 0xa4 310 | name = "xor32"; 311 | desc = try alu_imm_str(allocator, name, insn); 312 | }, 313 | ebpf.XOR32_REG => { // 0xac 314 | name = "xor32"; 315 | desc = try alu_reg_str(allocator, name, insn); 316 | }, 317 | ebpf.MOV32_IMM => { // 0xb4 318 | name = "mov32"; 319 | desc = try alu_imm_str(allocator, name, insn); 320 | }, 321 | ebpf.MOV32_REG => { // 0xbc 322 | name = "mov32"; 323 | desc = try alu_reg_str(allocator, name, insn); 324 | }, 325 | ebpf.ARSH32_IMM => { // 0xc4 326 | name = "arsh32"; 327 | desc = try alu_imm_str(allocator, name, insn); 328 | }, 329 | ebpf.ARSH32_REG => { // 0xcc 330 | name = "arsh32"; 331 | desc = try alu_reg_str(allocator, name, insn); 332 | }, 333 | ebpf.LE => { // 0xd4 334 | name = "le"; 335 | desc = try byteswap_str(allocator, name, insn); 336 | }, 337 | ebpf.BE => { // 0xdc 338 | name = "be"; 339 | desc = try byteswap_str(allocator, name, insn); 340 | }, 341 | 342 | // BPF_ALU64 class 343 | ebpf.ADD64_IMM => { // 0x07 344 | name = "add64"; 345 | desc = try alu_imm_str(allocator, name, insn); 346 | }, 347 | ebpf.ADD64_REG => { // 0x0f 348 | name = "add64"; 349 | desc = try alu_reg_str(allocator, name, insn); 350 | }, 351 | ebpf.SUB64_IMM => { // 0x17 352 | name = "sub64"; 353 | desc = try alu_imm_str(allocator, name, insn); 354 | }, 355 | ebpf.SUB64_REG => { // 0x1f 356 | name = "sub64"; 357 | desc = try alu_reg_str(allocator, name, insn); 358 | }, 359 | ebpf.MUL64_IMM => { // 0x27 360 | name = "mul64"; 361 | desc = try alu_imm_str(allocator, name, insn); 362 | }, 363 | ebpf.MUL64_REG => { // 0x2f 364 | name = "mul64"; 365 | desc = try alu_reg_str(allocator, name, insn); 366 | }, 367 | ebpf.DIV64_IMM => { // 0x37 368 | name = "div64"; 369 | desc = try alu_imm_str(allocator, name, insn); 370 | }, 371 | ebpf.DIV64_REG => { // 0x3f 372 | name = "div64"; 373 | desc = try alu_reg_str(allocator, name, insn); 374 | }, 375 | ebpf.OR64_IMM => { // 0x47 376 | name = "or64"; 377 | desc = try alu_imm_str(allocator, name, insn); 378 | }, 379 | ebpf.OR64_REG => { // 0x4f 380 | name = "or64"; 381 | desc = try alu_reg_str(allocator, name, insn); 382 | }, 383 | ebpf.AND64_IMM => { // 0x57 384 | name = "and64"; 385 | desc = try alu_imm_str(allocator, name, insn); 386 | }, 387 | ebpf.AND64_REG => { // 0x5f 388 | name = "and64"; 389 | desc = try alu_reg_str(allocator, name, insn); 390 | }, 391 | ebpf.LSH64_IMM => { // 0x67 392 | name = "lsh64"; 393 | desc = try alu_imm_str(allocator, name, insn); 394 | }, 395 | ebpf.LSH64_REG => { // 0x6f 396 | name = "lsh64"; 397 | desc = try alu_reg_str(allocator, name, insn); 398 | }, 399 | ebpf.RSH64_IMM => { // 0x77 400 | name = "rsh64"; 401 | desc = try alu_imm_str(allocator, name, insn); 402 | }, 403 | ebpf.RSH64_REG => { // 0x7f 404 | name = "rsh64"; 405 | desc = try alu_reg_str(allocator, name, insn); 406 | }, 407 | ebpf.NEG64 => { // 0x87 408 | name = "neg64"; 409 | desc = try std.fmt.allocPrint(allocator, "{s} r{d}", .{ name, insn.dst }); 410 | }, 411 | ebpf.MOD64_IMM => { // 0x97 412 | name = "mod64"; 413 | desc = try alu_imm_str(allocator, name, insn); 414 | }, 415 | ebpf.MOD64_REG => { // 0x9f 416 | name = "mod64"; 417 | desc = try alu_reg_str(allocator, name, insn); 418 | }, 419 | ebpf.XOR64_IMM => { // 0xa7 420 | name = "xor64"; 421 | desc = try alu_imm_str(allocator, name, insn); 422 | }, 423 | ebpf.XOR64_REG => { // 0xaf 424 | name = "xor64"; 425 | desc = try alu_reg_str(allocator, name, insn); 426 | }, 427 | ebpf.MOV64_IMM => { // 0xb7 428 | name = "mov64"; 429 | desc = try alu_imm_str(allocator, name, insn); 430 | }, 431 | ebpf.MOV64_REG => { // 0xbf 432 | name = "mov64"; 433 | desc = try alu_reg_str(allocator, name, insn); 434 | }, 435 | ebpf.ARSH64_IMM => { // 0xc7 436 | name = "arsh64"; 437 | desc = try alu_imm_str(allocator, name, insn); 438 | }, 439 | ebpf.ARSH64_REG => { // 0xcf 440 | name = "arsh64"; 441 | desc = try alu_reg_str(allocator, name, insn); 442 | }, 443 | 444 | // BPF_JMP class 445 | ebpf.JA => { // 0x05 446 | name = "ja"; 447 | desc = try std.fmt.allocPrint(allocator, "{s} {s}0x{x}", .{ name, if (insn.offset >= 0) "+" else "-", @abs(insn.offset) }); 448 | }, 449 | ebpf.JEQ_IMM => { // 0x15 450 | name = "jeq"; 451 | desc = try jmp_imm_str(allocator, name, insn); 452 | }, 453 | ebpf.JEQ_REG => { // 0x1d 454 | name = "jeq"; 455 | desc = try jmp_reg_str(allocator, name, insn); 456 | }, 457 | ebpf.JGT_IMM => { // 0x25 458 | name = "jgt"; 459 | desc = try jmp_imm_str(allocator, name, insn); 460 | }, 461 | ebpf.JGT_REG => { // 0x2d 462 | name = "jgt"; 463 | desc = try jmp_reg_str(allocator, name, insn); 464 | }, 465 | ebpf.JGE_IMM => { // 0x35 466 | name = "jge"; 467 | desc = try jmp_imm_str(allocator, name, insn); 468 | }, 469 | ebpf.JGE_REG => { // 0x3d 470 | name = "jge"; 471 | desc = try jmp_reg_str(allocator, name, insn); 472 | }, 473 | ebpf.JLT_IMM => { // 0xa5 474 | name = "jlt"; 475 | desc = try jmp_imm_str(allocator, name, insn); 476 | }, 477 | ebpf.JLT_REG => { // 0xad 478 | name = "jlt"; 479 | desc = try jmp_reg_str(allocator, name, insn); 480 | }, 481 | ebpf.JLE_IMM => { // 0xb5 482 | name = "jle"; 483 | desc = try jmp_imm_str(allocator, name, insn); 484 | }, 485 | ebpf.JLE_REG => { // 0xbd 486 | name = "jle"; 487 | desc = try jmp_reg_str(allocator, name, insn); 488 | }, 489 | ebpf.JSET_IMM => { // 0x45 490 | name = "jset"; 491 | desc = try jmp_imm_str(allocator, name, insn); 492 | }, 493 | ebpf.JSET_REG => { // 0x4d 494 | name = "jset"; 495 | desc = try jmp_reg_str(allocator, name, insn); 496 | }, 497 | ebpf.JNE_IMM => { // 0x55 498 | name = "jne"; 499 | desc = try jmp_imm_str(allocator, name, insn); 500 | }, 501 | ebpf.JNE_REG => { // 0x5d 502 | name = "jne"; 503 | desc = try jmp_reg_str(allocator, name, insn); 504 | }, 505 | ebpf.JSGT_IMM => { // 0x65 506 | name = "jsgt"; 507 | desc = try jmp_imm_str(allocator, name, insn); 508 | }, 509 | ebpf.JSGT_REG => { // 0x6d 510 | name = "jsgt"; 511 | desc = try jmp_reg_str(allocator, name, insn); 512 | }, 513 | ebpf.JSGE_IMM => { // 0x75 514 | name = "jsge"; 515 | desc = try jmp_imm_str(allocator, name, insn); 516 | }, 517 | ebpf.JSGE_REG => { // 0x7d 518 | name = "jsge"; 519 | desc = try jmp_reg_str(allocator, name, insn); 520 | }, 521 | ebpf.JSLT_IMM => { // 0xc5 522 | name = "jslt"; 523 | desc = try jmp_imm_str(allocator, name, insn); 524 | }, 525 | ebpf.JSLT_REG => { // 0xcd 526 | name = "jslt"; 527 | desc = try jmp_reg_str(allocator, name, insn); 528 | }, 529 | ebpf.JSLE_IMM => { // 0xd5 530 | name = "jsle"; 531 | desc = try jmp_imm_str(allocator, name, insn); 532 | }, 533 | ebpf.JSLE_REG => { // 0xdd 534 | name = "jsle"; 535 | desc = try jmp_reg_str(allocator, name, insn); 536 | }, 537 | ebpf.CALL => { // 0x85 538 | name = "call"; 539 | desc = try std.fmt.allocPrint(allocator, "{s} 0x{x}", .{ name, @as(u32, @bitCast(insn.imm)) }); 540 | }, 541 | ebpf.TAIL_CALL => { // 0x8d 542 | name = "tail_call"; 543 | desc = try std.fmt.allocPrint(allocator, "{s}", .{name}); 544 | }, 545 | ebpf.EXIT => { // 0x95 546 | name = "exit"; 547 | desc = try std.fmt.allocPrint(allocator, "{s}", .{name}); 548 | }, 549 | 550 | // BPF_JMP32 class 551 | ebpf.JEQ_IMM32 => { // 0x16 552 | name = "jeq32"; 553 | desc = try jmp_imm_str(allocator, name, insn); 554 | }, 555 | ebpf.JEQ_REG32 => { // 0x1e 556 | name = "jeq32"; 557 | desc = try jmp_reg_str(allocator, name, insn); 558 | }, 559 | ebpf.JGT_IMM32 => { // 0x26 560 | name = "jgt32"; 561 | desc = try jmp_imm_str(allocator, name, insn); 562 | }, 563 | ebpf.JGT_REG32 => { // 0x2e 564 | name = "jgt32"; 565 | desc = try jmp_reg_str(allocator, name, insn); 566 | }, 567 | ebpf.JGE_IMM32 => { // 0x36 568 | name = "jge32"; 569 | desc = try jmp_imm_str(allocator, name, insn); 570 | }, 571 | ebpf.JGE_REG32 => { // 0x3e 572 | name = "jge32"; 573 | desc = try jmp_reg_str(allocator, name, insn); 574 | }, 575 | ebpf.JLT_IMM32 => { // 0xa6 576 | name = "jlt32"; 577 | desc = try jmp_imm_str(allocator, name, insn); 578 | }, 579 | ebpf.JLT_REG32 => { // 0xae 580 | name = "jlt32"; 581 | desc = try jmp_reg_str(allocator, name, insn); 582 | }, 583 | ebpf.JLE_IMM32 => { // 0xb6 584 | name = "jle32"; 585 | desc = try jmp_imm_str(allocator, name, insn); 586 | }, 587 | ebpf.JLE_REG32 => { // 0xbe 588 | name = "jle32"; 589 | desc = try jmp_reg_str(allocator, name, insn); 590 | }, 591 | ebpf.JSET_IMM32 => { // 0x46 592 | name = "jset32"; 593 | desc = try jmp_imm_str(allocator, name, insn); 594 | }, 595 | ebpf.JSET_REG32 => { // 0x4e 596 | name = "jset32"; 597 | desc = try jmp_reg_str(allocator, name, insn); 598 | }, 599 | ebpf.JNE_IMM32 => { // 0x56 600 | name = "jne32"; 601 | desc = try jmp_imm_str(allocator, name, insn); 602 | }, 603 | ebpf.JNE_REG32 => { // 0x5e 604 | name = "jne32"; 605 | desc = try jmp_reg_str(allocator, name, insn); 606 | }, 607 | ebpf.JSGT_IMM32 => { // 0x66 608 | name = "jsgt32"; 609 | desc = try jmp_imm_str(allocator, name, insn); 610 | }, 611 | ebpf.JSGT_REG32 => { // 0x6e 612 | name = "jsgt32"; 613 | desc = try jmp_reg_str(allocator, name, insn); 614 | }, 615 | ebpf.JSGE_IMM32 => { // 0x76 616 | name = "jsge32"; 617 | desc = try jmp_imm_str(allocator, name, insn); 618 | }, 619 | ebpf.JSGE_REG32 => { // 0x7e 620 | name = "jsge32"; 621 | desc = try jmp_reg_str(allocator, name, insn); 622 | }, 623 | ebpf.JSLT_IMM32 => { // 0xc6 624 | name = "jslt32"; 625 | desc = try jmp_imm_str(allocator, name, insn); 626 | }, 627 | ebpf.JSLT_REG32 => { // 0xce 628 | name = "jslt32"; 629 | desc = try jmp_reg_str(allocator, name, insn); 630 | }, 631 | ebpf.JSLE_IMM32 => { // 0xd6 632 | name = "jsle32"; 633 | desc = try jmp_imm_str(allocator, name, insn); 634 | }, 635 | ebpf.JSLE_REG32 => { // 0xde 636 | name = "jsle32"; 637 | desc = try jmp_reg_str(allocator, name, insn); 638 | }, 639 | 640 | else => { 641 | return DisassemblerError.UnknownOpcode; 642 | }, 643 | } 644 | 645 | try res.append(HLInsn{ 646 | .opcode = insn.op, 647 | .name = name, 648 | .description = desc, 649 | .dst = insn.dst, 650 | .src = insn.src, 651 | .off = insn.offset, 652 | .imm = imm, 653 | }); 654 | } 655 | 656 | return res.toOwnedSlice(); 657 | } 658 | --------------------------------------------------------------------------------