diff --git a/.circleci/config.yml b/.circleci/config.yml index ab9038b00..ca0498a1c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -75,7 +75,7 @@ jobs: - attach_workspace: at: workspace - restore_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v6 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v7 - run: name: Build command: | @@ -83,7 +83,7 @@ jobs: ./scripts/proxy_workaround.sh workspace/zig/zig workspace/zig/zig build -Denable-tsan=true -p workspace/zig-out -Dcpu=x86_64_v3 --summary all - save_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v6 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v7 paths: - .zig-cache - ~/.cache/zig @@ -99,7 +99,7 @@ jobs: - attach_workspace: at: workspace - restore_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-release-v3 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-release-v4 - run: name: Build command: | @@ -107,7 +107,7 @@ jobs: ./scripts/proxy_workaround.sh workspace/zig/zig workspace/zig/zig build sig fuzz -Dno-run -Denable-tsan=false -Doptimize=ReleaseSafe -Dcpu=x86_64_v3 -p workspace/zig-out-release --summary all - save_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-release-v3 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-release-v4 paths: - .zig-cache - ~/.cache/zig @@ -147,7 +147,7 @@ jobs: - attach_workspace: at: workspace - restore_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v6 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v7 - run: name: Build and Test command: workspace/zig/zig build test -Denable-tsan=true -Dblockstore=hashmap -Dcpu=x86_64_v3 -Dfilter="ledger" --color off --summary all @@ -161,7 +161,7 @@ jobs: # Restore the cache in order to have access to the files which the DWARF info # is referencing when dumping stack traces. - restore_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v6 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v7 - run: name: Test # Disable network-accessing tests for this job, which behave badly on circleci @@ -174,7 +174,7 @@ jobs: - attach_workspace: at: workspace - restore_cache: - key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v6 + key: linux-x86_64-0.13.0-{{ checksum "build.zig.zon" }}-v7 - run: name: Build command: workspace/zig/zig build test -Dcpu=x86_64_v3 -Denable-tsan=false -Dno-run -Dno-network-tests --summary all diff --git a/build.zig b/build.zig index 429bb17a0..3cb88bd06 100644 --- a/build.zig +++ b/build.zig @@ -52,6 +52,9 @@ pub fn build(b: *Build) void { const zstd_dep = b.dependency("zstd", dep_opts); const zstd_mod = zstd_dep.module("zstd"); + const poseidon_dep = b.dependency("poseidon", dep_opts); + const poseidon_mod = poseidon_dep.module("poseidon"); + const rocksdb_dep = b.dependency("rocksdb", dep_opts); const rocksdb_mod = rocksdb_dep.module("rocksdb-bindings"); @@ -78,6 +81,8 @@ pub fn build(b: *Build) void { sig_mod.addImport("base58", base58_mod); sig_mod.addImport("zig-cli", zig_cli_mod); sig_mod.addImport("zstd", zstd_mod); + sig_mod.addImport("poseidon", poseidon_mod); + switch (blockstore_db) { .rocksdb => sig_mod.addImport("rocksdb", rocksdb_mod), .hashmap => {}, @@ -146,6 +151,7 @@ pub fn build(b: *Build) void { unit_tests_exe.root_module.addImport("base58", base58_mod); unit_tests_exe.root_module.addImport("zig-network", zig_network_mod); unit_tests_exe.root_module.addImport("zstd", zstd_mod); + unit_tests_exe.root_module.addImport("poseidon", poseidon_mod); switch (blockstore_db) { .rocksdb => unit_tests_exe.root_module.addImport("rocksdb", rocksdb_mod), .hashmap => {}, diff --git a/build.zig.zon b/build.zig.zon index c3690c761..37fcafe6f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -39,5 +39,9 @@ .url = "git+https://github.com/Syndica/base58-zig#e6337e7eb3dc7ca77cad4c3b2fe4a34357bd50ea", .hash = "122092353c0b494c2a4968c10a527dc90d6e89eaac48637fda1a0af3efd948bf4ee3", }, + .poseidon = .{ + .url = "git+https://github.com/Rexicon226/zig-poseidon#82e41845bfc689192359639fef5bb89c452388b2", + .hash = "12208f0d8388111308c919d73c24e9a595c3f2b5f83edf2cebf10598f8439fe33843", + }, }, } diff --git a/data/test-elfs/bss_section.so b/data/test-elfs/bss_section.so deleted file mode 100755 index 9df6d020a..000000000 Binary files a/data/test-elfs/bss_section.so and /dev/null differ diff --git a/data/test-elfs/bss_section_sbpfv0.so b/data/test-elfs/bss_section_sbpfv0.so new file mode 100755 index 000000000..3157e5e27 Binary files /dev/null and b/data/test-elfs/bss_section_sbpfv0.so differ diff --git a/data/test-elfs/data_section.so b/data/test-elfs/data_section.so deleted file mode 100755 index 3dc2e206b..000000000 Binary files a/data/test-elfs/data_section.so and /dev/null differ diff --git a/data/test-elfs/data_section_sbpfv0.so b/data/test-elfs/data_section_sbpfv0.so new file mode 100755 index 000000000..f2578e367 Binary files /dev/null and b/data/test-elfs/data_section_sbpfv0.so differ diff --git a/data/test-elfs/elf.ld b/data/test-elfs/elf.ld index 34ee22dfa..59eab41ad 100644 --- a/data/test-elfs/elf.ld +++ b/data/test-elfs/elf.ld @@ -1,26 +1,49 @@ -PHDRS -{ - text PT_LOAD ; - rodata PT_LOAD ; - data PT_LOAD ; - dynamic PT_DYNAMIC ; -} -ENTRY ( entrypoint ) SECTIONS { - . = SIZEOF_HEADERS; - .text : { *(.text*) } :text - .rodata : { *(.rodata*) } :rodata - .data.rel.ro : { *(.data.rel.ro*) } :rodata - .dynamic : { *(.dynamic) } :dynamic - .dynsym : { *(.dynsym) } :data - .dynstr : { *(.dynstr) } :data - .rel.dyn : { *(.rel.dyn) } :data - .data : { *(.data*) } :data - .bss : { *(.bss*) } :data + .text 0x000000000 : { + *(.text*) + } :text + .rodata 0x100000000 : { + *(.rodata*) + *(.data.rel.ro*) + BYTE(0); + . = ALIGN(8); + } :rodata + .bss.stack 0x200000000 (NOLOAD) : { + _stack_start = .; + . = . + 0x1000; + _stack_end = .; + . = ALIGN(8); + } :stack + .bss.heap 0x300000000 (NOLOAD) : { + _heap_start = .; + . = . + 0x1000; + _heap_end = .; + . = ALIGN(8); + } :heap + .dynsym 0xFFFFFFFF00000000 : { + *(.dynsym) + . = ALIGN(8); + } :dynsym + .strtab : { *(.strtab) } :other + .dynstr : { *(.dynstr) } :other /DISCARD/ : { + *(.comment*) *(.eh_frame*) - *(.gnu.hash*) - *(.hash*) + *(*hash*) + *(.bss*) + *(.data*) + *(.rel.dyn*) + *(.dynamic) } } + +PHDRS +{ + text PT_LOAD FLAGS(1); + rodata PT_LOAD FLAGS(4); + stack PT_GNU_STACK FLAGS(6); + heap PT_LOAD FLAGS(6); + dynsym PT_NULL FLAGS(0); + other PT_NULL FLAGS(0); +} diff --git a/data/test-elfs/elf_sbpfv0.ld b/data/test-elfs/elf_sbpfv0.ld new file mode 100644 index 000000000..859ab6693 --- /dev/null +++ b/data/test-elfs/elf_sbpfv0.ld @@ -0,0 +1,26 @@ +PHDRS +{ + text PT_LOAD ; + rodata PT_LOAD ; + data PT_LOAD ; + dynamic PT_DYNAMIC ; +} + +SECTIONS +{ + . = SIZEOF_HEADERS; + .text : { *(.text*) } :text + .rodata : { *(.rodata*) } :rodata + .data.rel.ro : { *(.data.rel.ro*) } :rodata + .dynamic : { *(.dynamic) } :dynamic + .dynsym : { *(.dynsym) } :data + .dynstr : { *(.dynstr) } :data + .rel.dyn : { *(.rel.dyn) } :data + .data : { *(.data*) } :data + .bss : { *(.bss*) } :data + /DISCARD/ : { + *(.eh_frame*) + *(.gnu.hash*) + *(.hash*) + } +} diff --git a/data/test-elfs/poseidon_test.so b/data/test-elfs/poseidon_test.so new file mode 100755 index 000000000..716c49b9c Binary files /dev/null and b/data/test-elfs/poseidon_test.so differ diff --git a/data/test-elfs/poseidon_test.zig b/data/test-elfs/poseidon_test.zig new file mode 100644 index 000000000..1afc80c25 --- /dev/null +++ b/data/test-elfs/poseidon_test.zig @@ -0,0 +1,288 @@ +const std = @import("std"); + +const SolBytes = extern struct { + addr: [*]const u8, + len: u64, +}; + +const log: *align(1) const fn (msg: [*]const u8, len: u64) void = @ptrFromInt(0x6bf5c3fe); + +const sol_poseidon: *const fn ( + parameters: u64, + endianness: u64, + bytes: [*]const SolBytes, + bytes_len: u64, + result: [*]u8, +) void = @ptrFromInt(0xc4947c21); + +const panic: *const fn ([*]const u8, u64, u64, u64) void = @ptrFromInt(0x686093bb); + +const POSEIDON_PARAMETERS_BN254_X5 = 0; +const POSEIDON_ENDIANNESS_BIG_ENDIAN = 0; +const POSEIDON_ENDIANNESS_LITTLE_ENDIAN = 1; +const POSEIDON_RESULT_LENGTH = 32; + +/// Mirrors this Agave test: https://github.com/anza-xyz/agave/blob/e87917adab5468fe13287147800922a3191d0040/programs/sbf/c/src/poseidon/poseidon.c#L8 +export fn entrypoint() u64 { + // Two inputs: ones and twos (big-endian). + { + const input1: [32]u8 = .{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + const input2: [32]u8 = .{ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + const inputs: [2]SolBytes = .{ + .{ .addr = &input1, .len = 32 }, + .{ .addr = &input2, .len = 32 }, + }; + + const expected: [32]u8 = .{ + 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, + 3, 85, 242, 99, 25, 32, 123, 132, 254, 156, 162, + 206, 27, 38, 231, 53, 200, 41, 130, 25, 144, + }; + + expectEqual(&inputs, &expected, .big); + } + // Two inputs: ones and twos (little-endian). + { + const input1: [32]u8 = .{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + const input2: [32]u8 = .{ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + const inputs: [2]SolBytes = .{ + .{ .addr = &input1, .len = 32 }, + .{ .addr = &input2, .len = 32 }, + }; + + const expected: [32]u8 = .{ + 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, + 156, 254, 132, 123, 32, 25, 99, 242, 85, 3, 94, + 235, 125, 28, 140, 138, 143, 147, 225, 84, 13, + }; + + expectEqual(&inputs, &expected, .little); + } + + const input0: [32]u8 = .{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + }; + + // 1 input. + { + const expected: [32]u8 = .{ + 41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, + 101, 77, 106, 60, 19, 14, 150, 164, 209, 22, 139, + 51, 132, 139, 137, 125, 197, 2, 130, 1, 51, + }; + const inputs: [1]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 2 inputs. + { + const expected: [32]u8 = .{ + 0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, + 169, 243, 2, 63, 119, 18, 148, 167, 138, 203, 112, + 231, 63, 144, 175, 226, 124, 173, 64, 30, 129, + }; + const inputs: [2]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 3 inputs. + { + const expected: [32]u8 = .{ + 2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, + 178, 20, 203, 62, 129, 188, 177, 182, 227, 9, 97, + 205, 35, 194, 2, 177, 134, 115, 191, 37, 67, + }; + const inputs: [3]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 4 inputs. + { + const expected: [32]u8 = .{ + 8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, + 65, 74, 55, 104, 31, 120, 68, 45, 39, 216, 99, + 133, 153, 28, 23, 214, 252, 12, 75, 125, 113, + }; + const inputs: [4]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 5 inputs. + { + const expected: [32]u8 = .{ + 16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, + 49, 34, 196, 125, 102, 168, 3, 199, 43, 65, 88, + 156, 177, 191, 134, 135, 65, 178, 6, 185, 187, + }; + const inputs: [5]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 6 inputs. + { + const expected: [32]u8 = .{ + 42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, + 229, 189, 191, 80, 179, 144, 53, 215, 114, 159, 19, + 91, 151, 9, 137, 15, 133, 197, 220, 94, 118, + }; + const inputs: [6]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 7 inputs. + { + const expected: [32]u8 = .{ + 34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, + 19, 157, 157, 169, 89, 190, 42, 49, 178, 199, 8, + 165, 248, 25, 84, 178, 101, 229, 58, 48, 184, + }; + const inputs: [7]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 8 inputs. + { + const expected: [32]u8 = .{ + 23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, + 51, 66, 81, 71, 9, 92, 79, 202, 187, 35, 61, + 35, 11, 109, 70, 162, 20, 217, 91, 40, 132, + }; + const inputs: [8]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 9 inputs. + { + const expected: [32]u8 = .{ + 14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, + 196, 46, 187, 68, 204, 110, 231, 5, 95, 97, 251, + 202, 94, 49, 59, 138, 95, 202, 131, 76, 71, + }; + const inputs: [9]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 10 inputs. + { + const expected: [32]u8 = .{ + 46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, + 79, 74, 112, 119, 193, 255, 146, 96, 228, 72, 133, + 196, 184, 29, 209, 49, 173, 58, 134, 205, 150, + }; + const inputs: [10]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 11 inputs. + { + const expected: [32]u8 = .{ + 0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, + 188, 235, 95, 58, 102, 220, 65, 66, 235, 112, 181, + 103, 101, 188, 53, 143, 27, 236, 64, 187, 155, + }; + const inputs: [11]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + // 12 inputs. + { + const expected: [32]u8 = .{ + 20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, + 221, 172, 101, 194, 229, 46, 133, 19, 192, 129, 193, + 205, 114, 201, 128, 6, 9, 142, 154, 143, 190, + }; + const inputs: [12]SolBytes = .{ + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + .{ .addr = &input0, .len = 32 }, .{ .addr = &input0, .len = 32 }, + }; + expectEqual(&inputs, &expected, .big); + } + + return 0; +} + +fn expectEqual( + inputs: []const SolBytes, + expected: []const u8, + endian: std.builtin.Endian, +) void { + var result: [32]u8 = undefined; + sol_poseidon( + POSEIDON_PARAMETERS_BN254_X5, + switch (endian) { + .big => POSEIDON_ENDIANNESS_BIG_ENDIAN, + .little => POSEIDON_ENDIANNESS_LITTLE_ENDIAN, + }, + inputs.ptr, + inputs.len, + &result, + ); + if (!std.mem.eql(u8, &result, expected)) { + log(&result, 32); + panic("failed", 6, 0, 0); + } +} diff --git a/data/test-elfs/reloc_64_64.so b/data/test-elfs/reloc_64_64.so index 17711ae0a..45615acab 100755 Binary files a/data/test-elfs/reloc_64_64.so and b/data/test-elfs/reloc_64_64.so differ diff --git a/data/test-elfs/reloc_64_64_sbpfv0.so b/data/test-elfs/reloc_64_64_sbpfv0.so index 964b73e9d..3f61d546e 100755 Binary files a/data/test-elfs/reloc_64_64_sbpfv0.so and b/data/test-elfs/reloc_64_64_sbpfv0.so differ diff --git a/data/test-elfs/reloc_64_relative.so b/data/test-elfs/reloc_64_relative.so index 4ebd735ab..55d00dca8 100755 Binary files a/data/test-elfs/reloc_64_relative.so and b/data/test-elfs/reloc_64_relative.so differ diff --git a/data/test-elfs/reloc_64_relative_data.so b/data/test-elfs/reloc_64_relative_data.so index 5f024fa1a..c13345111 100755 Binary files a/data/test-elfs/reloc_64_relative_data.so and b/data/test-elfs/reloc_64_relative_data.so differ diff --git a/data/test-elfs/reloc_64_relative_data_sbpfv0.so b/data/test-elfs/reloc_64_relative_data_sbpfv0.so index 94739643c..ae27029f1 100755 Binary files a/data/test-elfs/reloc_64_relative_data_sbpfv0.so and b/data/test-elfs/reloc_64_relative_data_sbpfv0.so differ diff --git a/data/test-elfs/reloc_64_relative_sbpfv0.so b/data/test-elfs/reloc_64_relative_sbpfv0.so index c9e0717fc..a5f9a8a77 100755 Binary files a/data/test-elfs/reloc_64_relative_sbpfv0.so and b/data/test-elfs/reloc_64_relative_sbpfv0.so differ diff --git a/data/test-elfs/rodata_section.so b/data/test-elfs/rodata_section.so index 197b3254f..7614a2125 100755 Binary files a/data/test-elfs/rodata_section.so and b/data/test-elfs/rodata_section.so differ diff --git a/data/test-elfs/rodata_section_sbpfv0.so b/data/test-elfs/rodata_section_sbpfv0.so index 2b3b6410f..fe165b991 100755 Binary files a/data/test-elfs/rodata_section_sbpfv0.so and b/data/test-elfs/rodata_section_sbpfv0.so differ diff --git a/data/test-elfs/strict_header.so b/data/test-elfs/strict_header.so new file mode 100755 index 000000000..c3794a201 Binary files /dev/null and b/data/test-elfs/strict_header.so differ diff --git a/data/test-elfs/strict_header.zig b/data/test-elfs/strict_header.zig new file mode 100644 index 000000000..51422a52d --- /dev/null +++ b/data/test-elfs/strict_header.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +const VAL: u64 = 42; + +noinline fn foo(ptr: *const volatile u64) u64 { + return ptr.*; +} + +export fn entrypoint() u64 { + return foo(&VAL); +} diff --git a/data/test-elfs/struct_func_pointer.so b/data/test-elfs/struct_func_pointer.so index c45582bf3..a3f44367e 100755 Binary files a/data/test-elfs/struct_func_pointer.so and b/data/test-elfs/struct_func_pointer.so differ diff --git a/data/test-elfs/struct_func_pointer_sbpfv0.so b/data/test-elfs/struct_func_pointer_sbpfv0.so new file mode 100755 index 000000000..963579388 Binary files /dev/null and b/data/test-elfs/struct_func_pointer_sbpfv0.so differ diff --git a/data/test-elfs/syscall_reloc_64_32.so b/data/test-elfs/syscall_reloc_64_32.so deleted file mode 100755 index 159288586..000000000 Binary files a/data/test-elfs/syscall_reloc_64_32.so and /dev/null differ diff --git a/data/test-elfs/syscall_reloc_64_32_sbpfv0.so b/data/test-elfs/syscall_reloc_64_32_sbpfv0.so new file mode 100755 index 000000000..4167e837b Binary files /dev/null and b/data/test-elfs/syscall_reloc_64_32_sbpfv0.so differ diff --git a/data/test-elfs/syscall_static.so b/data/test-elfs/syscall_static.so index 0667af4f1..dc0933674 100755 Binary files a/data/test-elfs/syscall_static.so and b/data/test-elfs/syscall_static.so differ diff --git a/data/test-elfs/syscall_static.zig b/data/test-elfs/syscall_static.zig new file mode 100644 index 000000000..63e109409 --- /dev/null +++ b/data/test-elfs/syscall_static.zig @@ -0,0 +1,8 @@ +const log: *align(1) const fn (msg: [*]const u8, len: u64) void = + // the murmur hash for "log" + @ptrFromInt(0x6bf5c3fe); + +export fn entrypoint() u64 { + log("foo\n", 4); + return 0; +} diff --git a/scripts/gen_svm_elfs.sh b/scripts/gen_svm_elfs.sh index c478d9977..6b6e564a6 100755 --- a/scripts/gen_svm_elfs.sh +++ b/scripts/gen_svm_elfs.sh @@ -4,34 +4,36 @@ CC="../toolchain/llvm/bin/clang" LD="../toolchain/llvm/bin/ld.lld" ZIG="zig" - -LD_FLAGS="-z notext -shared --Bdynamic --script data/test-elfs/elf.ld" -C_BASE_FLAGS="-target sbf-solana-solana \ - -fno-builtin \ - -fPIC -fno-unwind-tables \ - -fomit-frame-pointer -fno-exceptions \ - -fno-asynchronous-unwind-tables \ - -std=c23 \ - -O2 \ - -Werror \ - -Wno-override-module" -C_FLAGS="${C_BASE_FLAGS} -mcpu=generic" -C_FLAGS_V1="${C_BASE_FLAGS} -mcpu=sbfv2" +LD_FLAGS="${LD} -z notext -shared --Bdynamic -entry entrypoint" +C_FLAGS="-Werror -target sbf -O2 -fno-builtin -fPIC -Wno-override-module" +C_FLAGS_V3="${C_FLAGS} -mcpu=v3" CC_V0="${CC} ${C_FLAGS}" -CC_V1="${CC} ${C_FLAGS_V1}" - -LD_V0="${LD} ${LD_FLAGS}" -LD_V1="${LD_V0} --section-start=.text=0x100000000" - -V0_FILES=(reloc_64_64 reloc_64_relative reloc_64_relative_data rodata_section) +CC_V3="${CC} ${C_FLAGS_V3}" + +LD_V0="${LD_FLAGS} --script data/test-elfs/elf_sbpfv0.ld" +LD_V3="${LD_FLAGS} -Bsymbolic --script data/test-elfs/elf.ld" + +V0_FILES=(reloc_64_64 + reloc_64_relative + reloc_64_relative_data + rodata_section + bss_section + data_section + syscall_reloc_64_32 + struct_func_pointer) + +EXCLUDE_V3=(bss_section data_section + syscall_reloc_64_32) for ZIG_FILE in data/test-elfs/*.zig; do BASE_NAME=$(basename "$ZIG_FILE" .zig) - $ZIG build-obj "$ZIG_FILE" -OReleaseSmall -fstrip -fno-emit-bin -femit-llvm-bc="data/test-elfs/${BASE_NAME}.bc" - $CC_V1 "data/test-elfs/${BASE_NAME}.bc" -c -o "data/test-elfs/${BASE_NAME}.o" - $LD_V1 "data/test-elfs/${BASE_NAME}.o" -o "data/test-elfs/${BASE_NAME}.so" + $ZIG build-obj "$ZIG_FILE" -target bpfel-freestanding -OReleaseSmall -fstrip -fno-emit-bin -femit-llvm-bc="data/test-elfs/${BASE_NAME}.bc" + if [[ ! " ${EXCLUDE_V3[@]} " =~ " ${BASE_NAME} " ]]; then + $CC_V3 "data/test-elfs/${BASE_NAME}.bc" -c -o "data/test-elfs/${BASE_NAME}.o" + $LD_V3 "data/test-elfs/${BASE_NAME}.o" -o "data/test-elfs/${BASE_NAME}.so" + fi if [[ " ${V0_FILES[@]} " =~ " ${BASE_NAME} " ]]; then $CC_V0 "data/test-elfs/${BASE_NAME}.bc" -c -o "data/test-elfs/${BASE_NAME}_sbpfv0.o" diff --git a/scripts/kcov_test.sh b/scripts/kcov_test.sh index 016ed1e52..8c9c678ff 100755 --- a/scripts/kcov_test.sh +++ b/scripts/kcov_test.sh @@ -19,10 +19,9 @@ echo "=> Cleaning up" rm -rf kcov-output mkdir kcov-output - if [ -z "$1" ]; then echo "=> Building Sig" - zig build + zig build test -Dno-run test_bin="./zig-out/bin/test" else test_bin="$1" @@ -30,9 +29,7 @@ fi echo "=> Running kcov on tests" kcov \ - --collect-only \ --include-pattern=src/ \ --exclude-pattern=$HOME/.cache \ kcov-output \ - $test_bin -kcov --merge kcov-merged kcov-output/ \ No newline at end of file + $test_bin \ No newline at end of file diff --git a/src/prometheus/registry.zig b/src/prometheus/registry.zig index 45fd61596..e17f6ca0e 100644 --- a/src/prometheus/registry.zig +++ b/src/prometheus/registry.zig @@ -486,6 +486,6 @@ test "prometheus.registry: options" { } } -test { +comptime { testing.refAllDecls(@This()); } diff --git a/src/svm/elf.zig b/src/svm/elf.zig index c213d61ae..90b03a50f 100644 --- a/src/svm/elf.zig +++ b/src/svm/elf.zig @@ -3,6 +3,7 @@ //! Elf Spec: http://refspecs.linux-foundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic.html const std = @import("std"); +const sig = @import("../sig.zig"); const sbpf = @import("sbpf.zig"); const memory = @import("memory.zig"); @@ -34,11 +35,12 @@ pub const Elf = struct { phdrs: []align(1) const elf.Elf64_Phdr, fn parse(bytes: []const u8) !Headers { + if (bytes.len < @sizeOf(elf.Elf64_Ehdr)) return error.OutOfBounds; const header: elf.Elf64_Ehdr = @bitCast(bytes[0..@sizeOf(elf.Elf64_Ehdr)].*); const shoff = header.e_shoff; const shnum = header.e_shnum; - const shsize = shnum * @sizeOf(elf.Elf64_Shdr); + const shsize = try std.math.mul(u64, shnum, @sizeOf(elf.Elf64_Shdr)); const shdrs = std.mem.bytesAsSlice( elf.Elf64_Shdr, try safeSlice(bytes, shoff, shsize), @@ -46,7 +48,7 @@ pub const Elf = struct { const phoff = header.e_phoff; const phnum = header.e_phnum; - const phsize = phnum * @sizeOf(elf.Elf64_Phdr); + const phsize = try std.math.mul(u64, phnum, @sizeOf(elf.Elf64_Phdr)); const phdrs = std.mem.bytesAsSlice( elf.Elf64_Phdr, try safeSlice(bytes, phoff, phsize), @@ -61,6 +63,7 @@ pub const Elf = struct { } fn shdrSlice(self: Headers, index: u32) ![]const u8 { + if (index >= self.shdrs.len) return error.OutOfBounds; const shdr = self.shdrs[index]; const sh_offset = shdr.sh_offset; const sh_size = shdr.sh_size; @@ -68,12 +71,20 @@ pub const Elf = struct { } fn phdrSlice(self: Headers, index: u32) ![]const u8 { + if (index >= self.phdrs.len) return error.OutOfBounds; const phdr = self.phdrs[index]; const p_offset = phdr.p_offset; const p_filesz = phdr.p_filesz; return try safeSlice(self.bytes, p_offset, p_filesz); } + fn getStringInShdr(self: Headers, shdr: u32, off: u32) ![:0]const u8 { + const strtab = try self.shdrSlice(shdr); + assert(off < strtab.len); + const ptr: [*:0]const u8 = @ptrCast(strtab.ptr + off); + return std.mem.sliceTo(ptr, 0); + } + fn getPhdrIndexByType(self: Headers, p_type: elf.Elf64_Word) ?u32 { for (self.phdrs, 0..) |phdr, i| { if (phdr.p_type == p_type) return @intCast(i); @@ -186,9 +197,9 @@ pub const Elf = struct { self: Data, headers: Headers, config: Config, + version: sbpf.Version, allocator: std.mem.Allocator, ) !Executable.Section { - const version = config.minimum_version; const ro_names: []const []const u8 = &.{ ".text", ".rodata", @@ -246,10 +257,10 @@ pub const Elf = struct { } var vaddr_end = if (version.enableElfVaddr() and - section_addr >= memory.PROGRAM_START) + section_addr >= memory.RODATA_START) section_addr else - section_addr +| memory.PROGRAM_START; + section_addr +| memory.RODATA_START; if (version.rejectRodataStackOverlap()) { vaddr_end +|= shdr.sh_size; } @@ -279,9 +290,9 @@ pub const Elf = struct { const start = lowest_addr -| file_offset; const end = highest_addr -| file_offset; - if (lowest_addr >= memory.PROGRAM_START) { + if (lowest_addr >= memory.RODATA_START) { break :ro .{ .borrowed = .{ - .offset = lowest_addr -| memory.PROGRAM_START, + .offset = lowest_addr, .start = start, .end = end, } }; @@ -290,7 +301,7 @@ pub const Elf = struct { return error.ValueOutOfBounds; } break :ro .{ .borrowed = .{ - .offset = lowest_addr, + .offset = lowest_addr +| memory.RODATA_START, .start = start, .end = end, } }; @@ -316,6 +327,7 @@ pub const Elf = struct { } break :ro .{ .owned = .{ .offset = lowest_addr, .data = ro_section } }; }; + return ro_section; } @@ -326,7 +338,7 @@ pub const Elf = struct { return std.mem.sliceTo(ptr, 0); } - pub fn getShdrIndexByName(self: Data, headers: Headers, name: []const u8) ?u32 { + fn getShdrIndexByName(self: Data, headers: Headers, name: []const u8) ?u32 { for (headers.shdrs, 0..) |shdr, i| { const shdr_name = self.getString(shdr.sh_name); if (std.mem.eql(u8, shdr_name, name)) { @@ -351,12 +363,7 @@ pub const Elf = struct { const headers = try Headers.parse(bytes); const data = try Data.parse(headers); - const text_section = data.getShdrByName(headers, ".text") orelse - return error.NoTextSection; - const offset = headers.header.e_entry -| text_section.sh_addr; - const entry_pc = try std.math.divExact(u64, offset, 8); - - const sbpf_version: sbpf.Version = if (config.minimum_version == .v0) + const sbpf_version: sbpf.Version = if (config.maximum_version == .v0) if (headers.header.e_flags == sbpf.EF_SBPF_v1) .v1 else @@ -366,11 +373,181 @@ pub const Elf = struct { 1 => .v1, 2 => .v2, 3 => .v3, - else => @enumFromInt(headers.header.e_flags), + else => |v| @enumFromInt(v), }; - if (@intFromEnum(sbpf_version) < @intFromEnum(config.minimum_version)) + if (@intFromEnum(sbpf_version) < @intFromEnum(config.minimum_version) or + @intFromEnum(sbpf_version) > @intFromEnum(config.maximum_version)) + { return error.VersionUnsupported; + } + + if (sbpf_version.enableStricterElfHeaders()) { + return try parseStrict( + allocator, + bytes, + headers, + data, + sbpf_version, + config, + ); + } else { + return try parseLenient( + allocator, + bytes, + headers, + data, + sbpf_version, + config, + loader, + ); + } + } + + const ElfIdent = extern struct { + magic: [4]u8, + class: u8, + data: u8, + version: u8, + osabi: u8, + abiversion: u8, + padding: [7]u8, + }; + + fn parseStrict( + allocator: std.mem.Allocator, + bytes: []u8, + headers: Headers, + data: Data, + sbpf_version: sbpf.Version, + config: Config, + ) !Elf { + const header = headers.header; + + const ident: ElfIdent = @bitCast(header.e_ident); + if (!std.mem.eql(u8, ident.magic[0..4], elf.MAGIC) or + ident.class != elf.ELFCLASS64 or + ident.data != elf.ELFDATA2LSB or + ident.version != 1 or + ident.osabi != sbpf.ELFOSABI_NONE or + ident.abiversion != 0x00 or + !std.mem.allEqual(u8, &ident.padding, 0) or + @intFromEnum(header.e_machine) != sbpf.EM_SBPF or + header.e_type != .DYN or + header.e_version != 1 or + header.e_phoff != @sizeOf(elf.Elf64_Ehdr) or + header.e_ehsize != @sizeOf(elf.Elf64_Ehdr) or + header.e_phentsize != @sizeOf(elf.Elf64_Phdr) or + header.e_phnum < 5 or + header.e_shentsize != @sizeOf(elf.Elf64_Shdr) or + header.e_shstrndx >= header.e_shnum) + { + return error.InvalidFileHeader; + } + + inline for ( + .{ + .{ elf.PT_LOAD, elf.PF_X, memory.BYTECODE_START }, + .{ elf.PT_LOAD, elf.PF_R, memory.RODATA_START }, + .{ elf.PT_GNU_STACK, elf.PF_R | elf.PF_W, memory.STACK_START }, + .{ elf.PT_LOAD, elf.PF_R | elf.PF_W, memory.HEAP_START }, + .{ elf.PT_NULL, 0, 0xFFFFFFFF00000000 }, + }, + headers.phdrs[0..5], + ) |entry, phdr| { + const p_type, const p_flags, const p_vaddr = entry; + const p_filesz = if (p_flags & elf.PF_W != 0) 0 else phdr.p_memsz; + + if (phdr.p_type != p_type or + phdr.p_flags != p_flags or + phdr.p_offset % 8 != 0 or + phdr.p_vaddr != p_vaddr or + phdr.p_paddr != p_vaddr or + phdr.p_filesz != p_filesz or + phdr.p_filesz > bytes.len -| phdr.p_offset or + phdr.p_memsz >= memory.RODATA_START // larger than one region + ) { + return error.InvalidProgramHeader; + } + } + + const maybe_strtab: ?u32 = if (config.enable_symbol_and_section_labels) blk: { + for (headers.shdrs, 0..) |shdr, i| { + const name = data.getString(shdr.sh_name); + if (std.mem.eql(u8, name, ".dynstr")) { + if (shdr.sh_type != elf.SHT_STRTAB) return error.InvalidStringTable; + break :blk @intCast(i); + } + } + break :blk null; + } else null; + + const bytecode_hdr = headers.phdrs[0]; + const rodata_hdr = headers.phdrs[1]; + const ro_section: Executable.Section = .{ .borrowed = .{ + .offset = rodata_hdr.p_vaddr, + .start = rodata_hdr.p_offset, + .end = rodata_hdr.p_offset + rodata_hdr.p_filesz, + } }; + + const entry_pc = (header.e_entry -| bytecode_hdr.p_vaddr) / 8; + var self: Elf = .{ + .bytes = bytes, + .headers = headers, + .data = data, + .entry_pc = entry_pc, + .version = sbpf_version, + .function_registry = .{}, + .config = config, + .ro_section = ro_section, + }; + errdefer self.deinit(allocator); + + const dynsym_table = std.mem.bytesAsSlice(elf.Elf64_Sym, try headers.phdrSlice(4)); + var expected_symbol_address = bytecode_hdr.p_vaddr; + for (dynsym_table) |symbol| { + if (symbol.st_info & elf.STT_FUNC == 0) continue; + if (symbol.st_value != expected_symbol_address) return error.OutOfBounds; + if (symbol.st_size == 0 or symbol.st_size % 8 != 0) return error.InvalidSize; + if (!inRangeOfPhdrVm(bytecode_hdr, symbol.st_value)) return error.OutOfBounds; + + const name = if (config.enable_symbol_and_section_labels) + try headers.getStringInShdr(maybe_strtab.?, symbol.st_name) + else + &.{}; + + const target_pc = (symbol.st_value -| bytecode_hdr.p_vaddr) / 8; + try self.function_registry.register(allocator, target_pc, name, target_pc); + expected_symbol_address = symbol.st_value +| symbol.st_size; + } + if (expected_symbol_address != bytecode_hdr.p_vaddr +| bytecode_hdr.p_memsz) { + return error.OutOfBounds; + } + if (!inRangeOfPhdrVm(bytecode_hdr, header.e_entry) or + header.e_entry % 8 != 0) + { + return error.InvalidFileHeader; + } + if (self.function_registry.lookupKey(self.entry_pc) == null) { + return error.InvalidFileHeader; + } + + return self; + } + + fn parseLenient( + allocator: std.mem.Allocator, + bytes: []u8, + headers: Headers, + data: Data, + sbpf_version: sbpf.Version, + config: Config, + loader: *BuiltinProgram, + ) !Elf { + const text_section = data.getShdrByName(headers, ".text") orelse + return error.NoTextSection; + const offset = headers.header.e_entry -| text_section.sh_addr; + const entry_pc = try std.math.divExact(u64, offset, 8); var self: Elf = .{ .bytes = bytes, @@ -380,7 +557,7 @@ pub const Elf = struct { .version = sbpf_version, .function_registry = .{}, .config = config, - .ro_section = try data.parseRoSections(headers, config, allocator), + .ro_section = try data.parseRoSections(headers, config, sbpf_version, allocator), }; errdefer self.deinit(allocator); @@ -475,8 +652,10 @@ pub const Elf = struct { if (text_section_slice.len % @sizeOf(sbpf.Instruction) != 0) return error.InvalidTextSectionLength; - if (self.config.minimum_version.enableElfVaddr()) { - if (self.config.optimize_rodata != true) return error.UnsupportedSBPFVersion; + if (self.version.enableElfVaddr() and + self.config.optimize_rodata != true) + { + return error.UnsupportedSBPFVersion; } } @@ -555,8 +734,8 @@ pub const Elf = struct { const symbol = self.data.symbol_table[reloc.r_sym()]; var addr = symbol.st_value +| ref_addr; - if (addr < memory.PROGRAM_START) { - addr +|= memory.PROGRAM_START; + if (addr < memory.RODATA_START) { + addr +|= memory.RODATA_START; } if (in_text_section or version == .v0) { @@ -597,8 +776,8 @@ pub const Elf = struct { var ref_addr = (@as(u64, va_high) << 32) | va_low; if (ref_addr == 0) return error.InvalidVirtualAddress; - if (ref_addr < memory.PROGRAM_START) { - ref_addr +|= memory.PROGRAM_START; + if (ref_addr < memory.RODATA_START) { + ref_addr +|= memory.RODATA_START; } { @@ -626,7 +805,7 @@ pub const Elf = struct { .v0 => addr: { const addr_slice = try safeSlice(self.bytes, imm_offset, 4); const address = std.mem.readInt(u32, addr_slice[0..4], .little); - break :addr memory.PROGRAM_START +| address; + break :addr memory.RODATA_START +| address; }, else => addr: { const addr_slice = try safeSlice( @@ -635,8 +814,8 @@ pub const Elf = struct { @sizeOf(u64), ); var address = std.mem.readInt(u64, addr_slice[0..8], .little); - if (address < memory.PROGRAM_START) { - address +|= memory.PROGRAM_START; + if (address < memory.RODATA_START) { + address +|= memory.RODATA_START; } break :addr address; }, @@ -732,3 +911,113 @@ pub const Elf = struct { return base[start..][0..len]; } }; + +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectError = std.testing.expectError; + +test "parsing failing allocation" { + const S = struct { + fn foo(allocator: std.mem.Allocator) !void { + const input_file = try std.fs.cwd().openFile( + sig.ELF_DATA_DIR ++ "reloc_64_64.so", + .{}, + ); + const bytes = try input_file.readToEndAlloc(allocator, sbpf.MAX_FILE_SIZE); + defer allocator.free(bytes); + + var loader: BuiltinProgram = .{}; + var parsed = try Elf.parse(allocator, bytes, &loader, .{}); + defer parsed.deinit(allocator); + } + }; + + const allocator = std.testing.allocator; + try std.testing.checkAllAllocationFailures(allocator, S.foo, .{}); +} + +test "strict header empty" { + const allocator = std.testing.allocator; + var loader: BuiltinProgram = .{}; + try expectEqual( + error.OutOfBounds, + Elf.parse(allocator, &.{}, &loader, .{}), + ); +} + +test "strict header version" { + const allocator = std.testing.allocator; + const input_file = try std.fs.cwd().openFile(sig.ELF_DATA_DIR ++ "strict_header.so", .{}); + const bytes = try input_file.readToEndAlloc(allocator, sbpf.MAX_FILE_SIZE); + defer allocator.free(bytes); + + // set the e_flags to an invalid SBPF version + bytes[0x0030] = 0xFF; + + var loader: BuiltinProgram = .{}; + try expectEqual( + error.VersionUnsupported, + Elf.parse(allocator, bytes, &loader, .{}), + ); +} + +test "strict header functions" { + const allocator = std.testing.allocator; + const input_file = try std.fs.cwd().openFile(sig.ELF_DATA_DIR ++ "strict_header.so", .{}); + const bytes = try input_file.readToEndAlloc(allocator, sbpf.MAX_FILE_SIZE); + defer allocator.free(bytes); + + var loader: BuiltinProgram = .{}; + var parsed = try Elf.parse( + allocator, + bytes, + &loader, + .{ .enable_symbol_and_section_labels = true }, + ); + defer parsed.deinit(allocator); + + const entrypoint = parsed.function_registry.lookupKey(0).?; + try expect(std.mem.eql(u8, entrypoint.name, "entrypoint")); + + const foo = parsed.function_registry.lookupKey(2).?; + try expect(std.mem.eql(u8, foo.name, "strict_header.foo")); +} + +test "strict header corrupt file header" { + const allocator = std.testing.allocator; + const input_file = try std.fs.cwd().openFile(sig.ELF_DATA_DIR ++ "strict_header.so", .{}); + const bytes = try input_file.readToEndAlloc(allocator, sbpf.MAX_FILE_SIZE); + defer allocator.free(bytes); + + const expected_results = + .{error.InvalidFileHeader} ** 33 ++ + .{error.OutOfBounds} ** 15 ++ + .{error.VersionUnsupported} ** 4 ++ + .{error.InvalidFileHeader} ** 4 ++ + .{error.OutOfBounds} ** 2 ++ + .{error.InvalidFileHeader} ** 2 ++ + .{error.OutOfBounds} ** 4; + + for ( + 0..@sizeOf(elf.Elf64_Ehdr), + @as([]const ?error{ + InvalidFileHeader, + OutOfBounds, + VersionUnsupported, + }, &expected_results), + ) |offset, expected| { + const copy = try allocator.dupe(u8, bytes); + defer allocator.free(copy); + copy[offset] = 0xAF; + + var loader: BuiltinProgram = .{}; + var result = Elf.parse(allocator, copy, &loader, .{}); + defer if (result) |*parsed| parsed.deinit(allocator) else |_| {}; + + if (expected) |err| { + try expectError(err, result); + } else { + try std.testing.expect(!std.meta.isError(result)); + } + } +} diff --git a/src/svm/executable.zig b/src/svm/executable.zig index 9f7a65237..c413c8485 100644 --- a/src/svm/executable.zig +++ b/src/svm/executable.zig @@ -41,6 +41,13 @@ pub const Executable = struct { /// Takes ownership of the `Elf`. pub fn fromElf(elf: Elf) !Executable { + const text_section_addr = elf.getShdrByName(".text").?.sh_addr; + const text_vaddr = if (elf.version.enableElfVaddr() and + text_section_addr >= memory.RODATA_START) + text_section_addr + else + text_section_addr +| memory.RODATA_START; + return .{ .bytes = elf.bytes, .ro_section = elf.ro_section, @@ -48,7 +55,7 @@ pub const Executable = struct { .version = elf.version, .entry_pc = elf.entry_pc, .from_elf = true, - .text_vaddr = elf.getShdrByName(".text").?.sh_addr +| memory.PROGRAM_START, + .text_vaddr = text_vaddr, .function_registry = elf.function_registry, .config = elf.config, }; @@ -74,7 +81,7 @@ pub const Executable = struct { registry: *Registry(u64), config: Config, ) !Executable { - const version = config.minimum_version; + const version = config.maximum_version; const entry_pc = if (registry.lookupName("entrypoint")) |entry_pc| entry_pc.value @@ -95,12 +102,16 @@ pub const Executable = struct { .config = config, .function_registry = registry.*, .entry_pc = entry_pc, - .ro_section = .{ .borrowed = .{ .offset = 0, .start = 0, .end = 0 } }, + .ro_section = .{ .borrowed = .{ + .offset = memory.RODATA_START, + .start = 0, + .end = source.len, + } }, .from_elf = false, .text_vaddr = if (version.enableLowerBytecodeVaddr()) memory.BYTECODE_START else - memory.PROGRAM_START, + memory.RODATA_START, }; } @@ -123,7 +134,7 @@ pub const Executable = struct { .owned => |o| .{ o.offset, o.data }, .borrowed => |b| .{ b.offset, self.bytes[b.start..b.end] }, }; - return memory.Region.init(.constant, ro_data, memory.PROGRAM_START +| offset); + return memory.Region.init(.constant, ro_data, offset); } }; @@ -157,7 +168,7 @@ pub const Assembler = struct { source: []const u8, config: Config, ) !struct { Registry(u64), []const sbpf.Instruction } { - const version = config.minimum_version; + const version = config.maximum_version; var assembler: Assembler = .{ .source = source }; const statements = try assembler.tokenize(allocator); defer { @@ -170,10 +181,11 @@ pub const Assembler = struct { allocator.free(statements); } - var labels: std.StringHashMapUnmanaged(u64) = .{}; + var labels: std.StringHashMapUnmanaged(u32) = .{}; defer labels.deinit(allocator); var function_registry: Registry(u64) = .{}; + errdefer function_registry.deinit(allocator); try labels.put(allocator, "entrypoint", 0); var inst_ptr: u32 = 0; @@ -209,6 +221,10 @@ pub const Assembler = struct { const name = inst.name; const operands = inst.operands; + if (sbpf.Instruction.disallowed.get(name)) |since| { + if (version.gte(since)) return error.UnknownInstruction; + } + const bind = sbpf.Instruction.map.get(name) orelse std.debug.panic("invalid instruction: {s}", .{name}); @@ -265,13 +281,16 @@ pub const Assembler = struct { }; } }, - .jump_unconditional => .{ - .opcode = @enumFromInt(bind.opc), - .dst = .r0, - .src = .r0, - .off = @intCast(operands[0].integer), - .imm = 0, - }, + .jump_unconditional => if (operands[0] == .label) + @panic("TODO: jump_unconditional label") + else + .{ + .opcode = @enumFromInt(bind.opc), + .dst = .r0, + .src = .r0, + .off = @intCast(operands[0].integer), + .imm = 0, + }, .load_dw_imm => .{ .opcode = .ld_dw_imm, .dst = operands[0].register, @@ -280,21 +299,30 @@ pub const Assembler = struct { .imm = @truncate(@as(u64, @bitCast(operands[1].integer))), }, .load_reg => .{ - .opcode = @enumFromInt(bind.opc), + .opcode = if (version.moveMemoryInstructionClasses()) + @enumFromInt(bind.secondary) + else + @enumFromInt(bind.opc), .dst = operands[0].register, .src = operands[1].memory.base, .off = operands[1].memory.offset, .imm = 0, }, .store_reg => .{ - .opcode = @enumFromInt(bind.opc), + .opcode = if (version.moveMemoryInstructionClasses()) + @enumFromInt(bind.secondary) + else + @enumFromInt(bind.opc), .dst = operands[0].memory.base, .src = operands[1].register, .off = operands[0].memory.offset, .imm = 0, }, .store_imm => .{ - .opcode = @enumFromInt(bind.opc), + .opcode = if (version.moveMemoryInstructionClasses()) + @enumFromInt(bind.secondary) + else + @enumFromInt(bind.opc), .dst = operands[0].memory.base, .src = .r0, .off = operands[0].memory.offset, @@ -311,17 +339,24 @@ pub const Assembler = struct { const is_label = operands[0] == .label; if (is_label) { const label = operands[0].label; - const target_pc = labels.get(label) orelse + var target_pc: i64 = labels.get(label) orelse std.debug.panic("label not found: {s}", .{label}); + if (version.enableStaticSyscalls()) { + target_pc = target_pc - inst_ptr - 1; + } break :inst .{ .opcode = @enumFromInt(bind.opc), .dst = .r0, .src = .r1, .off = 0, - .imm = @intCast(target_pc), + .imm = @bitCast(@as(i32, @intCast(target_pc))), }; } else { const offset = operands[0].integer; + const instr_imm = if (version.enableStaticSyscalls()) + offset + else + offset + inst_ptr + 1; const target_pc: u32 = @intCast(offset + inst_ptr + 1); const label = try std.fmt.allocPrint( allocator, @@ -340,7 +375,7 @@ pub const Assembler = struct { .dst = .r0, .src = .r1, .off = 0, - .imm = target_pc, + .imm = @intCast(instr_imm), }; } }, @@ -472,7 +507,7 @@ pub fn Registry(T: type) type { const Self = @This(); /// Duplicates `name` to free later. - fn register( + pub fn register( self: *Self, allocator: std.mem.Allocator, key: u64, @@ -550,7 +585,9 @@ pub const BuiltinProgram = struct { pub const Config = struct { optimize_rodata: bool = true, reject_broken_elfs: bool = false, - minimum_version: sbpf.Version = .v3, + enable_symbol_and_section_labels: bool = false, + minimum_version: sbpf.Version = .v0, + maximum_version: sbpf.Version = .v3, stack_frame_size: u64 = 4096, max_call_depth: u64 = 64, diff --git a/src/svm/main.zig b/src/svm/main.zig index c2bc847f6..7ef6b2ecc 100644 --- a/src/svm/main.zig +++ b/src/svm/main.zig @@ -23,6 +23,7 @@ pub fn main() !void { var input_path: ?[]const u8 = null; var assemble: bool = false; + var version: sbpf.Version = .v3; var args = try std.process.argsWithAllocator(allocator); _ = args.next(); @@ -31,6 +32,12 @@ pub fn main() !void { assemble = true; continue; } + if (std.mem.eql(u8, arg, "-v")) { + const version_string = args.next() orelse fail("provide SBPF version", .{}); + version = std.meta.stringToEnum(sbpf.Version, version_string) orelse + fail("invalid SBPF version", .{}); + continue; + } if (input_path) |file| { fail("input file already given: {s}", .{file}); @@ -52,12 +59,15 @@ pub fn main() !void { defer loader.deinit(allocator); inline for (.{ - .{ "sol_log_", syscalls.log }, + .{ "log", syscalls.log }, .{ "sol_log_64_", syscalls.log64 }, .{ "sol_log_pubkey", syscalls.logPubkey }, .{ "sol_log_compute_units_", syscalls.logComputeUnits }, .{ "sol_memset_", syscalls.memset }, .{ "sol_memcpy_", syscalls.memcpy }, + .{ "sol_memcmp_", syscalls.memcmp }, + .{ "sol_poseidon", syscalls.poseidon }, + .{ "sol_panic_", syscalls.panic }, .{ "abort", syscalls.abort }, }) |entry| { const name, const function = entry; @@ -69,7 +79,8 @@ pub fn main() !void { } const config: Config = .{ - .minimum_version = if (assemble) .v3 else .v0, + .maximum_version = version, + .enable_symbol_and_section_labels = true, }; var executable = if (assemble) try Executable.fromAsm(allocator, bytes, config) diff --git a/src/svm/memory.zig b/src/svm/memory.zig index 6ef67437b..7f9be5f03 100644 --- a/src/svm/memory.zig +++ b/src/svm/memory.zig @@ -4,7 +4,7 @@ const sbpf = @import("sbpf.zig"); /// Virtual address of the bytecode region (in SBPFv3) pub const BYTECODE_START: u64 = 0x000000000; /// Virtual address of the readonly data region (also contains the bytecode until SBPFv3) -pub const PROGRAM_START: u64 = 0x100000000; +pub const RODATA_START: u64 = 0x100000000; /// Virtual address of the stack region pub const STACK_START: u64 = 0x200000000; /// Virtual address of the heap region @@ -109,7 +109,7 @@ pub const Region = struct { const host_slice = try self.getSlice(state); const begin_offset = vm_addr -| self.vm_addr_start; - if (begin_offset + len <= host_slice.len) { + if (try std.math.add(u64, begin_offset, len) <= host_slice.len) { return host_slice[begin_offset..][0..len]; } @@ -166,21 +166,21 @@ test "aligned vmap" { var stack_mem: [4]u8 = .{0xDD} ** 4; const m = try MemoryMap.init(&.{ - Region.init(.mutable, &program_mem, PROGRAM_START), + Region.init(.mutable, &program_mem, RODATA_START), Region.init(.constant, &stack_mem, STACK_START), }, .v0); try expectEqual( program_mem[0..1], - try m.vmap(.constant, PROGRAM_START, 1), + try m.vmap(.constant, RODATA_START, 1), ); try expectEqual( program_mem[0..3], - try m.vmap(.constant, PROGRAM_START, 3), + try m.vmap(.constant, RODATA_START, 3), ); try expectError( error.VirtualAccessTooLong, - m.vmap(.constant, PROGRAM_START, 5), + m.vmap(.constant, RODATA_START, 5), ); try expectError( @@ -202,25 +202,25 @@ test "aligned region" { var stack_mem: [4]u8 = .{0xDD} ** 4; const m = try MemoryMap.init(&.{ - Region.init(.mutable, &program_mem, PROGRAM_START), + Region.init(.mutable, &program_mem, RODATA_START), Region.init(.constant, &stack_mem, STACK_START), }, .v0); try expectError( error.AccessNotMapped, - m.region(PROGRAM_START - 1), + m.region(RODATA_START - 1), ); try expectEqual( &program_mem, - (try m.region(PROGRAM_START)).getSlice(.constant), + (try m.region(RODATA_START)).getSlice(.constant), ); try expectEqual( &program_mem, - (try m.region(PROGRAM_START + 3)).getSlice(.constant), + (try m.region(RODATA_START + 3)).getSlice(.constant), ); try expectError( error.AccessNotMapped, - m.region(PROGRAM_START + 4), + m.region(RODATA_START + 4), ); try expectError( @@ -249,7 +249,7 @@ test "invalid memory region" { error.InvalidMemoryRegion, MemoryMap.init(&.{ Region.init(.constant, &stack_mem, STACK_START), - Region.init(.mutable, &program_mem, PROGRAM_START), + Region.init(.mutable, &program_mem, RODATA_START), }, .v0), ); } diff --git a/src/svm/sbpf.zig b/src/svm/sbpf.zig index 78e5e5869..5a1922d55 100644 --- a/src/svm/sbpf.zig +++ b/src/svm/sbpf.zig @@ -52,18 +52,24 @@ pub const Version = enum(u32) { pub fn disableLDDW(version: Version) bool { return version.gte(.v2); } + pub fn moveMemoryInstructionClasses(version: Version) bool { + return version.gte(.v2); + } /// ... SIMD-0173 pub fn enableLe(version: Version) bool { return version == .v0; } /// Enable SIMD-0178: SBPF Static Syscalls - /// /// Enable SIMD-0179: SBPF stricter verification constraints pub fn enableStaticSyscalls(version: Version) bool { return version.gte(.v3); } /// Enable SIMD-0189: SBPF stricter ELF headers + pub fn enableStricterElfHeaders(version: Version) bool { + return version.gte(.v3); + } + /// ... SIMD-0189 pub fn enableLowerBytecodeVaddr(version: Version) bool { return version.gte(.v3); } @@ -79,7 +85,7 @@ pub const Version = enum(u32) { return version != .v0; } - fn gte(version: Version, other: Version) bool { + pub fn gte(version: Version, other: Version) bool { return @intFromEnum(version) >= @intFromEnum(other); } }; @@ -119,6 +125,11 @@ pub const Instruction = packed struct(u64) { /// bpf opcode: `stxdw [dst + off], src` /// `(dst + offset) as u64 = src`. st_dw_reg = stx | mem | dw, + /// BPF opcode: `ldxw dst, [src + off]` /// `dst = (src + off) as u32`. + ld_4b_reg = alu32 | x | @"4b", + /// BPF opcode: `stxw [dst + off], src` /// `(dst + offset) as u32 = src`. + st_4b_reg = alu64 | x | @"4b", + /// bpf opcode: `add32 dst, imm` /// `dst += imm`. add32_imm = alu32 | k | add, /// bpf opcode: `add32 dst, src` /// `dst += src`. @@ -343,6 +354,7 @@ pub const Instruction = packed struct(u64) { /// bpf opcode: `exit` /// `return r0`. /// valid only until sbpfv3 exit = jmp | exit_code, + @"return" = jmp | x | exit_code, _, pub fn isReg(opcode: OpCode) bool { @@ -372,6 +384,7 @@ pub const Instruction = packed struct(u64) { const Entry = struct { inst: InstructionType, opc: u8, + secondary: u8 = 0, }; const InstructionType = union(enum) { @@ -392,6 +405,33 @@ pub const Instruction = packed struct(u64) { no_operand, }; + /// Denotes at which version the instruction isn't allowed anymore. + /// + /// Example usage: + /// ```zig + /// const result = disallowed.get(inst) orelse ...; + /// if (current_version.gte(result)) @panic("not allowed") + /// ``` + pub const disallowed = std.StaticStringMap(Version).initComptime(&.{ + .{ "neg32", .v2 }, + .{ "neg64", .v2 }, + .{ "neg", .v2 }, + + .{ "mul32", .v2 }, + .{ "mul64", .v2 }, + .{ "mul", .v2 }, + + .{ "div32", .v2 }, + .{ "div64", .v2 }, + .{ "div", .v2 }, + + .{ "le32", .v2 }, + .{ "le64", .v2 }, + .{ "le16", .v2 }, + + .{ "lddw", .v2 }, + }); + pub const map = std.StaticStringMap(Entry).initComptime(&.{ // zig fmt: off .{ "mov" , .{ .inst = .alu_binary, .opc = mov | alu64 } }, @@ -490,20 +530,20 @@ pub const Instruction = packed struct(u64) { .{ "jslt" , .{ .inst = .jump_conditional, .opc = jslt | jmp } }, .{ "jsle" , .{ .inst = .jump_conditional, .opc = jsle | jmp } }, - .{ "ldxb" , .{ .inst = .load_reg, .opc = mem | ldx | b } }, - .{ "ldxh" , .{ .inst = .load_reg, .opc = mem | ldx | h } }, - .{ "ldxw" , .{ .inst = .load_reg, .opc = mem | ldx | w } }, - .{ "ldxdw" , .{ .inst = .load_reg, .opc = mem | ldx | dw } }, + .{ "ldxb" , .{ .inst = .load_reg, .opc = mem | ldx | b, .secondary = alu32 | x | @"1b" } }, + .{ "ldxh" , .{ .inst = .load_reg, .opc = mem | ldx | h, .secondary = alu32 | x | @"2b" } }, + .{ "ldxw" , .{ .inst = .load_reg, .opc = mem | ldx | w, .secondary = alu32 | x | @"4b" } }, + .{ "ldxdw", .{ .inst = .load_reg, .opc = mem | ldx | dw,.secondary = alu32 | x | @"8b" } }, - .{ "stb" , .{ .inst = .store_imm, .opc = mem | st | b } }, - .{ "sth" , .{ .inst = .store_imm, .opc = mem | st | h } }, - .{ "stw" , .{ .inst = .store_imm, .opc = mem | st | w } }, - .{ "stdw" , .{ .inst = .store_imm, .opc = mem | st | dw } }, + .{ "stb" , .{ .inst = .store_imm, .opc = mem | st | b, .secondary = alu64 | k | @"1b" } }, + .{ "sth" , .{ .inst = .store_imm, .opc = mem | st | h, .secondary = alu64 | k | @"2b" } }, + .{ "stw" , .{ .inst = .store_imm, .opc = mem | st | w, .secondary = alu64 | k | @"4b" } }, + .{ "stdw", .{ .inst = .store_imm, .opc = mem | st | dw, .secondary = alu64 | k | @"8b" } }, - .{ "stxb" , .{ .inst = .store_reg, .opc = mem | stx | b } }, - .{ "stxh" , .{ .inst = .store_reg, .opc = mem | stx | h } }, - .{ "stxw" , .{ .inst = .store_reg, .opc = mem | stx | w } }, - .{ "stxdw" , .{ .inst = .store_reg, .opc = mem | stx | dw } }, + .{ "stxb" , .{ .inst = .store_reg, .opc = mem | stx | b, .secondary = alu64 | x | @"1b" } }, + .{ "stxh" , .{ .inst = .store_reg, .opc = mem | stx | h, .secondary = alu64 | x | @"2b" } }, + .{ "stxw" , .{ .inst = .store_reg, .opc = mem | stx | w, .secondary = alu64 | x | @"4b" } }, + .{ "stxdw", .{ .inst = .store_reg, .opc = mem | stx | dw,.secondary = alu64 | x | @"8b" } }, .{ "be16", .{ .inst = .{.endian = 16 }, .opc = alu32 | x | end } }, .{ "be32", .{ .inst = .{.endian = 32 }, .opc = alu32 | x | end } }, @@ -514,6 +554,7 @@ pub const Instruction = packed struct(u64) { .{ "le64", .{ .inst = .{.endian = 64 }, .opc = alu32 | k | end } }, .{ "exit" , .{ .inst = .no_operand, .opc = jmp | exit_code } }, + .{ "return", .{ .inst = .no_operand, .opc = jmp | x | exit_code } }, .{ "lddw" , .{ .inst = .load_dw_imm, .opc = ld | imm | dw } }, .{ "call" , .{ .inst = .call_imm, .opc = jmp | call } }, .{ "callx" , .{ .inst = .call_reg, .opc = jmp | call | x } }, diff --git a/src/svm/syscalls.zig b/src/svm/syscalls.zig index 26a51d4b8..8a86c5088 100644 --- a/src/svm/syscalls.zig +++ b/src/svm/syscalls.zig @@ -1,8 +1,11 @@ const std = @import("std"); -const sig = @import("../sig.zig"); const builtin = @import("builtin"); -const Vm = @import("vm.zig").Vm; +const phash = @import("poseidon"); +const sig = @import("../sig.zig"); +const lib = @import("lib.zig"); +const testElfWithSyscalls = @import("tests.zig").testElfWithSyscalls; +const Vm = lib.Vm; const Pubkey = sig.core.Pubkey; pub const Syscall = struct { @@ -16,22 +19,19 @@ pub const Error = error{ SyscallAbort, AccessViolation, VirtualAccessTooLong, + Overflow, + InvalidLength, + NonCanonical, + Unexpected, }; // logging -pub fn printString(vm: *Vm) Error!void { - const vm_addr = vm.registers.get(.r1); - const len = vm.registers.get(.r2); - const host_addr = try vm.memory_map.vmap(.constant, vm_addr, len); - const string = std.mem.sliceTo(host_addr, 0); - if (!builtin.is_test) std.debug.print("{s}", .{string}); -} - pub fn log(vm: *Vm) Error!void { const vm_addr = vm.registers.get(.r1); const len = vm.registers.get(.r2); const host_addr = try vm.memory_map.vmap(.constant, vm_addr, len); - std.debug.print("log: {s}\n", .{host_addr}); + const string = std.mem.sliceTo(host_addr, 0); + if (!builtin.is_test) std.debug.print("{d}\n", .{string}); } pub fn log64(vm: *Vm) Error!void { @@ -78,7 +78,92 @@ pub fn memcpy(vm: *Vm) Error!void { @memcpy(dst_host, src_host); } +pub fn memcmp(vm: *Vm) Error!void { + const a_addr = vm.registers.get(.r1); + const b_addr = vm.registers.get(.r2); + const n = vm.registers.get(.r3); + const cmp_result_addr = vm.registers.get(.r4); + + const a = try vm.memory_map.vmap(.constant, a_addr, n); + const b = try vm.memory_map.vmap(.constant, b_addr, n); + const cmp_result_slice = try vm.memory_map.vmap( + .mutable, + cmp_result_addr, + @sizeOf(u32), + ); + const cmp_result: *align(1) u32 = @ptrCast(cmp_result_slice.ptr); + + const result = std.mem.order(u8, a, b); + cmp_result.* = @intFromEnum(result); +} + +// hashing +const Parameters = enum(u64) { + Bn254X5 = 0, +}; + +const Slice = extern struct { + addr: [*]const u8, + len: u64, +}; + +pub fn poseidon(vm: *Vm) Error!void { + // const parameters: Parameters = @enumFromInt(vm.registers.get(.r1)); + const endianness: std.builtin.Endian = @enumFromInt(vm.registers.get(.r2)); + const vals_addr = vm.registers.get(.r3); + const vals_len = vm.registers.get(.r4); + const result_addr = vm.registers.get(.r5); + + if (vals_len > 12) { + return error.InvalidLength; + } + + const hash_result = try vm.memory_map.vmap(.mutable, result_addr, 32); + const input_bytes = try vm.memory_map.vmap( + .constant, + vals_addr, + vals_len * @sizeOf(Slice), + ); + const inputs = std.mem.bytesAsSlice(Slice, input_bytes); + + var hasher = phash.Hasher.init(endianness); + for (inputs) |input| { + var slice = try vm.memory_map.vmap( + .constant, + @intFromPtr(input.addr), + input.len, + ); + try hasher.append(slice[0..32]); + } + const result = try hasher.finish(); + @memcpy(hash_result, &result); +} + // special pub fn abort(_: *Vm) Error!void { return error.SyscallAbort; } + +pub fn panic(vm: *Vm) Error!void { + const file = vm.registers.get(.r1); + const len = vm.registers.get(.r2); + // const line = vm.registers.get(.r3); + // const column = vm.registers.get(.r4); + + const message = try vm.memory_map.vmap(.constant, file, len); + if (!builtin.is_test) + std.debug.print("panic: {s}\n", .{message}); + return error.SyscallAbort; +} + +test poseidon { + try testElfWithSyscalls( + .{}, + sig.ELF_DATA_DIR ++ "poseidon_test.so", + &.{ + .{ .name = "sol_poseidon", .builtin_fn = poseidon }, + .{ .name = "log", .builtin_fn = log }, + }, + 0, + ); +} diff --git a/src/svm/tests.zig b/src/svm/tests.zig index 80b8950ea..d82d42ac3 100644 --- a/src/svm/tests.zig +++ b/src/svm/tests.zig @@ -36,7 +36,7 @@ fn testAsmWithMemory( defer allocator.free(stack_memory); const m = try MemoryMap.init(&.{ - Region.init(.constant, &.{}, memory.PROGRAM_START), + Region.init(.constant, &.{}, memory.RODATA_START), Region.init(.mutable, stack_memory, memory.STACK_START), Region.init(.constant, &.{}, memory.HEAP_START), Region.init(.mutable, mutable, memory.INPUT_START), @@ -55,7 +55,7 @@ test "basic mov" { \\entrypoint: \\ mov r1, 1 \\ mov r0, r1 - \\ exit + \\ return , 1); } @@ -63,7 +63,7 @@ test "mov32 imm large" { try testAsm(.{}, \\entrypoint: \\ mov32 r0, -1 - \\ exit + \\ return , 0xFFFFFFFF); } @@ -72,7 +72,7 @@ test "mov large" { \\entrypoint: \\ mov32 r1, -1 \\ mov32 r0, r1 - \\ exit + \\ return , 0xFFFFFFFF); } @@ -85,7 +85,7 @@ test "bounce" { \\ mov r8, r7 \\ mov r9, r8 \\ mov r0, r9 - \\ exit + \\ return , 1); } @@ -96,12 +96,12 @@ test "add32" { \\ mov32 r1, 2 \\ add32 r0, 1 \\ add32 r0, r1 - \\ exit + \\ return , 3); } test "add64" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x300000fff \\ add r0, -1 @@ -132,7 +132,7 @@ test "alu32 logic" { \\ rsh32 r0, r7 \\ xor32 r0, 0x03 \\ xor32 r0, r2 - \\ exit + \\ return , 0x11); } @@ -157,7 +157,7 @@ test "alu32 arithmetic" { \\ lmul32 r0, r3 \\ udiv32 r0, 2 \\ udiv32 r0, r4 - \\ exit + \\ return , 110); } @@ -186,12 +186,12 @@ test "alu64 logic" { \\ rsh r0, r7 \\ xor r0, 0x03 \\ xor r0, r2 - \\ exit + \\ return , 0x11); } test "mul32 imm" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 3 \\ mul32 r0, 4 @@ -200,7 +200,7 @@ test "mul32 imm" { } test "mul32 reg" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 3 \\ mov r1, 4 @@ -210,7 +210,7 @@ test "mul32 reg" { } test "mul32 overflow" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0x40000001 \\ mov r1, 4 @@ -220,7 +220,7 @@ test "mul32 overflow" { } test "mul64 imm" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0x40000001 \\ mul r0, 4 @@ -229,7 +229,7 @@ test "mul64 imm" { } test "mul64 reg" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0x40000001 \\ mov r1, 4 @@ -239,7 +239,7 @@ test "mul64 reg" { } test "mul32 negative" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, -1 \\ mul32 r0, 4 @@ -248,7 +248,7 @@ test "mul32 negative" { } test "div32 imm" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x10000000c \\ div32 r0, 4 @@ -257,7 +257,7 @@ test "div32 imm" { } test "div32 reg" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 12 \\ lddw r1, 0x100000004 @@ -267,7 +267,7 @@ test "div32 reg" { } test "div32 small" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x10000000c \\ mov r1, 4 @@ -277,7 +277,7 @@ test "div32 small" { } test "div64 imm" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0xc \\ lsh r0, 32 @@ -287,7 +287,7 @@ test "div64 imm" { } test "div64 reg" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0xc \\ lsh r0, 32 @@ -298,7 +298,7 @@ test "div64 reg" { } test "div division by zero" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 1 \\ mov32 r1, 0 @@ -308,7 +308,7 @@ test "div division by zero" { } test "div32 division by zero" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 1 \\ mov32 r1, 0 @@ -318,7 +318,7 @@ test "div32 division by zero" { } test "neg32" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 2 \\ neg32 r0 @@ -327,7 +327,7 @@ test "neg32" { } test "neg64" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 2 \\ neg r0 @@ -336,17 +336,32 @@ test "neg64" { } test "neg invalid on v3" { - try testAsm(.{}, - \\entrypoint: - \\ neg32 r0 - \\ exit - , error.UnknownInstruction); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\entrypoint: + \\ neg32 r0 + \\ return + , 0), + ); - try testAsm(.{}, - \\entrypoint: - \\ neg64 r0 - \\ exit - , error.UnknownInstruction); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\entrypoint: + \\ neg64 r0 + \\ return + , 0), + ); + + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\entrypoint: + \\ neg r0 + \\ return + , 0), + ); } test "sub32 imm" { @@ -354,7 +369,7 @@ test "sub32 imm" { \\entrypoint: \\ mov32 r0, 3 \\ sub32 r0, 1 - \\ exit + \\ return , 0xFFFFFFFFFFFFFFFE); } @@ -364,7 +379,7 @@ test "sub32 reg" { \\ mov32 r0, 4 \\ mov32 r1, 2 \\ sub32 r0, r1 - \\ exit + \\ return , 2); } @@ -373,7 +388,7 @@ test "sub64 imm" { \\entrypoint: \\ mov r0, 3 \\ sub r0, 1 - \\ exit + \\ return , 0xFFFFFFFFFFFFFFFE); } @@ -382,7 +397,7 @@ test "sub64 imm negative" { \\entrypoint: \\ mov r0, 3 \\ sub r0, -1 - \\ exit + \\ return , 0xFFFFFFFFFFFFFFFC); } @@ -392,12 +407,12 @@ test "sub64 reg" { \\ mov r0, 4 \\ mov r1, 2 \\ sub r0, r1 - \\ exit + \\ return , 2); } test "mod32" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 5748 \\ mod32 r0, 92 @@ -408,7 +423,7 @@ test "mod32" { } test "mod32 overflow" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x100000003 \\ mod32 r0, 3 @@ -417,7 +432,7 @@ test "mod32 overflow" { } test "mod32 all" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, -1316649930 \\ lsh r0, 32 @@ -432,7 +447,7 @@ test "mod32 all" { } test "mod64 divide by zero" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 1 \\ mov32 r1, 0 @@ -442,7 +457,7 @@ test "mod64 divide by zero" { } test "mod32 divide by zero" { - try testAsm(.{}, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov32 r0, 1 \\ mov32 r1, 0 @@ -458,7 +473,7 @@ test "arsh32 high shift" { \\ mov32 r1, 0x00000001 \\ hor64 r1, 0x00000001 \\ arsh32 r0, r1 - \\ exit + \\ return , 0x4); } @@ -468,7 +483,7 @@ test "arsh32 imm" { \\ mov32 r0, 0xf8 \\ lsh32 r0, 28 \\ arsh32 r0, 16 - \\ exit + \\ return , 0xffff8000); } @@ -479,7 +494,7 @@ test "arsh32 reg" { \\ mov32 r1, 16 \\ lsh32 r0, 28 \\ arsh32 r0, r1 - \\ exit + \\ return , 0xffff8000); } @@ -491,7 +506,7 @@ test "arsh64" { \\ arsh r0, 55 \\ mov32 r1, 5 \\ arsh r0, r1 - \\ exit + \\ return , 0xfffffffffffffff8); } @@ -500,12 +515,12 @@ test "hor64" { \\entrypoint: \\ hor64 r0, 0x10203040 \\ hor64 r0, 0x01020304 - \\ exit + \\ return , 0x1122334400000000); } test "lddw" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x1122334455667788 \\ exit @@ -513,7 +528,7 @@ test "lddw" { } test "lddw bottom" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ lddw r0, 0x0000000080000000 \\ exit @@ -521,7 +536,7 @@ test "lddw bottom" { } test "lddw logic" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov r0, 0 \\ mov r1, 0 @@ -537,16 +552,19 @@ test "lddw logic" { } test "lddw invalid on v3" { - try testAsm(.{}, - \\entrypoint: - \\ lddw r0, 0x1122334455667788 - \\ exit - , error.UnknownInstruction); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\entrypoint: + \\ lddw r0, 0x1122334455667788 + \\ return + , 0), + ); } test "le16" { try testAsmWithMemory( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, \\ ldxh r0, [r1] \\ le16 r0 \\ exit @@ -558,7 +576,7 @@ test "le16" { test "le16 high" { try testAsmWithMemory( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, \\ ldxdw r0, [r1] \\ le16 r0 \\ exit @@ -570,7 +588,7 @@ test "le16 high" { test "le32" { try testAsmWithMemory( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, \\ ldxw r0, [r1] \\ le32 r0 \\ exit @@ -582,7 +600,7 @@ test "le32" { test "le32 high" { try testAsmWithMemory( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, \\ ldxdw r0, [r1] \\ le32 r0 \\ exit @@ -594,7 +612,7 @@ test "le32 high" { test "le64" { try testAsmWithMemory( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, \\ ldxdw r0, [r1] \\ le64 r0 \\ exit @@ -605,20 +623,28 @@ test "le64" { } test "le invalid on v3" { - try testAsm(.{}, - \\ le16 r0 - \\ exit - , error.UnknownInstruction); - - try testAsm(.{}, - \\ le32 r0 - \\ exit - , error.UnknownInstruction); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\ le16 r0 + \\ return + , 0), + ); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\ le32 r0 + \\ return + , 0), + ); - try testAsm(.{}, - \\ le64 r0 - \\ exit - , error.UnknownInstruction); + try expectEqual( + error.UnknownInstruction, + testAsm(.{}, + \\ le64 r0 + \\ return + , 0), + ); } test "be16" { @@ -627,7 +653,7 @@ test "be16" { \\entrypoint: \\ ldxh r0, [r1] \\ be16 r0 - \\ exit + \\ return , &.{ 0x11, 0x22 }, 0x1122, @@ -640,7 +666,7 @@ test "be16 high" { \\entrypoint: \\ ldxdw r0, [r1] \\ be16 r0 - \\ exit + \\ return , &.{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, 0x1122, @@ -653,7 +679,7 @@ test "be32" { \\entrypoint: \\ ldxw r0, [r1] \\ be32 r0 - \\ exit + \\ return , &.{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, 0x11223344, @@ -666,7 +692,7 @@ test "be32 high" { \\entrypoint: \\ ldxdw r0, [r1] \\ be32 r0 - \\ exit + \\ return , &.{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, 0x11223344, @@ -679,7 +705,7 @@ test "be64" { \\entrypoint: \\ ldxdw r0, [r1] \\ be64 r0 - \\ exit + \\ return , &.{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, 0x1122334455667788, @@ -692,7 +718,7 @@ test "lsh64 reg" { \\ mov r0, 0x1 \\ mov r7, 4 \\ lsh r0, r7 - \\ exit + \\ return , 0x10); } @@ -702,7 +728,7 @@ test "rhs32 imm" { \\ xor r0, r0 \\ add r0, -1 \\ rsh32 r0, 8 - \\ exit + \\ return , 0x00ffffff); } @@ -712,7 +738,7 @@ test "rhs64 reg" { \\ mov r0, 0x10 \\ mov r7, 4 \\ rsh r0, r7 - \\ exit + \\ return , 0x1); } @@ -721,7 +747,7 @@ test "ldxb" { .{}, \\entrypoint: \\ ldxb r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0x11, 0xcc, 0xdd }, 0x11, @@ -733,7 +759,7 @@ test "ldxh" { .{}, \\entrypoint: \\ ldxh r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd }, 0x2211, @@ -745,7 +771,7 @@ test "ldxw" { .{}, \\entrypoint: \\ ldxw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0xcc, 0xdd }, 0x44332211, @@ -759,7 +785,7 @@ test "ldxw same reg" { \\ mov r0, r1 \\ sth [r0], 0x1234 \\ ldxh r0, [r0] - \\ exit + \\ return , &.{ 0xff, 0xff }, 0x1234, @@ -771,7 +797,7 @@ test "ldxdw" { .{}, \\entrypoint: \\ ldxdw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, @@ -786,7 +812,7 @@ test "ldxdw oob" { .{}, \\entrypoint: \\ ldxdw r0, [r1+6] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, @@ -801,7 +827,7 @@ test "ldxdw oom" { .{}, \\entrypoint: \\ ldxdw r0, [r1+6] - \\ exit + \\ return , &.{}, error.AccessNotMapped, @@ -842,7 +868,7 @@ test "ldxb all" { \\ or r0, r7 \\ or r0, r8 \\ or r0, r9 - \\ exit + \\ return , &.{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, @@ -896,7 +922,7 @@ test "ldxh all" { \\ or r0, r7 \\ or r0, r8 \\ or r0, r9 - \\ exit + \\ return , &.{ 0x00, 0x00, 0x00, 0x01, @@ -941,7 +967,7 @@ test "ldxh all" { \\ or r0, r7 \\ or r0, r8 \\ or r0, r9 - \\ exit + \\ return , &.{ 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, @@ -986,7 +1012,7 @@ test "ldxw all" { \\ or r0, r7 \\ or r0, r8 \\ or r0, r9 - \\ exit + \\ return , &.{ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, @@ -1005,7 +1031,7 @@ test "stb" { \\entrypoint: \\ stb [r1+2], 0x11 \\ ldxb r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xcc, 0xdd }, 0x11, @@ -1018,7 +1044,7 @@ test "sth" { \\entrypoint: \\ sth [r1+2], 0x2211 \\ ldxh r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, @@ -1034,7 +1060,7 @@ test "stw" { \\entrypoint: \\ stw [r1+2], 0x44332211 \\ ldxw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, @@ -1050,7 +1076,7 @@ test "stdw" { \\entrypoint: \\ stdw [r1+2], 0x44332211 \\ ldxdw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, @@ -1067,7 +1093,7 @@ test "stxb" { \\ mov32 r2, 0x11 \\ stxb [r1+2], r2 \\ ldxb r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xcc, 0xdd }, 0x11, @@ -1081,7 +1107,7 @@ test "stxh" { \\ mov32 r2, 0x2211 \\ stxh [r1+2], r2 \\ ldxh r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, 0xcc, 0xdd }, 0x2211, @@ -1095,7 +1121,7 @@ test "stxw" { \\ mov32 r2, 0x44332211 \\ stxw [r1+2], r2 \\ ldxw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd }, 0x44332211, @@ -1111,7 +1137,7 @@ test "stxdw" { \\ or r2, 0x44332211 \\ stxdw [r1+2], r2 \\ ldxdw r0, [r1+2] - \\ exit + \\ return , &.{ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, @@ -1143,7 +1169,7 @@ test "stxb all" { \\ stxb [r1+7], r8 \\ ldxdw r0, [r1] \\ be64 r0 - \\ exit + \\ return , &.{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, 0xf0f2f3f4f5f6f7f8, @@ -1159,7 +1185,7 @@ test "stxb all" { \\ stxb [r0+1], r9 \\ ldxh r0, [r0] \\ be16 r0 - \\ exit + \\ return , &.{ 0xff, 0xff }, 0xf1f9, @@ -1190,7 +1216,7 @@ test "stxb chain" { \\ ldxb r1, [r0+8] \\ stxb [r0+9], r1 \\ ldxb r0, [r0+9] - \\ exit + \\ return , &.{ 0x2a, 0x00, 0x00, 0x00, 0x00, @@ -1200,31 +1226,31 @@ test "stxb chain" { ); } -test "exit without value" { +test "return without value" { try testAsm( .{}, \\entrypoint: - \\ exit + \\ return , 0x0, ); } -test "exit" { +test "return" { try testAsm(.{}, \\entrypoint: \\ mov r0, 0 - \\ exit + \\ return , 0x0); } -test "early exit" { +test "early return" { try testAsm(.{}, \\entrypoint: \\ mov r0, 3 - \\ exit + \\ return \\ mov r0, 4 - \\ exit + \\ return , 3); } @@ -1235,7 +1261,7 @@ test "ja" { \\ mov r0, 1 \\ ja +1 \\ mov r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1252,7 +1278,7 @@ test "jeq imm" { \\ mov32 r1, 0xb \\ jeq r1, 0xb, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1270,7 +1296,7 @@ test "jeq reg" { \\ mov32 r1, 0xb \\ jeq r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1287,7 +1313,7 @@ test "jge imm" { \\ mov32 r1, 0xc \\ jge r1, 0xb, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1305,7 +1331,7 @@ test "jge reg" { \\ mov32 r1, 0xb \\ jge r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1319,11 +1345,11 @@ test "jle imm" { \\ mov32 r1, 5 \\ jle r1, 4, +1 \\ jle r1, 6, +1 - \\ exit + \\ return \\ jle r1, 5, +1 - \\ exit + \\ return \\ mov32 r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1339,11 +1365,11 @@ test "jle reg" { \\ mov r3, 6 \\ jle r1, r2, +2 \\ jle r1, r1, +1 - \\ exit + \\ return \\ jle r1, r3, +1 - \\ exit + \\ return \\ mov r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1358,9 +1384,9 @@ test "jgt imm" { \\ jgt r1, 6, +2 \\ jgt r1, 5, +1 \\ jgt r1, 4, +1 - \\ exit + \\ return \\ mov32 r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1377,9 +1403,9 @@ test "jgt reg" { \\ jgt r1, r2, +2 \\ jgt r1, r1, +1 \\ jgt r1, r3, +1 - \\ exit + \\ return \\ mov r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1394,9 +1420,9 @@ test "jlt imm" { \\ jlt r1, 4, +2 \\ jlt r1, 5, +1 \\ jlt r1, 6, +1 - \\ exit + \\ return \\ mov32 r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1413,9 +1439,9 @@ test "jlt reg" { \\ jlt r1, r2, +2 \\ jlt r1, r1, +1 \\ jlt r1, r3, +1 - \\ exit + \\ return \\ mov r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1429,9 +1455,9 @@ test "jlt extend" { \\ add r0, -3 \\ jlt r0, -2, +2 \\ mov r0, 1 - \\ exit + \\ return \\ mov r0, 2 - \\ exit + \\ return , 2, ); @@ -1448,7 +1474,7 @@ test "jne imm" { \\ mov32 r1, 0xa \\ jne r1, 0xb, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1466,7 +1492,7 @@ test "jne reg" { \\ mov32 r1, 0xa \\ jne r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1483,7 +1509,7 @@ test "jset imm" { \\ mov32 r1, 0x9 \\ jset r1, 0x8, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1501,7 +1527,7 @@ test "jset reg" { \\ mov32 r1, 0x9 \\ jset r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1519,7 +1545,7 @@ test "jsge imm" { \\ mov r1, -1 \\ jsge r1, -1, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1539,7 +1565,7 @@ test "jsge reg" { \\ mov r1, r2 \\ jsge r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1553,11 +1579,11 @@ test "jsle imm" { \\ mov r1, -2 \\ jsle r1, -3, +1 \\ jsle r1, -1, +1 - \\ exit + \\ return \\ mov32 r0, 1 \\ jsle r1, -2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1573,12 +1599,12 @@ test "jsle reg" { \\ mov32 r3, 0 \\ jsle r1, r2, +1 \\ jsle r1, r3, +1 - \\ exit + \\ return \\ mov32 r0, 1 \\ mov r1, r2 \\ jsle r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1595,7 +1621,7 @@ test "jsgt imm" { \\ mov32 r1, 0 \\ jsgt r1, -1, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1613,7 +1639,7 @@ test "jsgt reg" { \\ mov32 r1, 0 \\ jsgt r1, r2, +1 \\ mov32 r0, 2 - \\ exit + \\ return , 0x1, ); @@ -1628,9 +1654,9 @@ test "jslt imm" { \\ jslt r1, -3, +2 \\ jslt r1, -2, +1 \\ jslt r1, -1, +1 - \\ exit + \\ return \\ mov32 r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1647,9 +1673,9 @@ test "jslt reg" { \\ jslt r1, r1, +2 \\ jslt r1, r2, +1 \\ jslt r1, r3, +1 - \\ exit + \\ return \\ mov32 r0, 1 - \\ exit + \\ return , 0x1, ); @@ -1668,7 +1694,7 @@ test "lmul loop" { \\ lmul r0, 0x7 \\ add r1, -1 \\ jne r1, 0x0, -3 - \\ exit + \\ return , 0x75db9c97, ); @@ -1718,7 +1744,7 @@ test "lmul128" { \\ rsh64 r4, 0x20 \\ or64 r0, r4 \\ stxdw [r1+0x0], r0 - \\ exit + \\ return , &(.{0} ** 16), 600); } @@ -1740,7 +1766,7 @@ test "prime" { \\ sub r4, r3 \\ mov r0, 0x0 \\ jne r4, 0x0, -10 - \\ exit + \\ return , 1); } @@ -1760,7 +1786,7 @@ test "subnet" { \\ and r1, 0xffffff \\ jeq r1, 0x1a8c0, +1 \\ mov r0, 0x0 - \\ exit + \\ return , &.{ 0x00, 0x00, 0xc0, 0x9f, 0xa0, 0x97, 0x00, 0xa0, 0xcc, 0x3b, 0xbf, 0xfa, 0x08, 0x00, 0x45, 0x10, 0x00, 0x3c, 0x46, 0x3c, @@ -1863,7 +1889,7 @@ test "pqr" { std.mem.writeInt(u32, program[36..][0..4], @truncate(src), .little); program[32] = @intFromEnum(opc); - const config: Config = .{ .minimum_version = .v3 }; + const config: Config = .{ .maximum_version = .v2 }; var registry: lib.Registry(u64) = .{}; defer registry.deinit(allocator); @@ -1876,7 +1902,7 @@ test "pqr" { ); var loader: BuiltinProgram = .{}; - const map = try MemoryMap.init(&.{}, .v3); + const map = try MemoryMap.init(&.{}, .v2); var vm = try Vm.init(allocator, &executable, map, &loader, 0); defer vm.deinit(); @@ -1904,7 +1930,7 @@ test "pqr divide by zero" { }) |opcode| { program[8] = @intFromEnum(opcode); - const config: Config = .{ .minimum_version = .v3 }; + const config: Config = .{ .maximum_version = .v2 }; var registry: lib.Registry(u64) = .{}; defer registry.deinit(allocator); @@ -1938,21 +1964,21 @@ test "stack1" { \\ mov r2, r10 \\ add r2, r1 \\ ldxdw r0, [r2-16] - \\ exit + \\ return , 0xcd, ); } -test "entrypoint exit" { +test "entrypoint return" { try testAsm(.{}, \\entrypoint: \\ call function_foo \\ mov r0, 42 - \\ exit + \\ return \\function_foo: \\ mov r0, 12 - \\ exit + \\ return , 42); } @@ -1963,12 +1989,12 @@ test "call depth in bounds" { \\ mov r2, 63 \\ call function_foo \\ mov r0, r1 - \\ exit + \\ return \\function_foo: \\ add r1, 1 \\ jeq r1, r2, +1 \\ call function_foo - \\ exit + \\ return , 63); } @@ -1979,17 +2005,17 @@ test "call depth out of bounds" { \\ mov r2, 64 \\ call function_foo \\ mov r0, r1 - \\ exit + \\ return \\function_foo: \\ add r1, 1 \\ jeq r1, r2, +1 \\ call function_foo - \\ exit + \\ return , error.CallDepthExceeded); } test "callx imm" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov64 r0, 0x0 \\ mov64 r8, 0x1 @@ -2004,7 +2030,7 @@ test "callx imm" { } test "callx out of bounds low" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ mov64 r0, 0x3 \\ callx r0 @@ -2019,7 +2045,7 @@ test "callx out of bounds high" { \\ lsh64 r0, 0x20 \\ or64 r0, 0x3 \\ callx r0 - \\ exit + \\ return , error.PcOutOfBounds); } @@ -2029,7 +2055,7 @@ test "callx out of bounds max" { \\ mov64 r0, -0x8 \\ hor64 r0, -0x1 \\ callx r0 - \\ exit + \\ return , error.PcOutOfBounds); } @@ -2045,18 +2071,18 @@ test "call bpf 2 bpf" { \\ add64 r0, r7 \\ add64 r0, r8 \\ add64 r0, r9 - \\ exit + \\ return \\function_foo: \\ mov64 r6, 0x00 \\ mov64 r7, 0x00 \\ mov64 r8, 0x00 \\ mov64 r9, 0x00 - \\ exit + \\ return , 255); } test "fixed stack out of bounds" { - try testAsm(.{ .minimum_version = .v0 }, + try testAsm(.{ .maximum_version = .v0 }, \\entrypoint: \\ stb [r10-0x4000], 0 \\ exit @@ -2071,29 +2097,29 @@ test "dynamic frame pointer" { \\ stxdw [r10+8], r10 \\ call function_foo \\ ldxdw r0, [r10+8] - \\ exit + \\ return \\function_foo: - \\ exit + \\ return , memory.STACK_START + config.stackSize() - 64); try testAsm(config, \\entrypoint: \\ add r10, -64 \\ call function_foo - \\ exit + \\ return \\function_foo: \\ mov r0, r10 - \\ exit + \\ return , memory.STACK_START + config.stackSize() - 64); try testAsm(config, \\entrypoint: \\ call function_foo \\ mov r0, r10 - \\ exit + \\ return \\function_foo: \\ add r10, -64 - \\ exit + \\ return , memory.STACK_START + config.stackSize()); } @@ -2101,7 +2127,7 @@ fn testElf(config: Config, path: []const u8, expected: anytype) !void { return testElfWithSyscalls(config, path, &.{}, expected); } -fn testElfWithSyscalls( +pub fn testElfWithSyscalls( config: Config, path: []const u8, extra_syscalls: []const syscalls.Syscall, @@ -2152,9 +2178,9 @@ test "BPF_64_64 sbpfv0" { // [ 1] .text PROGBITS 0000000000000120 000120 000018 00 AX 0 0 8 // prints the address of the first byte in the .text section try testElf( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, sig.ELF_DATA_DIR ++ "reloc_64_64_sbpfv0.so", - memory.PROGRAM_START + 0x120, + memory.RODATA_START + 0x120, ); } @@ -2163,7 +2189,7 @@ test "BPF_64_64" { try testElf( .{}, sig.ELF_DATA_DIR ++ "reloc_64_64.so", - memory.PROGRAM_START, + memory.BYTECODE_START, ); } @@ -2171,32 +2197,32 @@ test "BPF_64_RELATIVE data sbpv0" { // 4: 0000000000000140 8 OBJECT LOCAL DEFAULT 3 reloc_64_relative_data.DATA // 0000000000000140 0000000000000008 R_BPF_64_RELATIVE try testElf( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, sig.ELF_DATA_DIR ++ "reloc_64_relative_data_sbpfv0.so", - memory.PROGRAM_START + 0x140, + memory.RODATA_START + 0x140, ); } test "BPF_64_RELATIVE data" { - // 0000000100000020 0000000000000008 R_SBF_64_RELATIVE + // 2: 0000000100000008 8 OBJECT LOCAL DEFAULT 2 reloc_64_relative_data.DATA try testElf( .{}, sig.ELF_DATA_DIR ++ "reloc_64_relative_data.so", - memory.PROGRAM_START + 0x20, + memory.RODATA_START + 0x8, ); } test "BPF_64_RELATIVE sbpv0" { try testElf( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, sig.ELF_DATA_DIR ++ "reloc_64_relative_sbpfv0.so", - memory.PROGRAM_START + 0x138, + memory.RODATA_START + 0x138, ); } test "load elf rodata sbpfv0" { try testElf( - .{ .minimum_version = .v0 }, + .{ .maximum_version = .v0 }, sig.ELF_DATA_DIR ++ "rodata_section_sbpfv0.so", 42, ); @@ -2204,7 +2230,7 @@ test "load elf rodata sbpfv0" { test "load elf rodata" { try testElf( - .{}, + .{ .optimize_rodata = false }, sig.ELF_DATA_DIR ++ "rodata_section.so", 42, ); @@ -2212,10 +2238,10 @@ test "load elf rodata" { test "syscall reloc 64_32" { try testElfWithSyscalls( - .{}, - sig.ELF_DATA_DIR ++ "syscall_reloc_64_32.so", - &.{.{ .name = "log", .builtin_fn = syscalls.printString }}, - error.UnresolvedFunction, + .{ .maximum_version = .v0 }, + sig.ELF_DATA_DIR ++ "syscall_reloc_64_32_sbpfv0.so", + &.{.{ .name = "log", .builtin_fn = syscalls.log }}, + 0, ); } @@ -2223,7 +2249,7 @@ test "static syscall" { try testElfWithSyscalls( .{}, sig.ELF_DATA_DIR ++ "syscall_static.so", - &.{.{ .name = "log", .builtin_fn = syscalls.printString }}, + &.{.{ .name = "log", .builtin_fn = syscalls.log }}, 0, ); } @@ -2237,13 +2263,22 @@ test "struct func pointer" { ); } +test "struct func pointer sbpfv0" { + try testElfWithSyscalls( + .{ .maximum_version = .v0 }, + sig.ELF_DATA_DIR ++ "struct_func_pointer_sbpfv0.so", + &.{}, + 0x0102030405060708, + ); +} + test "data section" { // [ 6] .data PROGBITS 0000000000000250 000250 000004 00 WA 0 0 4 try expectEqual( error.WritableSectionsNotSupported, testElfWithSyscalls( - .{}, - sig.ELF_DATA_DIR ++ "data_section.so", + .{ .maximum_version = .v0 }, + sig.ELF_DATA_DIR ++ "data_section_sbpfv0.so", &.{}, 0, ), @@ -2255,8 +2290,8 @@ test "bss section" { try expectEqual( error.WritableSectionsNotSupported, testElfWithSyscalls( - .{}, - sig.ELF_DATA_DIR ++ "bss_section.so", + .{ .maximum_version = .v0 }, + sig.ELF_DATA_DIR ++ "bss_section_sbpfv0.so", &.{}, 0, ), diff --git a/src/svm/vm.zig b/src/svm/vm.zig index 5de894249..ef1518940 100644 --- a/src/svm/vm.zig +++ b/src/svm/vm.zig @@ -79,6 +79,84 @@ pub const Vm = struct { const inst = instructions[pc]; const opcode = inst.opcode; + if (version.moveMemoryInstructionClasses()) { + switch (opcode) { + // reserved opcodes + .mul32_imm, + .mod32_imm, + .div32_imm, + => return error.UnknownInstruction, + + inline + // LD_1B_REG + .mul32_reg, + // LD_2B_REG + .div32_reg, + // LD_4B_REG + .ld_4b_reg, + // LD_8B_REG + .mod32_reg, + => |tag| { + const T = switch (tag) { + .mul32_reg => u8, + .div32_reg => u16, + .ld_4b_reg => u32, + .mod32_reg => u64, + else => unreachable, + }; + const base_address: i64 = @bitCast(registers.get(inst.src)); + const vm_addr: u64 = @bitCast(base_address +% @as(i64, inst.off)); + registers.set(inst.dst, try self.load(T, vm_addr)); + }, + + inline + // ST_1B_IMM + .mul64_imm, + // ST_2B_IMM + .div64_imm, + // ST_4B_IMM + .neg64, + // ST_8B_IMM + .mod64_imm, + => |tag| { + const T = switch (tag) { + .mul64_imm => u8, + .div64_imm => u16, + .neg64 => u32, + .mod64_imm => u64, + else => unreachable, + }; + const base_address: i64 = @bitCast(registers.get(inst.dst)); + const vm_addr: u64 = @bitCast(base_address +% @as(i64, inst.off)); + try self.store(T, vm_addr, @truncate(@as(u64, inst.imm))); + }, + + inline + // ST_1B_REG + .mul64_reg, + // ST_2B_REG + .div64_reg, + // ST_4B_REG + .st_4b_reg, + // ST_8B_REG + .mod64_reg, + => |tag| { + const T = switch (tag) { + .mul64_reg => u8, + .div64_reg => u16, + .st_4b_reg => u32, + .mod64_reg => u64, + else => unreachable, + }; + const base_address: i64 = @bitCast(registers.get(inst.dst)); + const vm_addr: u64 = @bitCast(base_address +% @as(i64, inst.off)); + try self.store(T, vm_addr, @truncate(registers.get(inst.src))); + }, + + else => {}, + } + } + switch (opcode) { // alu operations .add64_reg, @@ -132,7 +210,19 @@ pub const Vm = struct { .rsh64_imm, .rsh32_reg, .rsh32_imm, - => { + => cont: { + if (version.moveMemoryInstructionClasses()) { + // instructions handled above + switch (@intFromEnum(opcode) & 0xF0) { + Instruction.mod, + Instruction.neg, + Instruction.mul, + Instruction.div, + => break :cont, + else => {}, + } + } + const lhs_large = registers.get(inst.dst); const rhs_large = if (opcode.isReg()) registers.get(inst.src) @@ -156,10 +246,10 @@ pub const Vm = struct { Instruction.xor => lhs ^ rhs, Instruction.@"or" => lhs | rhs, Instruction.@"and" => lhs & rhs, - Instruction.mod => try std.math.mod(u64, lhs, rhs), Instruction.lsh => lhs << @truncate(rhs), Instruction.rsh => lhs >> @truncate(rhs), Instruction.mov => rhs, + Instruction.mod => try std.math.mod(u64, lhs, rhs), // zig fmt: on Instruction.mul => value: { if (opcode.is64()) break :value lhs *% rhs; @@ -413,37 +503,52 @@ pub const Vm = struct { const predicate: bool = switch (opcode) { // zig fmt: off - .ja => true, - .jeq_imm, .jeq_reg => lhs == rhs, - .jne_imm, .jne_reg => lhs != rhs, - .jge_imm, .jge_reg => lhs >= rhs, - .jgt_imm, .jgt_reg => lhs > rhs, - .jle_imm, .jle_reg => lhs <= rhs, - .jlt_imm, .jlt_reg => lhs < rhs, - .jset_imm, .jset_reg => lhs & rhs != 0, - .jsge_imm, .jsge_reg => lhs_signed >= rhs_signed, - .jsgt_imm, .jsgt_reg => lhs_signed > rhs_signed, - .jsle_imm, .jsle_reg => lhs_signed <= rhs_signed, - .jslt_imm, .jslt_reg => lhs_signed < rhs_signed, - // zig fmt: on + .ja => true, + .jeq_imm, .jeq_reg => lhs == rhs, + .jne_imm, .jne_reg => lhs != rhs, + .jge_imm, .jge_reg => lhs >= rhs, + .jgt_imm, .jgt_reg => lhs > rhs, + .jle_imm, .jle_reg => lhs <= rhs, + .jlt_imm, .jlt_reg => lhs < rhs, + .jset_imm, .jset_reg => lhs & rhs != 0, + .jsge_imm, .jsge_reg => lhs_signed >= rhs_signed, + .jsgt_imm, .jsgt_reg => lhs_signed > rhs_signed, + .jsle_imm, .jsle_reg => lhs_signed <= rhs_signed, + .jslt_imm, .jslt_reg => lhs_signed < rhs_signed, + // zig fmt: on else => unreachable, }; if (predicate) next_pc = target_pc; }, // calling - .exit => { - if (self.depth == 0) { - return false; - } - self.depth -= 1; - const frame = self.call_frames.pop(); - self.registers.set(.r10, frame.fp); - @memcpy(self.registers.values[6..][0..4], &frame.caller_saved_regs); - if (!version.enableDynamicStackFrames()) { - registers.getPtr(.r10).* -= self.executable.config.stack_frame_size; + .exit, + .@"return", + => { + if (opcode == .exit and version.enableStaticSyscalls()) { + // SBPFv3 SYSCALL instruction + if (self.loader.functions.lookupKey(inst.imm)) |entry| { + try entry.value(self); + } else { + @panic("TODO: detect invalid syscall in verifier"); + } + } else { + if (opcode == .@"return" and !version.enableStaticSyscalls()) { + return error.UnknownInstruction; + } + + if (self.depth == 0) { + return false; + } + self.depth -= 1; + const frame = self.call_frames.pop(); + self.registers.set(.r10, frame.fp); + @memcpy(self.registers.values[6..][0..4], &frame.caller_saved_regs); + if (!version.enableDynamicStackFrames()) { + registers.getPtr(.r10).* -= self.executable.config.stack_frame_size; + } + next_pc = frame.return_pc; } - next_pc = frame.return_pc; }, .call_imm => { var resolved = false; @@ -451,6 +556,7 @@ pub const Vm = struct { .{ inst.src == .r0, inst.src != .r0 } else .{ true, true }; + if (external) { if (self.loader.functions.lookupKey(inst.imm)) |entry| { resolved = true; @@ -458,8 +564,13 @@ pub const Vm = struct { try builtin_fn(self); } } + if (internal and !resolved) { - if (self.executable.function_registry.lookupKey(inst.imm)) |entry| { + const target_pc: i64 = if (version.enableStaticSyscalls()) + @as(i64, @intCast(pc)) +| @as(i32, @bitCast(inst.imm)) +| 1 + else + inst.imm; + if (self.executable.function_registry.lookupKey(@bitCast(target_pc))) |entry| { resolved = true; try self.pushCallFrame(); next_pc = entry.value; @@ -489,6 +600,12 @@ pub const Vm = struct { registers.set(inst.dst, value); next_pc += 1; }, + + // handled above + .ld_4b_reg, + .st_4b_reg, + => if (!version.moveMemoryInstructionClasses()) return error.UnknownInstruction, + else => return error.UnknownInstruction, } diff --git a/src/utils/deduper.zig b/src/utils/deduper.zig index faa84e7dd..a0af5b2a8 100644 --- a/src/utils/deduper.zig +++ b/src/utils/deduper.zig @@ -171,7 +171,7 @@ fn testDedupSeeded( try std.testing.expectEqual(num_dups, num_dups); try std.testing.expectEqual( popcount, - deduper.masked_count.load(.unordered), + deduper.masked_count.load(.monotonic), ); try std.testing.expect(deduper.falsePositiveRate() < false_positive_rate); try std.testing.expect(