wdext 0.1.0

A DbgEng wrapper framework
// SPDX-FileCopyrightText: 2026 takubokudori
// SPDX-License-Identifier: MIT OR Apache-2.0
#![allow(non_snake_case, non_camel_case_types)]

#[cfg(test)]
pub mod tests {
    use crate::tests::util::local_debug;
    use std::mem::offset_of;
    use wdext::{
        dbgeng::TimeoutQuery,
        object::{traits::list::ListEntry, win_25h2::*},
        util::ListEntryIterator,
        zerocopy::*,
        *,
    };

    mod util;

    trait MyEntry: Sized + FromBytes + IntoBytes + Immutable {
        type Ptr: TPtr<Self>;
        type CharPtr: TPtr<u8>;
        type Entry: ListEntry;

        fn a(&self) -> i32;

        fn entry(&self) -> Self::Entry;

        fn s(&self) -> Self::CharPtr;

        fn offset_of_entry() -> u64;

        fn read_s(
            &self,
            ds: &WdDataSpaces,
            mem: &Memory,
        ) -> WdResult<(String, ReadStringStatus)> {
            ds.read_ansi_string(mem, self.s().offset(), 32, 932)
        }

        fn flink(&self) -> Self::Ptr {
            (self.entry().flink().offset() - Self::offset_of_entry()).into()
        }

        fn blink(&self) -> Self::Ptr {
            (self.entry().blink().offset() - Self::offset_of_entry()).into()
        }
    }

    #[repr(C)]
    #[derive(Debug, FromBytes, IntoBytes, Immutable)]
    struct _MY_ENTRY64 {
        a: i32,
        _Pad1: [u8; 4],
        entry: x64::list::_LIST_ENTRY,
        s: TPtr64<u8>,
    }

    impl MyEntry for _MY_ENTRY64 {
        type Ptr = TPtr64<Self>;
        type CharPtr = TPtr64<u8>;
        type Entry = x64::list::_LIST_ENTRY;

        fn a(&self) -> i32 { self.a }

        fn entry(&self) -> Self::Entry { self.entry }

        fn s(&self) -> Self::CharPtr { self.s }

        fn offset_of_entry() -> u64 { offset_of!(Self, entry) as u64 }
    }

    #[repr(C)]
    #[derive(Debug, FromBytes, IntoBytes, Immutable)]
    struct _MY_ENTRY32 {
        a: i32,
        entry: x86::list::_LIST_ENTRY,
        s: TPtr32<u8>,
    }

    impl MyEntry for _MY_ENTRY32 {
        type Ptr = TPtr32<Self>;
        type CharPtr = TPtr32<u8>;
        type Entry = x86::list::_LIST_ENTRY;

        fn a(&self) -> i32 { self.a }

        fn entry(&self) -> Self::Entry { self.entry }

        fn s(&self) -> Self::CharPtr { self.s }

        fn offset_of_entry() -> u64 { offset_of!(Self, entry) as u64 }
    }

    fn test_my_entries<E: MyEntry>(ctx: &TargetContext, timeout: TimeoutQuery) {
        let ctrl = ctx.control();
        let ds = ctx.data_spaces();
        let mem = ctx.memory();

        macro_rules! assert_my_entry {
            ($entry:expr, $a:expr, $s:expr) => {
                assert_eq!($entry.a(), $a);
                assert_eq!(
                    $entry.read_s(ds, mem).unwrap(),
                    ($s.to_string(), ReadStringStatus::NulTerminated)
                );
            };
        }

        // 1st InsertTailListFunc
        let offset_head_list: E::Ptr =
            ctx.evaluate_pointer("ListEntry!Head").unwrap().into();

        // CONTAINING_RECORD
        let offset_head: E::Ptr =
            (offset_head_list.offset() - E::offset_of_entry()).into();

        let head = offset_head.read(ctx, mem).unwrap();
        assert_eq!(head.flink(), offset_head);
        assert_eq!(head.blink(), offset_head);

        let mut it = ListEntryIterator::<'_, E::Entry>::new(
            ds,
            mem,
            offset_head_list.offset(),
            None,
            true,
            false,
            true,
            false,
        )
        .unwrap();
        assert!(it.next().is_none());

        let mut it = ListEntryIterator::<'_, E::Entry>::new(
            ds,
            mem,
            offset_head_list.offset(),
            None,
            true,
            true,
            true,
            false,
        )
        .unwrap();
        let tobj = it.next().unwrap().unwrap();
        assert_eq!(tobj.ptr().offset(), offset_head_list.offset());
        assert!(it.next().is_none());
        // 2nd InsertTailListFunc
        ctrl.go(timeout).unwrap();
        // 3rd InsertTailListFunc
        ctrl.go(timeout).unwrap();
        // After 3rd InsertTailListFunc
        ctrl.go_upper(timeout).unwrap();

        let head = offset_head.read(ctx, mem).unwrap();
        let offset_entry1 = head.flink();
        let entry1 = offset_entry1.read(ctx, mem).unwrap();
        let offset_entry2 = entry1.flink();
        let entry2 = offset_entry2.read(ctx, mem).unwrap();
        let offset_entry3 = entry2.flink();
        let entry3 = offset_entry3.read(ctx, mem).unwrap();
        assert_my_entry!(entry1, 1, "test 1");
        assert_my_entry!(entry2, 2, "test 2");
        assert_my_entry!(entry3, 3, "test 3");

        let s = entry1.s().read_objects(ctx, mem, 7).unwrap();
        assert_eq!(&s, b"test 1\x00".as_ref());

        assert_eq!(entry3.flink(), offset_head);
        assert_eq!(entry1.blink(), offset_head);

        let head = offset_head.read(ctx, mem).unwrap();
        let offset_entry3 = head.blink();
        let entry3 = offset_entry3.read(ctx, mem).unwrap();
        let offset_entry2 = entry3.blink();
        let entry2 = offset_entry2.read(ctx, mem).unwrap();
        let offset_entry1 = entry2.blink();
        let entry1 = offset_entry1.read(ctx, mem).unwrap();
        assert_my_entry!(entry1, 1, "test 1");
        assert_my_entry!(entry2, 2, "test 2");
        assert_my_entry!(entry3, 3, "test 3");

        let mut it = ListEntryIterator::<'_, E::Entry>::new(
            ds,
            mem,
            offset_head_list.offset(),
            None,
            true,
            false,
            true,
            false,
        )
        .unwrap();

        let tobj = it.next().unwrap().unwrap();
        assert_eq!(
            tobj.ptr().offset(),
            offset_entry1.offset() + E::offset_of_entry()
        );
        let tobj = it.next().unwrap().unwrap();
        assert_eq!(
            tobj.ptr().offset(),
            offset_entry2.offset() + E::offset_of_entry()
        );
        let tobj = it.next().unwrap().unwrap();
        assert_eq!(
            tobj.ptr().offset(),
            offset_entry3.offset() + E::offset_of_entry()
        );
        assert!(it.next().is_none());

        let mut it = ListEntryIterator::<'_, E::Entry>::new(
            ds,
            mem,
            offset_head_list.offset(),
            None,
            false,
            false,
            true,
            false,
        )
        .unwrap();

        let tobj = it.next().unwrap().unwrap();
        assert_eq!(
            tobj.ptr().offset(),
            offset_entry3.offset() + E::offset_of_entry()
        );
        let addr = it.next().unwrap().unwrap();
        assert_eq!(
            addr.ptr().offset(),
            offset_entry2.offset() + E::offset_of_entry()
        );
        let addr = it.next().unwrap().unwrap();
        assert_eq!(
            addr.ptr().offset(),
            offset_entry1.offset() + E::offset_of_entry()
        );
        assert!(it.next().is_none());
    }

    #[test]
    fn test_list_entry() {
        #[cfg(target_arch = "x86")]
        let exe_path = r#"./tests/bin/x86/ListEntry.exe"#;
        #[cfg(target_arch = "x86_64")]
        let exe_path = r#"./tests/bin/x64/ListEntry.exe"#;
        let ctx = local_debug(exe_path);
        let ctrl = ctx.control();
        let timeout = TimeoutQuery::from_secs(10);
        let sym = ctx.symbols();
        let offset_insert = sym
            .get_symbol_offset_by_name("ListEntry!InsertTailListFunc")
            .unwrap();
        let _ = ctrl.add_software_breakpoint(offset_insert).unwrap();

        // InsertTailListFunc
        ctrl.go(timeout).unwrap();
        match ctx.pointer_width() {
            PointerWidth::Ptr32 => {
                test_my_entries::<_MY_ENTRY32>(&ctx, timeout)
            }
            PointerWidth::Ptr64 => {
                test_my_entries::<_MY_ENTRY64>(&ctx, timeout)
            }
        }
    }
}