From c2a5cb4ce88bd16677127ac4d87ae5fcb4df89a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20H=C3=B6ner?= Date: Tue, 6 Feb 2024 12:23:46 +0100 Subject: [PATCH] CI: add basic FFI struct validation --- .github/workflows/main.yml | 30 +++++++++++++++++++++- validate-structs.py | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 validate-structs.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4334c4..ab0d7bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,7 @@ jobs: - name: "Linux (all features)" image_name: "ubuntu-22.04" extra_args: "--all-features" + do_struct_checks: true - name: "Linux (minimal)" image_name: "ubuntu-22.04" extra_args: "--no-default-features" @@ -35,7 +36,34 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Build - run: cargo build ${{ matrix.extra_args }} + run: cargo build --all --examples ${{ matrix.extra_args }} + - name: Validate FFI structs + if: matrix.do_struct_checks + run: | + set -eu + + executable=target/debug/examples/pattern + if [[ ! -f "${executable}" ]]; then + echo "Test executable '${executable}' not built: skipping test" + exit 0 + fi + + sudo apt-get -qq install -y gdb + + gdb -q "${executable}" -batch \ + -ex 'source validate-structs.py' \ + 1>script-out.txt 2>gdb-stderr.txt + + if [[ $(cat ./script-out.txt) != *"ALL STRUCTS OK"* ]]; then + echo >&2 "ERROR: struct validation failed!" + echo >&2 "================================" + cat >&2 ./gdb-stderr.txt + echo >&2 "================================" + cat >&2 ./script-out.txt + exit 1 + fi + + echo PASSED. - name: Test run: cargo test ${{ matrix.extra_args }} diff --git a/validate-structs.py b/validate-structs.py new file mode 100644 index 0000000..6e2f6c5 --- /dev/null +++ b/validate-structs.py @@ -0,0 +1,52 @@ +# NOTE: this isn't a standalone script -- meant to be run within gdb! + +# gdb's rust mode doesn't parse the `<` and `>` in the types correctly +gdb.execute('set language c++') + + +checked_types = [ + # decoder + ('zydis::decoder::Decoder', 'ZydisDecoder'), + ('zydis::ffi::decoder::AccessedFlags', 'ZydisAccessedFlags'), + ('zydis::ffi::decoder::AccessedFlags', 'ZydisAccessedFlags'), + ('zydis::ffi::decoder::AvxInfo', 'ZydisDecodedInstructionAvx'), + ('zydis::ffi::decoder::MetaInfo', 'ZydisDecodedInstructionMeta'), + ('zydis::ffi::decoder::RawInfo', 'ZydisDecodedInstructionRaw'), + ('zydis::ffi::decoder::MemoryInfo', 'ZydisDecodedOperandMem'), + ('zydis::ffi::decoder::PointerInfo', 'ZydisDecodedOperandPtr'), + ('zydis::enums::generated::Register', 'ZydisDecodedOperandReg'), + ('zydis::ffi::decoder::ImmediateInfo', 'ZydisDecodedOperandImm'), + ('zydis::ffi::decoder::DecodedOperand', 'ZydisDecodedOperand'), + ('zydis::ffi::decoder::DecoderContext', 'ZydisDecoderContext'), + + # encoder + ('zydis::ffi::encoder::OperandRegister', '((ZydisEncoderOperand*)(0))->reg'), + ('zydis::ffi::encoder::OperandPointer', '((ZydisEncoderOperand*)(0))->ptr'), + ('zydis::ffi::encoder::OperandMemory', '((ZydisEncoderOperand*)(0))->mem'), + ('zydis::ffi::encoder::EncoderOperand', 'ZydisEncoderOperand'), + ('zydis::ffi::encoder::EncoderRequest', 'ZydisEncoderRequest'), + + # formatter + ('zydis::ffi::formatter::Formatter', 'ZydisFormatter'), + ('zydis::ffi::formatter::FormatterBuffer', 'ZydisFormatterBuffer'), + + # zycore + ('zydis::ffi::zycore::ZyanVector', 'ZyanVector'), + ('zydis::ffi::zycore::ZyanString', 'ZyanString'), + + # TODO: incomplete, add more .. +] + + +def sizeof(ty: str) -> int: + return gdb.parse_and_eval(f'sizeof({ty})') + + +for bind_ty, c_ty in checked_types: + bind_ty_sz = sizeof(bind_ty) + c_ty_sz = sizeof(c_ty) + + assert bind_ty_sz == c_ty_sz, \ + f'binding type {bind_ty} is {bind_ty_sz} bytes, but expected {c_ty_sz}' + +print("ALL STRUCTS OK")