wdext 0.1.0

A DbgEng wrapper framework
// SPDX-FileCopyrightText: 2026 takubokudori
// SPDX-License-Identifier: MIT OR Apache-2.0
#[cfg(test)]
pub mod tests {
    use crate::tests::util::local_debug2;
    use wdext::{data::RegisterIndex, error::WdErrorKind, *};

    mod util;

    macro_rules! assert_invalid_reg {
        ($e:expr) => {
            assert_eq!($e.unwrap_err().kind(), WdErrorKind::RegisterNotFound)
        };
    }

    fn assert_read_reg(ctx: &TargetContext, name: &str) {
        // interconversion between name and index.
        let reg_view = ctx.register_view();
        let idx = reg_view.get_index_by_name(name).unwrap();
        let name2 = reg_view.get_name_by_index(idx).unwrap();
        assert_eq!(name, name2);

        macro_rules! assert_read {
            ($ty:ty) => {{
                let a = reg_view
                    .read_value::<$ty>(name)
                    .expect("RegisterSelector::Name failed");
                let b = reg_view
                    .read_value::<$ty>(idx)
                    .expect("RegisterSelector::Index failed");
                assert_eq!(a, b);
            }};
        }

        assert_read!(u8);
        assert_read!(i8);
        assert_read!(u16);
        assert_read!(i16);
        assert_read!(u32);
        assert_read!(i32);
        assert_read!(u64);
        assert_read!(i64);
        assert_read!(f32);
        assert_read!(f64);
        assert_read!([u8; 10]);
        assert_read!([u8; 8]);
        assert_read!([u16; 4]);
        assert_read!([u32; 2]);
        assert_read!([u64; 1]);
        assert_read!([u8; 16]);
        assert_read!([u16; 8]);
        assert_read!([u32; 4]);
        assert_read!([u64; 2]);
        assert_read!([f32; 2]);
        assert_read!([f64; 1]);
        assert_read!([f32; 4]);
        assert_read!([f64; 2]);
    }

    fn assert_write_regs(
        ctx: &TargetContext,
        name: &str,
        width: usize,
        is_float: bool,
    ) {
        let reg_view = ctx.register_view();
        let idx = reg_view.get_index_by_name(name).unwrap();

        macro_rules! assert_write {
            ($name:expr, $idx:expr, $t:ty, $v1:expr, $v2:expr) => {{
                reg_view
                    .write_value($name, $v1)
                    .expect("write1 RegisterSelector::Name failed");
                let a: $t = reg_view.read_value($idx).expect("read1 failed");
                assert_eq!(a, $v1);
                reg_view
                    .write_value($idx, $v2)
                    .expect("write2 RegisterSelector::Index failed");
                let a: $t = reg_view.read_value($idx).expect("read2 failed ");
                assert_eq!(a, $v2);
            }};

            (
                $name:expr,
                $idx:expr,
                $t:ty,
                $v1:expr,
                $v1_2:expr,
                $v2:expr,
                $v2_2:expr
            ) => {{
                reg_view
                    .write_value($name, $v1)
                    .expect("write1 RegisterSelector::Name failed");
                let a: $t = reg_view.read_value($idx).expect("read1 failed");
                assert_eq!(a, $v1_2);
                reg_view
                    .write_value($idx, $v2)
                    .expect("write2 RegisterSelector::Index failed");
                let a: $t = reg_view.read_value($idx).expect("read2 failed ");
                assert_eq!(a, $v2_2);
            }};
        }

        macro_rules! assert_invalid_write {
            ($name:expr, $idx:expr, $t:ty, $v1:expr, $v2:expr) => {{
                assert_eq!(
                    reg_view.write_value($name, $v1).unwrap_err().kind(),
                    WdErrorKind::InvalidArgument
                );
                let a: $t = reg_view.read_value($idx).expect("read1 failed");
                assert_eq!(a, $v2);
                assert_eq!(
                    reg_view.write_value($idx, $v1).unwrap_err().kind(),
                    WdErrorKind::InvalidArgument
                );
                let a: $t = reg_view.read_value($idx).expect("read2 failed ");
                assert_eq!(a, $v2);
            }};
        }

        // Registers can be read regardless of their width, but writes will fail unless the width is taken into account.
        #[rustfmt::skip]
        {
            assert_write!(name, idx, u8, u8::MAX, 0);
            assert_write!(name, idx, i8, -1i8, 0);
            if width >= 16 {
                assert_write!(name, idx, u16, u16::MAX, 0);
                assert_write!(name, idx, i16, -1i16, 0);
            } else {
                assert_invalid_write!(name, idx, u16, u16::MAX, 0);
                assert_invalid_write!(name, idx, i16, -1i16,       0);
            }

            if width >= 32 {
                assert_write!(name, idx, u32, u32::MAX, 0);
                assert_write!(name, idx, i32, -1i32,       0);
            } else {
                assert_invalid_write!(name, idx, u32, u32::MAX, 0);
                assert_invalid_write!(name, idx, i32, -1i32,       0);
            }

            if width >= 64 {
                assert_write!(name, idx,       u64,      u64::MAX,      0);
                assert_write!(name, idx,       i64,            -1i64,      0);
                {
    }
                assert_write!(name, idx,  [u8; 8],  [u8::MAX; 8], [0u8; 8]);
                assert_write!(name, idx, [u16; 4], [u16::MAX; 4], [0u16; 4]);
                assert_write!(name, idx, [u32; 2], [u32::MAX; 2], [0u32; 2]);
                assert_write!(name, idx, [u64; 1], [u64::MAX; 1], [0u64; 1]);
            } else {
                assert_invalid_write!(name, idx,       u64,      u64::MAX,      0);
                assert_invalid_write!(name, idx,       i64,            -1i64,      0);
                assert_invalid_write!(name, idx,  [u8; 8],  [u8::MAX; 8], [0u8; 8]);
                assert_invalid_write!(name, idx, [u16; 4], [u16::MAX; 4], [0u16; 4]);
                assert_invalid_write!(name, idx, [u32; 2], [u32::MAX; 2], [0u32; 2]);
                assert_invalid_write!(name, idx, [u64; 1], [u64::MAX; 1], [0u64; 1]);
            }

            if width >= 128 {
                assert_write!(name, idx, [u8; 16], [u8::MAX; 16], [0u8; 16]);
                assert_write!(name, idx, [u16; 8], [u16::MAX; 8], [0u16;  8]);
                assert_write!(name, idx, [u32; 4], [u32::MAX; 4], [0u32;  4]);
                assert_write!(name, idx, [u64; 2], [u64::MAX; 2], [0u64;  2]);
                if is_float {
                    assert_write!(name, idx, [f32; 2], [1.1f32; 2], [0.0f32; 2]);
                } else {
                    // For non-float registers, the fractional part is truncated.
                    assert_write!(name, idx, [f32; 2], [1.1f32; 2], [1.0f32; 2], [0.0f32; 2], [0.0f32; 2]);
                }
            }

            if is_float {
                assert_write!(name, idx, f32, 1.1f32, 0.0f32);
                assert_write!(name, idx, f64, 1.1f64, 0.0f64);
            } else {
                // For non-float registers, the fractional part is truncated.
                assert_write!(name, idx, f32, 1.1f32, 1.0f32, 0.0f32, 0.0f32);
                assert_write!(name, idx, f64, 1.1f64, 1.0f64, 0.0f64, 0.0f64);
            }
        };
    }

    #[test]
    fn test_wd_registers() {
        let ctx = local_debug2();
        let reg = ctx.registers();

        assert_invalid_reg!(reg.get_index_by_name("invalid"));

        let invalid_index = RegisterIndex(u32::MAX);
        assert_invalid_reg!(reg.get_name_by_index(invalid_index));
        assert_invalid_reg!(reg.get_description(invalid_index));

        #[cfg(target_arch = "x86_64")]
        assert_read_reg(&ctx, "rax");
        assert_read_reg(&ctx, "eax");
        assert_read_reg(&ctx, "ax");
        assert_read_reg(&ctx, "ah");
        assert_read_reg(&ctx, "al");
        assert_read_reg(&ctx, "xmm0");

        #[cfg(target_arch = "x86_64")]
        assert_write_regs(&ctx, "rax", 64, false);
        // On x86, writing a `u64` to `eax` writes only the lower 32 bits.
        #[cfg(target_arch = "x86_64")]
        assert_write_regs(&ctx, "eax", 32, false);
        assert_write_regs(&ctx, "ax", 16, false);
        assert_write_regs(&ctx, "ah", 8, false);
        assert_write_regs(&ctx, "al", 8, false);
        assert_write_regs(&ctx, "xmm0", 128, true);
    }
}