Full Code of TimMisiak/dbgrs for AI

main b5ce6fb29669 cached
20 files
78.3 KB
18.9k tokens
123 symbols
1 requests
Download .txt
Repository: TimMisiak/dbgrs
Branch: main
Commit: b5ce6fb29669
Files: 20
Total size: 78.3 KB

Directory structure:
gitextract_ezw1bihx/

├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── Cargo.toml
├── LICENSE
├── build.rs
└── src/
    ├── breakpoint.rs
    ├── command.rs
    ├── eval.rs
    ├── event.rs
    ├── main.rs
    ├── memory.rs
    ├── module.rs
    ├── name_resolution.rs
    ├── process.rs
    ├── registers.rs
    ├── source.rs
    ├── stack.rs
    ├── unassemble.rs
    └── util.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
target

================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug executable 'dbgrs'",
            "cargo": {
                "args": [
                    "build",
                    "--bin=dbgrs",
                    "--package=dbgrs"
                ],
                "filter": {
                    "name": "dbgrs",
                    "kind": "bin"
                }
            },
            "args": ["C:\\git\\HelloWorld\\hello.exe"],
            "cwd": "${workspaceFolder}"
        },
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug unit tests in executable 'dbgrs'",
            "cargo": {
                "args": [
                    "test",
                    "--no-run",
                    "--bin=dbgrs",
                    "--package=dbgrs"
                ],
                "filter": {
                    "name": "dbgrs",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        }
    ]
}

================================================
FILE: .vscode/tasks.json
================================================
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "cargo",
			"command": "build",
			"problemMatcher": [
				"$rustc",
				"$rust-panic"
			],
			"group": "build",
			"label": "rust: cargo build"
		}
	]
}

================================================
FILE: Cargo.toml
================================================
[package]
name = "dbgrs"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rust-sitter = "0.2.1"
codemap = "0.1.3"
codemap-diagnostic = "0.1.1"
pdb = "0.8.0"
num-traits = "0.2.15"
iced-x86 = "1.20.0"
anyhow = "1.0.79"
regex = "*"

[build-dependencies]
rust-sitter-tool = "0.2.1"

[dependencies.windows-sys]
version = "0.48.0"
features = [
    "Win32_Foundation",
    "Win32_Security",
    "Win32_Storage_FileSystem",
    "Win32_System_Kernel",
    "Win32_System_Threading",
    "Win32_UI_WindowsAndMessaging",
    "Win32_System_Diagnostics_Debug",
    "Win32_System_Environment",
    "Win32_System_WindowsProgramming",
]

[dependencies.windows]
version = "0.48.0"
features = [
    "Win32_System_Diagnostics_Debug",
    "Win32_System_SystemInformation",
    "Win32_System_SystemServices",
]


================================================
FILE: LICENSE
================================================
Copyright 2023 Tim Misiak

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: build.rs
================================================
use std::path::PathBuf;

fn main() {
    println!("cargo:rerun-if-changed=src");
    rust_sitter_tool::build_parsers(&PathBuf::from("src/command.rs"));
}


================================================
FILE: src/breakpoint.rs
================================================
use windows_sys::Win32::System::Diagnostics::Debug::GetThreadContext;
use windows_sys::Win32::System::Diagnostics::Debug::SetThreadContext;
use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;
use windows_sys::Win32::System::Threading::*;
use windows_sys::Win32::Foundation::*;
use num_traits::int::PrimInt;

use crate::memory::MemorySource;
use crate::process::Process;
use crate::name_resolution;
use crate::util::*;

const DR7_LEN_BIT: [usize; 4] = [19, 23, 27, 31];
const DR7_RW_BIT: [usize; 4] = [17, 21, 25, 29];
const DR7_LE_BIT: [usize; 4] = [0, 2, 4, 6];
const DR7_GE_BIT: [usize; 4] = [1, 3, 5, 7];

const DR7_LEN_SIZE: usize = 2;
const DR7_RW_SIZE: usize = 2;

const DR6_B_BIT: [usize; 4] = [0, 1, 2, 3];

const EFLAG_RF: usize = 16;

struct Breakpoint {
    addr: u64,
    id: u32,
}

pub struct BreakpointManager {
    breakpoints: Vec::<Breakpoint>,
}

fn set_bits<T: PrimInt>(val: &mut T, set_val: T, start_bit: usize, bit_count: usize) {
    // First, mask out the relevant bits
    let max_bits = std::mem::size_of::<T>() * 8;
    let mask: T = T::max_value() << (max_bits - bit_count);
    let mask: T = mask >> (max_bits - 1 - start_bit);
    let inv_mask = !mask;

    *val = *val & inv_mask;
    *val = *val | (set_val << (start_bit + 1 - bit_count));
}

fn get_bit<T: PrimInt>(val: T, bit_index: usize) -> bool {
    let mask = T::one() << bit_index;
    let masked_val = val & mask;
    masked_val != T::zero()
}

impl BreakpointManager {

    pub fn new() -> BreakpointManager {
        BreakpointManager { breakpoints: Vec::new() }
    }

    fn get_free_id(&self) -> u32 {
        for i in 0..4 {
            if self.breakpoints.iter().find(|&x| x.id == i).is_none() {
                return i;
            }
        }
        panic!("Too many breakpoints!")
    }

    pub fn add_breakpoint(&mut self, addr: u64) {
        self.breakpoints.push(Breakpoint{addr, id: self.get_free_id()});
        self.breakpoints.sort_by(|a, b| a.id.cmp(&b.id));
    }

    pub fn list_breakpoints(&self, process: &mut Process) {
        for bp in self.breakpoints.iter() {
            if let Some(sym) = name_resolution::resolve_address_to_name(bp.addr, process) {
                println!("{:3} {:#018x} ({})", bp.id, bp.addr, sym)
            } else {
                println!("{:3} {:#018x}", bp.id, bp.addr)
            }            
        }
    }

    pub fn clear_breakpoint(&mut self, id: u32) {
        self.breakpoints.retain(|x| x.id != id)
    }

    pub fn was_breakpoint_hit(&self, thread_context: &CONTEXT) -> Option<u32> {
        for idx in 0..self.breakpoints.len() {
            if get_bit(thread_context.Dr6, DR6_B_BIT[idx]) {
                return Some(idx as u32);
            }
        }
        None
    }

    pub fn apply_breakpoints(&mut self, process: &mut Process, resume_thread_id: u32, _memory_source: &dyn MemorySource) {

        for thread_id in process.iterate_threads() {
            let mut ctx: AlignedContext = unsafe { std::mem::zeroed() };
            ctx.context.ContextFlags = CONTEXT_ALL;            
            let thread = AutoClosedHandle(unsafe {
                OpenThread(
                    THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
                    FALSE,
                    *thread_id,
                )
            });
            let ret = unsafe { GetThreadContext(thread.handle(), &mut ctx.context) };

            if ret == 0 {
                println!("Could not get thread context of thread {:x}", thread_id);
                continue;
            }

            // Currently there is a limit of 4 breakpoints, since we are using hardware breakpoints.
            for idx in 0..4 {
                if self.breakpoints.len() > idx {
                    
                    set_bits(&mut ctx.context.Dr7, 0, DR7_LEN_BIT[idx], DR7_LEN_SIZE);
                    set_bits(&mut ctx.context.Dr7, 0, DR7_RW_BIT[idx], DR7_RW_SIZE);
                    set_bits(&mut ctx.context.Dr7, 1, DR7_LE_BIT[idx], 1);
                    match idx {
                        0 => ctx.context.Dr0 = self.breakpoints[idx].addr,
                        1 => ctx.context.Dr1 = self.breakpoints[idx].addr,
                        2 => ctx.context.Dr2 = self.breakpoints[idx].addr,
                        3 => ctx.context.Dr3 = self.breakpoints[idx].addr,
                        _ => (),
                    }
                } else {
                    // We'll assume that we own all breakpoints. This will cause problems with programs that expect to control their own debug registers.
                    // As a result, we'll disable any breakpoints that we aren't using.
                    set_bits(&mut ctx.context.Dr7, 0, DR7_LE_BIT[idx], 1);
                    break;
                }    
            }

            // This prevents the current thread from hitting a breakpoint on the current instruction
            if *thread_id == resume_thread_id {
                set_bits(&mut ctx.context.EFlags, 1, EFLAG_RF, 1);
            }

            let ret = unsafe { SetThreadContext(thread.handle(), &mut ctx.context) };
            if ret == 0 {
                println!("Could not set thread context of thread {:x}", thread_id);
            }

        }
    }
}


================================================
FILE: src/command.rs
================================================
use std::io::Write;
use regex::Regex;

use codemap::CodeMap;
use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
use rust_sitter::errors::{ParseError, ParseErrorReason};

#[rust_sitter::grammar("command")]
pub mod grammar {
    #[rust_sitter::language]
    pub enum CommandExpr {
        StepInto(#[rust_sitter::leaf(text = "t")] ()),
        Go(#[rust_sitter::leaf(text = "g")] ()),
        SetBreakpoint(#[rust_sitter::leaf(text = "bp")] (), Box<EvalExpr>),
        ListBreakpoints(#[rust_sitter::leaf(text = "bl")] ()),
        ClearBreakpoint(#[rust_sitter::leaf(text = "bc")] (), Box<EvalExpr>),
        DisplaySpecificRegister(#[rust_sitter::leaf(text = "r")] (), #[rust_sitter::leaf(pattern = "([a-zA-Z]+)", transform = parse_sym)] String),
        DisplayRegisters(#[rust_sitter::leaf(text = "r")] ()),
        StackWalk(#[rust_sitter::leaf(text = "k")] ()),
        DisplayBytes(#[rust_sitter::leaf(text = "db")] (), Box<EvalExpr>),
        Evaluate(#[rust_sitter::leaf(text = "?")] (), Box<EvalExpr>),
        ListNearest(#[rust_sitter::leaf(text = "ln")] (), Box<EvalExpr>),
        Unassemble(#[rust_sitter::leaf(text = "u")] (), Box<EvalExpr>),
        UnassembleContinue(#[rust_sitter::leaf(text = "u")] ()),
        ListSource(#[rust_sitter::leaf(text = "lsa")] (), Box<EvalExpr>),
        SrcPath(#[rust_sitter::leaf(text = ".srcpath")] (), #[rust_sitter::leaf(pattern = "(.*)", transform = parse_path)] String),
        Quit(#[rust_sitter::leaf(text = "q")] ()),
    }

    #[rust_sitter::language]
    pub enum EvalExpr {
        Number(#[rust_sitter::leaf(pattern = r"(\d+|0x[0-9a-fA-F]+)", transform = parse_int)] u64),
        Symbol(#[rust_sitter::leaf(pattern = r"(([a-zA-Z0-9_@#.]+!)?[a-zA-Z0-9_@#.]+)", transform = parse_sym)] String),
        SourceLine(#[rust_sitter::leaf(pattern = r"(`[^`!]+!(?:[a-zA-Z]+:)[^`:!]+:\d+`)", transform = parse_source_line)] (String, String, u32)),
        #[rust_sitter::prec_left(1)]
        Add(
            Box<EvalExpr>,
            #[rust_sitter::leaf(text = "+")] (),
            Box<EvalExpr>,
        ),
    }

    #[rust_sitter::extra]
    struct Whitespace {
        #[rust_sitter::leaf(pattern = r"\s")]
        _whitespace: (),
    }

    fn parse_int(text: &str) -> u64 {
        let text = text.trim();
        if text.starts_with("0x") {
            let text = text.split_at(2).1;
            u64::from_str_radix(text, 16).unwrap()
        } else {
            text.parse().unwrap()
        }
    }

    fn parse_sym(text: &str) -> String {
        text.to_owned()
    }

    fn parse_path(text: &str) -> String {
        text.trim().to_owned()
    }

    fn parse_source_line(text: &str) -> (String, String, u32) {
        let re = regex::Regex::new(r"^`([^!]+)!((?:[a-zA-Z]+:)[^:]+):(\d+)`$").unwrap();
        if let Some(captures) = re.captures(text) {
            let module_name = captures.get(1).unwrap().as_str().to_string();
            let file_name = captures.get(2).unwrap().as_str().to_string();
            let line_number = captures.get(3).unwrap().as_str().parse::<u32>().unwrap();
            (module_name, file_name, line_number)
        } else {
            panic!("Unexpected")
        }
    }
}

// This came from https://github.com/hydro-project/rust-sitter/blob/main/example/src/main.rs
fn convert_parse_error_to_diagnostics(
    file_span: &codemap::Span,
    error: &ParseError,
    diagnostics: &mut Vec<Diagnostic>,
) {
    match &error.reason {
        ParseErrorReason::MissingToken(tok) => diagnostics.push(Diagnostic {
            level: Level::Error,
            message: format!("Missing token: \"{tok}\""),
            code: Some("S000".to_string()),
            spans: vec![SpanLabel {
                span: file_span.subspan(error.start as u64, error.end as u64),
                style: SpanStyle::Primary,
                label: Some(format!("missing \"{tok}\"")),
            }],
        }),

        ParseErrorReason::UnexpectedToken(tok) => diagnostics.push(Diagnostic {
            level: Level::Error,
            message: format!("Unexpected token: \"{tok}\""),
            code: Some("S000".to_string()),
            spans: vec![SpanLabel {
                span: file_span.subspan(error.start as u64, error.end as u64),
                style: SpanStyle::Primary,
                label: Some(format!("unexpected \"{tok}\"")),
            }],
        }),

        ParseErrorReason::FailedNode(errors) => {
            if errors.is_empty() {
                diagnostics.push(Diagnostic {
                    level: Level::Error,
                    message: "Failed to parse node".to_string(),
                    code: Some("S000".to_string()),
                    spans: vec![SpanLabel {
                        span: file_span.subspan(error.start as u64, error.end as u64),
                        style: SpanStyle::Primary,
                        label: Some("failed".to_string()),
                    }],
                })
            } else {
                for error in errors {
                    convert_parse_error_to_diagnostics(file_span, error, diagnostics);
                }
            }
        }
    }
}

pub fn read_command() -> grammar::CommandExpr {
    let stdin = std::io::stdin();
    loop {
        print!("> ");
        std::io::stdout().flush().unwrap();
        let mut input = String::new();
        stdin.read_line(&mut input).unwrap();
        let input = input.trim().to_string();
        if !input.is_empty() {
            let cmd = grammar::parse(&input);
            match cmd {
                Ok(c) => return c,
                Err(errs) => {
                    // This came from https://github.com/hydro-project/rust-sitter/blob/main/example/src/main.rs
                    let mut codemap = CodeMap::new();
                    let file_span = codemap.add_file("<input>".to_string(), input.to_string());
                    let mut diagnostics = vec![];
                    for error in errs {
                        convert_parse_error_to_diagnostics(
                            &file_span.span,
                            &error,
                            &mut diagnostics,
                        );
                    }

                    let mut emitter = Emitter::stderr(ColorConfig::Always, Some(&codemap));
                    emitter.emit(&diagnostics);
                }
            }
        }
    }
}


================================================
FILE: src/eval.rs
================================================
use crate::command::grammar::EvalExpr;
use crate::process::Process;
use crate::name_resolution::resolve_name_to_address;
use crate::registers::get_register;
use crate::source::resolve_source_line_to_address;
use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;

pub struct EvalContext<'a> {
    pub process: &'a mut Process,
    // TODO: This should really be an abstraction on top of the context
    pub register_context: &'a CONTEXT,
}

pub fn evaluate_expression(expr: EvalExpr, context: &mut EvalContext) -> Result<u64, anyhow::Error> {
    match expr {
        EvalExpr::Number(x) => Ok(x),
        EvalExpr::Add(x, _, y) => Ok(evaluate_expression(*x, context)? + evaluate_expression(*y, context)?),
        EvalExpr::Symbol(sym) => {
            if sym.starts_with('@') {
                if let Ok(val) = get_register(context.register_context, &sym[1..]) {
                    return Ok(val);
                }
            }
            resolve_name_to_address(&sym, context.process)
        },
        EvalExpr::SourceLine((src_module, src_file, src_line)) => {
            resolve_source_line_to_address(&src_module, &src_file, src_line, context.process)
        }
    }
}


================================================
FILE: src/event.rs
================================================
use std::os::windows::prelude::OsStringExt;

use windows_sys::Win32::{System::{Diagnostics::Debug::{DEBUG_EVENT, WaitForDebugEventEx, EXCEPTION_DEBUG_EVENT, CREATE_THREAD_DEBUG_EVENT, CREATE_PROCESS_DEBUG_EVENT, EXIT_THREAD_DEBUG_EVENT, EXIT_PROCESS_DEBUG_EVENT, LOAD_DLL_DEBUG_EVENT, UNLOAD_DLL_DEBUG_EVENT, OUTPUT_DEBUG_STRING_EVENT, RIP_EVENT}, Threading::{INFINITE, GetThreadId}}, Storage::FileSystem::GetFinalPathNameByHandleW, Foundation::CloseHandle};

use crate::memory::{MemorySource, self};

#[allow(non_snake_case)]

pub enum DebugEvent {
    Exception{first_chance: bool, exception_code: i32},
    CreateProcess{exe_name: Option<String>, exe_base: u64},
    CreateThread{thread_id: u32},
    ExitThread{thread_id: u32},
    LoadModule{module_name: Option<String>, module_base: u64},
    OutputDebugString(String),
    ExitProcess,
    Other(String)
}

pub struct EventContext {
    pub process_id: u32,
    pub thread_id: u32,
}

pub fn wait_for_next_debug_event(mem_source: &dyn MemorySource) -> (EventContext, DebugEvent) {
    let mut debug_event: DEBUG_EVENT = unsafe { std::mem::zeroed() };
    unsafe {
        WaitForDebugEventEx(&mut debug_event, INFINITE);
    }

    let ctx = EventContext{ process_id: debug_event.dwProcessId, thread_id: debug_event.dwThreadId };

    match debug_event.dwDebugEventCode {
        EXCEPTION_DEBUG_EVENT => {
            let code = unsafe { debug_event.u.Exception.ExceptionRecord.ExceptionCode };
            let first_chance = unsafe { debug_event.u.Exception.dwFirstChance };
            (ctx, DebugEvent::Exception { first_chance: first_chance != 0, exception_code: code })
        },
        CREATE_THREAD_DEBUG_EVENT => {
            let create_thread = unsafe { debug_event.u.CreateThread };
            let thread_id = unsafe { GetThreadId(create_thread.hThread) };
            unsafe { CloseHandle(create_thread.hThread) };
            (ctx, DebugEvent::CreateThread { thread_id } )
        },
        EXIT_THREAD_DEBUG_EVENT => {
            (ctx, DebugEvent::ExitThread { thread_id: debug_event.dwThreadId} )
        },
        CREATE_PROCESS_DEBUG_EVENT => {
            let create_process = unsafe { debug_event.u.CreateProcessInfo };
            let exe_base = create_process.lpBaseOfImage as u64;
            let mut exe_name = vec![0u16; 260];
            let exe_name_len = unsafe { GetFinalPathNameByHandleW(create_process.hFile, exe_name.as_mut_ptr(), 260, 0) } as usize;
            let exe_name = if exe_name_len != 0 {
                // This will be the full name, e.g. \\?\C:\git\HelloWorld\hello.exe
                // It might be useful to have the full name, but it's not available for all
                // modules in all cases.
                let full_path = std::ffi::OsString::from_wide(&exe_name[0..exe_name_len]);
                let file_name = std::path::Path::new(&full_path).file_name();

                match file_name {
                    None => None,
                    Some(s) => Some(s.to_string_lossy().to_string())
                }
            } else {
                None
            };
            
            //load_module_at_address(&mut process, mem_source.as_ref(), exe_base, exe_name);
            (ctx, DebugEvent::CreateProcess { exe_name, exe_base })
        },
        EXIT_PROCESS_DEBUG_EVENT => (ctx, DebugEvent::ExitProcess),
        LOAD_DLL_DEBUG_EVENT => {
            let load_dll = unsafe { debug_event.u.LoadDll };
            let module_base: u64 = load_dll.lpBaseOfDll as u64;
            let module_name = if load_dll.lpImageName == std::ptr::null_mut() {
                None
            } else {
                let is_wide = load_dll.fUnicode != 0;
                memory::read_memory_string_indirect(mem_source, load_dll.lpImageName as u64, 260, is_wide)
                    .map_or(None, |x| Some(x))
            };

            //load_module_at_address(&mut process, mem_source.as_ref(), dll_base, dll_name);
            (ctx, DebugEvent::LoadModule { module_name, module_base })
        }
        UNLOAD_DLL_DEBUG_EVENT => (ctx, DebugEvent::Other("UnloadDll".to_string())),
        OUTPUT_DEBUG_STRING_EVENT => {
            let debug_string_info = unsafe { debug_event.u.DebugString };
            let is_wide = debug_string_info.fUnicode != 0;
            let address = debug_string_info.lpDebugStringData as u64;
            let len = debug_string_info.nDebugStringLength as usize;
            let debug_string =
                memory::read_memory_string(mem_source, address, len, is_wide).unwrap();
            (ctx, DebugEvent::OutputDebugString(debug_string))
        }
        RIP_EVENT => (ctx, DebugEvent::Other("RipEvent".to_string())),
        _ => panic!("Unexpected debug event"),
    }
}

================================================
FILE: src/main.rs
================================================
use event::DebugEvent;
use memory::MemorySource;
use windows_sys::{
    Win32::Foundation::*,
    Win32::System::Environment::*,
    Win32::System::{Diagnostics::Debug::*, Threading::*},
};

use std::{fs::File, io::{self, BufRead}, mem::MaybeUninit, ptr::null, cmp::{max, min}};

mod command;
mod eval;
mod memory;
mod process;
mod registers;
mod stack;
mod module;
mod name_resolution;
mod event;
mod breakpoint;
mod util;
mod unassemble;
mod source;

use process::Process;
use command::grammar::{CommandExpr, EvalExpr};
use breakpoint::BreakpointManager;
use util::*;
use source::resolve_address_to_source_line;

const TRAP_FLAG: u32 = 1 << 8;

fn show_usage(error_message: &str) {
    println!("Error: {msg}", msg = error_message);
    println!("Usage: DbgRs <Command Line>");
}

unsafe fn wcslen(ptr: *const u16) -> usize {
    let mut len = 0;
    while *ptr.add(len) != 0 {
        len += 1;
    }
    len
}

// For now, we only accept the command line of the process to launch, so we'll just return that for now. Later, we can parse additional
// command line options such as attaching to processes.
// Q: Why not just convert to UTF8?
// A: There can be cases where this is lossy, and we want to make sure we can debug as close as possible to normal execution.
fn parse_command_line() -> Result<Vec<u16>, &'static str> {
    let cmd_line = unsafe {
        // As far as I can tell, standard rust command line argument parsing won't preserve spaces. So we'll call
        // the win32 api directly and then parse it out.
        let p = GetCommandLineW();
        let len = wcslen(p);
        std::slice::from_raw_parts(p, len + 1)
    };

    let mut cmd_line_iter = cmd_line.iter().copied();

    let first = cmd_line_iter.next().ok_or("Command line was empty")?;

    // If the first character is a quote, we need to find a matching end quote. Otherwise, the first space.
    let end_char = (if first == '"' as u16 { '"' } else { ' ' }) as u16;

    loop {
        let next = cmd_line_iter.next().ok_or("No arguments found")?;
        if next == end_char {
            break;
        }
    }

    // Now we need to skip any whitespace
    let cmd_line_iter = cmd_line_iter.skip_while(|x| x == &(' ' as u16));

    Ok(cmd_line_iter.collect())
}

fn load_module_at_address(process: &mut Process, memory_source: &dyn MemorySource, base_address: u64, module_name: Option<String>) {
    let module = process.add_module(base_address, module_name, memory_source).unwrap();

    println!("LoadDll: {:X}   {}", base_address, module.name);
}

fn main_debugger_loop(process: HANDLE) {
    let mut expect_step_exception = false;
    let mem_source = memory::make_live_memory_source(process);
    let mut process = Process::new();
    let mut breakpoints = BreakpointManager::new();

    let mut source_search_paths = Vec::new();

    loop {
        let (event_context, debug_event) = event::wait_for_next_debug_event(mem_source.as_ref());

        // The thread context will be needed to determine what to do with some events
        let thread = AutoClosedHandle(unsafe {
            OpenThread(
                THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
                FALSE,
                event_context.thread_id,
            )
        });

        let mut ctx: AlignedContext = unsafe { std::mem::zeroed() };
        ctx.context.ContextFlags = CONTEXT_ALL;
        let ret = unsafe { GetThreadContext(thread.handle(), &mut ctx.context) };
        if ret == 0 {
            panic!("GetThreadContext failed");
        }

        let mut continue_status = DBG_CONTINUE;
        let mut is_exit = false;
        match debug_event {
            DebugEvent::Exception { first_chance, exception_code } => {
                let chance_string = if first_chance {
                    "first chance"
                } else {
                    "second chance"
                };

                if expect_step_exception && exception_code == EXCEPTION_SINGLE_STEP {
                    expect_step_exception = false;
                    continue_status = DBG_CONTINUE;
                } else if let Some(bp_index) = breakpoints.was_breakpoint_hit(&ctx.context) {
                    println!("Breakpoint {} hit", bp_index);
                    continue_status = DBG_CONTINUE;
                } else {
                    println!("Exception code {:x} ({})", exception_code, chance_string);
                    continue_status = DBG_EXCEPTION_NOT_HANDLED;
                }
            },
            DebugEvent::CreateProcess { exe_name, exe_base } => {
                load_module_at_address(&mut process, mem_source.as_ref(), exe_base, exe_name);
                process.add_thread(event_context.thread_id);
            },
            DebugEvent::CreateThread { thread_id } => {
                process.add_thread(thread_id);
                println!("Thread created: {:x}", thread_id);
            },
            DebugEvent::ExitThread { thread_id } => {
                process.remove_thread(thread_id);
                println!("Thread exited: {:x}", thread_id);
            },
            DebugEvent::LoadModule { module_name, module_base } => {
                load_module_at_address(&mut process, mem_source.as_ref(), module_base, module_name);
            },
            DebugEvent::OutputDebugString(debug_string) => println!("DebugOut: {}", debug_string),
            DebugEvent::Other(msg) => println!("{}", msg),
            DebugEvent::ExitProcess => {
                is_exit = true;
                println!("ExitProcess");
            },
        }

        let mut next_unassemble_address = ctx.context.Rip;
        let mut continue_execution = false;

        while !continue_execution {

            if let Some(sym) = name_resolution::resolve_address_to_name(ctx.context.Rip, &mut process) {
                println!("[{:X}] {}", event_context.thread_id, sym);
            } else {
                println!("[{:X}] {:#018x}", event_context.thread_id, ctx.context.Rip);
            }

            let cmd = command::read_command();


            let mut eval_expr = |expr: Box<EvalExpr>| -> Option<u64> {
                let mut eval_context = eval::EvalContext{ process: &mut process, register_context: &ctx.context };
                let result = eval::evaluate_expression(*expr, &mut eval_context);
                match result {
                    Ok(val) => Some(val),
                    Err(e) => {
                        print!("Could not evaluate expression: {}", e);
                        None
                    }
                }
            };

            match cmd {
                CommandExpr::StepInto(_) => {
                    ctx.context.EFlags |= TRAP_FLAG;
                    let ret = unsafe { SetThreadContext(thread.handle(), &ctx.context) };
                    if ret == 0 {
                        panic!("SetThreadContext failed");
                    }
                    expect_step_exception = true;
                    continue_execution = true;
                }
                CommandExpr::Go(_) => {
                    continue_execution = true;
                }
                CommandExpr::DisplayRegisters(_) => {
                    registers::display_all(&ctx.context);
                }
                CommandExpr::DisplaySpecificRegister(_, reg) => {
                    registers::display_named(&ctx.context, &reg);
                }
                CommandExpr::DisplayBytes(_, expr) => {
                    if let Some(address) = eval_expr(expr) {
                        let bytes = mem_source.read_raw_memory(address, 16);
                        for byte in bytes {
                            print!("{:02X} ", byte);
                        }
                        println!();
                    }
                }
                CommandExpr::Evaluate(_, expr) => {
                    if let Some(val) = eval_expr(expr) {
                        println!(" = 0x{:X}", val);
                    }
                }
                CommandExpr::ListNearest(_, expr) => {
                    if let Some(val) = eval_expr(expr) {
                        if let Some(sym) = name_resolution::resolve_address_to_name(val, &mut process) {
                            println!("{}", sym);
                        } else {
                            println!("No symbol found");
                        }
                    }
                }
                CommandExpr::Unassemble(_, expr) => {
                    if let Some(addr) = eval_expr(expr) {
                        next_unassemble_address = unassemble::unassemble(mem_source.as_ref(), addr, 16);
                    }
                }
                CommandExpr::UnassembleContinue(_) => {
                    next_unassemble_address = unassemble::unassemble(mem_source.as_ref(), next_unassemble_address, 16);
                }
                CommandExpr::ListSource(_, expr) => {
                    if let Some(val) = eval_expr(expr) {
                        match resolve_address_to_source_line(val, &mut process) {
                            Ok((file_name, line_number)) => {
                                println!("LSA: {}:{}", file_name, line_number);
                                if let Ok(file_name) = source::find_source_file_match(&file_name, &source_search_paths) {
                                    if let Ok(file) = File::open(&file_name) {
                                        println!("Found matching file: {}", file_name.display());
                                        let reader = io::BufReader::new(file);
                                        let lines: Vec<_> = reader.lines().map(|l| l.unwrap_or("".to_string())) .collect();
                                        for print_line_num in (max(1, line_number - 2))..=(min(lines.len() as u32, line_number + 2)) {
                                            if print_line_num == line_number {
                                                println!(">{:4}: {}", print_line_num, lines[print_line_num as usize - 1]);
                                            } else {
                                                println!("{:5}: {}", print_line_num, lines[print_line_num as usize - 1]);
                                            }
                                        }
                                    } else {
                                        println!("Couldn't open file: {}", file_name.display());
                                    }
                                }
                            },
                            Err(e) => {
                                println!("Couldn't look up source: {}", e);
                            }
                        }                        
                    }
                }
                CommandExpr::SrcPath(_, path) => {
                    source_search_paths.clear();
                    source_search_paths.extend(path.split(';').map(|s| s.to_string()));
                }
                CommandExpr::SetBreakpoint(_, expr) => {
                    if let Some(addr) = eval_expr(expr) {
                        breakpoints.add_breakpoint(addr);
                    }
                }
                CommandExpr::ListBreakpoints(_) => {
                    breakpoints.list_breakpoints(&mut process);
                }
                CommandExpr::ClearBreakpoint(_, expr) => {
                    if let Some(id) = eval_expr(expr) {
                        breakpoints.clear_breakpoint(id as u32);
                    }
                }
                CommandExpr::StackWalk(_) => {
                    let mut context = ctx.context.clone();
                    println!(" #   RSP              Call Site");
                    let mut frame_number = 0;
                    loop {
                        if let Some(sym) = name_resolution::resolve_address_to_name(context.Rip, &mut process) {
                            println!("{:02X} 0x{:016X} {}", frame_number, context.Rsp, sym);
                        } else {
                            println!("{:02X} 0x{:016X} 0x{:X}", frame_number, context.Rsp, context.Rip);
                        }
                        match stack::unwind_context(&mut process, context, mem_source.as_ref()) {
                            Ok(Some(unwound_context)) => context = unwound_context,
                            _ => break
                        }
                        frame_number += 1;
                    }
                }
                CommandExpr::Quit(_) => {
                    // The process will be terminated since we didn't detach.
                    return;
                }
            }
        }

        if is_exit {
            break;
        }

        breakpoints.apply_breakpoints(&mut process, event_context.thread_id, mem_source.as_ref());

        unsafe {
            ContinueDebugEvent(
                event_context.process_id,
                event_context.thread_id,
                continue_status,
            );
        }
    }
}

fn main() {
    let target_command_line_result = parse_command_line();

    let mut command_line_buffer = match target_command_line_result {
        Ok(i) => i,
        Err(msg) => {
            show_usage(msg);
            return;
        }
    };

    println!(
        "Command line was: '{str}'",
        str = String::from_utf16_lossy(&command_line_buffer)
    );

    let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };
    si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;
    let mut pi: MaybeUninit<PROCESS_INFORMATION> = MaybeUninit::uninit();
    let ret = unsafe {
        CreateProcessW(
            null(),
            command_line_buffer.as_mut_ptr(),
            null(),
            null(),
            FALSE,
            DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,
            null(),
            null(),
            &mut si.StartupInfo,
            pi.as_mut_ptr(),
        )
    };

    if ret == 0 {
        panic!("CreateProcessW failed");
    }

    let pi = unsafe { pi.assume_init() };

    unsafe { CloseHandle(pi.hThread) };

    // Later, we'll need to pass in a process handle.
    main_debugger_loop(pi.hProcess);

    unsafe { CloseHandle(pi.hProcess) };
}


================================================
FILE: src/memory.rs
================================================
use core::ffi::c_void;
use windows_sys::{Win32::Foundation, Win32::System::Diagnostics::Debug::*};

pub trait MemorySource {
    // Read up to "len" bytes, and return Option<u8> to represent what bytes are available in the range
    fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u8>>, &'static str>;
    // Read up to "len" bytes, and stop at the first failure
    fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8>;
}

pub fn read_memory_array<T: Sized + Default>(
    source: &dyn MemorySource,
    address: u64,
    max_count: usize,
) -> Result<Vec<T>, &'static str> {
    let element_size = ::core::mem::size_of::<T>();
    let max_bytes = max_count * element_size;
    let raw_bytes = source.read_raw_memory(address, max_bytes);
    let mut data: Vec<T> = Vec::new();
    let mut offset: usize = 0;
    while offset + element_size <= raw_bytes.len() {
        let mut item: T = T::default();
        let dst: *mut u8 = unsafe { std::mem::transmute(&mut item) };
        let src = &raw_bytes[offset] as *const u8;
        unsafe { std::ptr::copy_nonoverlapping(src, dst, element_size) };
        data.push(item);
        offset += element_size;
    }

    Ok(data)
}

pub fn read_memory_full_array<T: Sized + Default>(
    source: &dyn MemorySource,
    address: u64,
    count: usize,
) -> Result<Vec<T>, &'static str> {
    let arr = read_memory_array(source, address, count)?;

    if arr.len() != count {
        Err("Could not read all items")
    } else {
        Ok(arr)
    }
}

pub fn read_memory_data<T: Sized + Default + Copy>(
    source: &dyn MemorySource,
    address: u64,
) -> Result<T, &'static str> {
    let data = read_memory_array::<T>(source, address, 1)?;
    Ok(data[0])
}

pub fn read_memory_string(
    source: &dyn MemorySource,
    address: u64,
    max_count: usize,
    is_wide: bool,
) -> Result<String, &'static str> {
    let result: String = if is_wide {
        let mut words = read_memory_array::<u16>(source, address, max_count)?;
        let null_pos = words.iter().position(|&v| v == 0);
        if let Some(null_pos) = null_pos {
            words.truncate(null_pos);
        }
        String::from_utf16_lossy(&words)
    } else {
        let mut bytes = read_memory_array::<u8>(source, address, max_count)?;
        let null_pos = bytes.iter().position(|&v| v == 0);
        if let Some(null_pos) = null_pos {
            bytes.truncate(null_pos);
        }
        // TODO: This is not quite right. Technically most strings read here are encoded as ASCII.
        String::from_utf8(bytes).unwrap()
    };
    Ok(result)
}

pub fn read_memory_string_indirect(
    source: &dyn MemorySource,
    address: u64,
    max_count: usize,
    is_wide: bool,
) -> Result<String, &'static str> {
    let string_address = read_memory_data::<u64>(source, address)?;
    read_memory_string(source, string_address, max_count, is_wide)
}

struct LiveMemorySource {
    hprocess: Foundation::HANDLE,
}

pub fn make_live_memory_source(hprocess: Foundation::HANDLE) -> Box<dyn MemorySource> {
    Box::new(LiveMemorySource { hprocess })
}

impl MemorySource for LiveMemorySource {
    fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u8>>, &'static str> {
        let mut buffer: Vec<u8> = vec![0; len];
        let mut data: Vec<Option<u8>> = vec![None; len];
        let mut offset: usize = 0;

        while offset < len {
            let mut bytes_read: usize = 0;
            let len_left = len - offset;
            let cur_address = address + (offset as u64);

            let result = unsafe {
                ReadProcessMemory(
                    self.hprocess,
                    cur_address as *const c_void,
                    buffer.as_mut_ptr() as *mut c_void,
                    len_left,
                    &mut bytes_read as *mut usize,
                )
            };

            if result == 0 {
                return Err("ReadProcessMemory failed");
            };

            for index in 0..bytes_read {
                let data_index = offset + index;
                data[data_index] = Some(buffer[index]);
            }

            if bytes_read > 0 {
                offset += bytes_read;
            } else {
                offset += 1;
            }
        }

        Ok(data)
    }

    fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8> {
        let mut buffer: Vec<u8> = vec![0; len];
        let mut bytes_read: usize = 0;

        let result = unsafe {
            ReadProcessMemory(
                self.hprocess,
                address as *const c_void,
                buffer.as_mut_ptr() as *mut c_void,
                len,
                &mut bytes_read as *mut usize,
            )
        };

        if result == 0 {
            bytes_read = 0;
        }

        buffer.truncate(bytes_read);

        buffer
    }
}


================================================
FILE: src/module.rs
================================================
use crate::memory::{*, self};
use windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE_AMD64;
use windows::Win32::System::SystemServices::*;
use windows::Win32::System::Diagnostics::Debug::{*, IMAGE_DATA_DIRECTORY};
use pdb::{PDB, AddressMap};
use std::fs::File;

pub struct Module {
    pub name: String,
    pub address: u64,
    pub size: u64,
    pub exports: Vec::<Export>,
    pub pdb_name: Option<String>,
    pub pdb_info: Option<PdbInfo>,
    pub pdb: Option<PDB<'static, File>>,
    pub address_map: Option<AddressMap<'static>>,
    pe_header: IMAGE_NT_HEADERS64,
}

pub struct Export {
    pub name: Option<String>,
    // This is the "biased" ordinal
    pub ordinal: u32,
    pub target: ExportTarget,
}

impl ToString for Export {
    fn to_string(&self) -> String {
        if let Some(str) = &self.name {
            str.to_string()
        } else {
            format!("Ordinal{}", self.ordinal)
        }
    }
}

pub enum ExportTarget {
    RVA(u64),
    Forwarder(String)
}

#[derive(Default)]
#[repr(C)]
pub struct PdbInfo {
    pub signature: u32,
    pub guid: windows::core::GUID,
    pub age: u32,
    // Null terminated name goes after the end
}

impl ::core::marker::Copy for PdbInfo {}
impl ::core::clone::Clone for PdbInfo {
    fn clone(&self) -> Self {
        *self
    }
}

impl Module {
    pub fn from_memory_view(module_address: u64, module_name: Option<String>, memory_source: &dyn MemorySource) -> Result<Module, &'static str> {

        let dos_header: IMAGE_DOS_HEADER = memory::read_memory_data(memory_source, module_address)?;

        // NOTE: Do we trust that the headers are accurate, even if it means we could read outside the bounds of the
        //       module? For this debugger, we'll trust the data, but a real debugger should do sanity checks and 
        //       report discrepancies to the user in some way.
        let pe_header_addr = module_address + dos_header.e_lfanew as u64;

        // NOTE: This should be IMAGE_NT_HEADERS32 for 32-bit modules, but the FileHeader lines up for both structures.
        let pe_header: IMAGE_NT_HEADERS64 = memory::read_memory_data(memory_source, pe_header_addr)?;
        let size = pe_header.OptionalHeader.SizeOfImage as u64;

        if pe_header.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 {
            return Err("Unsupported machine architecture for module");
        }
        
        let (pdb_info, pdb_name, mut pdb) = Module::read_debug_info(&pe_header, module_address, memory_source)?;
        let (exports, export_table_module_name) = Module::read_exports(&pe_header, module_address, memory_source)?;

        let module_name = module_name.or(export_table_module_name);
         let module_name = match module_name {
            Some(s) => s,
            None => {
                format!("module_{:X}", module_address)
            }
        };
        let address_map = pdb.as_mut().and_then(|pdb| pdb.address_map().ok());

        Ok(Module{
            name: module_name,
            address: module_address,
            size,
            exports,
            pdb_info,
            pdb_name,
            pdb,
            address_map,
            pe_header
        })
    }

    pub fn contains_address(&self, address: u64) -> bool {
        let end = self.address + self.size;
        self.address <= address && address < end
    }

    fn read_debug_info(pe_header: &IMAGE_NT_HEADERS64, module_address: u64, memory_source: &dyn MemorySource) -> Result<(Option<PdbInfo>, Option<String>, Option<PDB<'static, File>>), &'static str> {
        let mut pdb_info: Option<PdbInfo> = None;
        let mut pdb_name: Option<String> = None;
        let mut pdb: Option<PDB<File>> = None;
        

        let debug_table_info = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG.0 as usize];
        if debug_table_info.VirtualAddress != 0 {
            let dir_size = std::mem::size_of::<IMAGE_DEBUG_DIRECTORY>() as u64;
            // We'll arbitrarily limit to 20 entries to keep it sane.
            let count: u64 = std::cmp::min(debug_table_info.Size as u64 / dir_size, 20);
            for dir_index in 0..count {
                let debug_directory_address = module_address + (debug_table_info.VirtualAddress as u64) + (dir_index * dir_size);
                let debug_directory: IMAGE_DEBUG_DIRECTORY = memory::read_memory_data(memory_source, debug_directory_address)?;
                if debug_directory.Type == IMAGE_DEBUG_TYPE_CODEVIEW {
                    let pdb_info_address = debug_directory.AddressOfRawData as u64 + module_address;
                    pdb_info = Some(memory::read_memory_data(memory_source, pdb_info_address)?);
                    // We could check that pdb_info.signature is RSDS here.
                    let pdb_name_address = pdb_info_address + std::mem::size_of::<PdbInfo>() as u64;
                    let max_size = debug_directory.SizeOfData as usize - std::mem::size_of::<PdbInfo>();
                    pdb_name = Some(memory::read_memory_string(memory_source, pdb_name_address, max_size, false)?);

                    let pdb_file = File::open(pdb_name.as_ref().unwrap());
                    if let Ok(pdb_file) = pdb_file {
                        let pdb_data = PDB::open(pdb_file);
                        if let Ok(pdb_data) = pdb_data {
                            pdb = Some(pdb_data);
                        }
                    }
                }
            }
        }

        Ok((pdb_info, pdb_name, pdb))
    }

    pub fn get_data_directory(&self, entry: IMAGE_DIRECTORY_ENTRY) -> IMAGE_DATA_DIRECTORY {
        self.pe_header.OptionalHeader.DataDirectory[entry.0 as usize]
    }

    fn read_exports(pe_header: &IMAGE_NT_HEADERS64, module_address: u64, memory_source: &dyn MemorySource) -> Result<(Vec::<Export>, Option<String>), &'static str> {
        let mut exports = Vec::<Export>::new();
        let mut module_name: Option<String> = None;
        let export_table_info = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];
        if export_table_info.VirtualAddress != 0 {
            let export_table_addr = module_address + export_table_info.VirtualAddress as u64;
            let export_table_end = export_table_addr + export_table_info.Size as u64;
            let export_directory: IMAGE_EXPORT_DIRECTORY = memory::read_memory_data(memory_source, export_table_addr)?;

            // This is a fallback that lets us find a name if none was available.
            if export_directory.Name != 0 {
                let name_addr = module_address + export_directory.Name as u64;
                module_name = Some(memory::read_memory_string(memory_source, name_addr, 512, false)?);
            }

            // We'll read the name table first, which is essentially a list of (ordinal, name) pairs that give names 
            // to some or all of the exports. The table is stored as parallel arrays of orindals and name pointers
            let ordinal_array_address = module_address + export_directory.AddressOfNameOrdinals as u64;
            let ordinal_array = memory::read_memory_full_array::<u16>(memory_source, ordinal_array_address, export_directory.NumberOfNames as usize)?;
            let name_array_address = module_address + export_directory.AddressOfNames as u64;
            let name_array = memory::read_memory_full_array::<u32>(memory_source, name_array_address, export_directory.NumberOfNames as usize)?;

            let address_table_address = module_address + export_directory.AddressOfFunctions as u64;
            let address_table = memory::read_memory_full_array::<u32>(memory_source, address_table_address, export_directory.NumberOfFunctions as usize)?;

            for (unbiased_ordinal, function_address) in address_table.iter().enumerate() {
                let ordinal = export_directory.Base + unbiased_ordinal as u32;
                let target_address = module_address + *function_address as u64;

                let name_index = ordinal_array.iter().position(|&o| o == unbiased_ordinal as u16);
                let export_name = match name_index {
                    None => None,
                    Some(idx) => {
                        let name_address = module_address + name_array[idx] as u64;
                        Some(memory::read_memory_string(memory_source, name_address, 4096, false)?)
                    }
                };

                // An address that falls inside the export directory is actually a forwarder
                if target_address >= export_table_addr && target_address < export_table_end {
                    // I don't know that there actually is a max size for a forwader name, but 4K is probably reasonable.
                    let forwarding_name = memory::read_memory_string(memory_source, target_address, 4096, false)?;
                    exports.push(Export {name: export_name, ordinal, target: ExportTarget::Forwarder(forwarding_name)});                    
                } else {
                    exports.push(Export{name: export_name, ordinal, target: ExportTarget::RVA(target_address)});
                }
            }
        };

        Ok((exports, module_name))
    }
}

================================================
FILE: src/name_resolution.rs
================================================
use pdb::FallibleIterator;
use pdb::SymbolData;

use crate::{process::Process, module::{Export, ExportTarget, Module}};
use anyhow::anyhow;

enum AddressMatch<'a> {
    None,
    Export(&'a Export),
    Public(String)
}
impl AddressMatch<'_> {
    fn is_none(&self) -> bool {
        match self {
            AddressMatch::None => true,
            _ => false
        }
    }
}

pub fn resolve_name_to_address(sym: &str, process: &mut Process) -> Result<u64, anyhow::Error> {
    match sym.chars().position(|c| c == '!') {
        None => {
            // Search all modules
            Err(anyhow!("Not yet implemented"))
        },
        Some(pos) => {
            let module_name = &sym[..pos];
            let func_name = &sym[pos + 1..];
            if let Some(module) = process.get_module_by_name_mut(module_name) {
                if let Some(addr) = resolve_function_in_module(module, func_name) {
                    Ok(addr)
                } else {
                    Err(anyhow!("Could not find {} in module {}", func_name, module_name))
                }
            } else {
                Err(anyhow!("Could not find module {}", module_name))
            }
        },
    }
}

pub fn resolve_function_in_module(module: &mut Module, func: &str) -> Option<u64> {
    // We'll search exports first and private symbols next
    let export_resolution = resolve_export_in_module(module, func);
    if export_resolution.is_some() {
        return export_resolution;
    }

    resolve_symbol_name_in_module(module, func).unwrap_or(None)
}

fn resolve_export_in_module(module: &mut Module, func: &str) -> Option<u64> {
    // We'll search exports first and private symbols next
    for export in module.exports.iter() {
        if let Some(export_name) = &export.name {
            if *export_name == *func {
                // Just support direct exports for now, rather than forwarded functions.
                if let ExportTarget::RVA(export_addr) = export.target {
                    return Some(export_addr)
                }
            }
        }
    }
    None
}

fn resolve_symbol_name_in_module(module: &mut Module, func: &str) -> Result<Option<u64>, anyhow::Error> {
    let pdb = module.pdb.as_mut().ok_or(anyhow!("No PDB loaded"))?;
    let dbi = pdb.debug_information()?;
    let mut modules = dbi.modules()?;
    let address_map = module.address_map.as_mut().ok_or(anyhow!("No address map available"))?;
    while let Some(pdb_module) = modules.next()? {
        let mi = pdb.module_info(&pdb_module)?.ok_or(anyhow!("Couldn't get module info"))?;
        let mut symbols = mi.symbols()?;
        while let Some(sym) = symbols.next()? {
            if let Ok(parsed) = sym.parse() {
                if let SymbolData::Procedure(proc_data) = parsed {
                    if proc_data.name.to_string() == func {
                        let rva = proc_data.offset.to_rva(address_map).ok_or(anyhow!("Couldn't convert procedure offset to RVA"))?;
                        let address = module.address + rva.0 as u64;
                        return Ok(Some(address));
                    }
                }
            }
        }
    }
    Ok(None)
}


pub fn resolve_address_to_name(address: u64, process: &mut Process) -> Option<String> {
    let module = match process.get_containing_module_mut(address) {
        Some(module) => module,
        None => return None
    };

    let mut closest: AddressMatch = AddressMatch::None;
    let mut closest_addr: u64 = 0;
    // This could be faster if we were always in sorted order
    for export in module.exports.iter() {
        if let ExportTarget::RVA(export_addr) = export.target {
            if export_addr <= address {
                if closest.is_none() || closest_addr < export_addr {
                    closest = AddressMatch::Export(export);
                    closest_addr = export_addr;
                }
            }
        };
    }

    if let Some(pdb) = module.pdb.as_mut() {
        if let Ok(symbol_table) = pdb.global_symbols() {
            if let Ok(address_map) = pdb.address_map() {
                let mut symbols = symbol_table.iter();
                while let Ok(Some(symbol)) = symbols.next() {
                    match symbol.parse() {
                        Ok(pdb::SymbolData::Public(data)) if data.function => {
                            let rva = data.offset.to_rva(&address_map).unwrap_or_default();
                            let global_addr = module.address + rva.0 as u64;
                            if global_addr <= address && (closest.is_none() || closest_addr <= global_addr) {
                                // TODO: Take a reference to the data?
                                closest = AddressMatch::Public(data.name.to_string().to_string());
                                closest_addr = global_addr;
                            }
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    if let AddressMatch::Export(closest) = closest {
        let offset = address - closest_addr;
        let sym_with_offset = if offset == 0 {
            format!("{}!{}", &module.name, closest.to_string())
        } else {
            format!("{}!{}+0x{:X}", &module.name, closest.to_string(), offset)
        };
        return Some(sym_with_offset)
    }

    if let AddressMatch::Public(closest) = closest {
        let offset = address - closest_addr;
        let sym_with_offset = if offset == 0 {
            format!("{}!{}", &module.name, closest)
        } else {
            format!("{}!{}+0x{:X}", &module.name, closest, offset)
        };
        return Some(sym_with_offset)
    }
    
    None
}

================================================
FILE: src/process.rs
================================================
use windows_sys::Win32::Foundation;

use crate::{module::Module, memory::MemorySource};

pub struct Process {
    module_list: std::vec::Vec<Module>,
    thread_list: std::vec::Vec<u32>,
}

impl Process {
    pub fn new() -> Process {
        Process { module_list: Vec::new(), thread_list: Vec::new() }
    }

    pub fn add_module(&mut self, address: u64, name: Option<String>, memory_source: &dyn MemorySource) -> Result<&Module, &'static str> {
        let module = Module::from_memory_view(address, name, memory_source)?;
        self.module_list.push(module);
        Ok(self.module_list.last().unwrap())
    }

    pub fn add_thread(&mut self, thread_id: u32) {
        self.thread_list.push(thread_id);
    }

    pub fn remove_thread(&mut self, thread_id: u32) {
        self.thread_list.retain(|x| *x != thread_id);
    }

    pub fn iterate_threads(&self) -> core::slice::Iter<'_, u32> {
        self.thread_list.iter()
    }

    pub fn _get_containing_module(&self, address: u64) -> Option<&Module> {
        for module in self.module_list.iter() {
            if module.contains_address(address) {
                return Some(&module);
            }
        };

        None
    }

    pub fn get_containing_module_mut(&mut self, address: u64) -> Option<&mut Module> {
        for module in self.module_list.iter_mut() {
            if module.contains_address(address) {
                return Some(module);
            }
        };

        None
    }

    pub fn get_module_by_name_mut(&mut self, module_name: &str) -> Option<&mut Module> {
        let mut potential_trimmed_match = None;
        let mut potential_trimmed_noext_match = None;
    
        for module in self.module_list.iter_mut() {
            if module.name == module_name {
                return Some(module);
            }
    
            let trimmed = module.name.rsplitn(2, '\\').next().unwrap_or(&module.name);
            if potential_trimmed_match.is_none() && trimmed.to_lowercase() == module_name.to_lowercase() {
                potential_trimmed_match = Some(module);
            } else if potential_trimmed_noext_match.is_none() {
                let parts: Vec<&str> = trimmed.rsplitn(2, '.').collect();
                let trimmed_noext = if parts.len() == 2 {
                    parts[1]
                } else {
                    parts[0]
                };
                if trimmed_noext.to_lowercase() == module_name.to_lowercase() {
                    potential_trimmed_noext_match = Some(module);
                }
            }
        };
    
        potential_trimmed_match.or(potential_trimmed_noext_match)
    }
}


================================================
FILE: src/registers.rs
================================================
use windows_sys::{
    Win32::System::{Diagnostics::Debug::*},
};

pub fn display_all(context: &CONTEXT) {
    println!("rax={:#018x} rbx={:#018x} rcx={:#018x}", context.Rax, context.Rbx, context.Rcx);
    println!("rdx={:#018x} rsi={:#018x} rdi={:#018x}", context.Rdx, context.Rsi, context.Rdi);
    println!("rip={:#018x} rsp={:#018x} rbp={:#018x}", context.Rip, context.Rsp, context.Rbp);
    println!(" r8={:#018x}  r9={:#018x} r10={:#018x}", context.R8, context.R9, context.R10);
    println!("r11={:#018x} r12={:#018x} r13={:#018x}", context.R11, context.R12, context.R13);
    println!("r14={:#018x} r15={:#018x} eflags={:#010x}", context.R14, context.R15, context.EFlags);
}

pub fn display_named(context: &CONTEXT, reg_name: &str) {
    if let Ok(val) = get_register(context, reg_name) {
        println!("{}={:#018x}", reg_name.to_lowercase(), val);
    } else {
        println!("Unrecognized register name: {}", reg_name);
    }
}

pub fn get_register(context: &CONTEXT, reg_name: &str) -> Result<u64, String> {
    let val = match reg_name.to_lowercase().as_str() {
        "rax" => context.Rax,
        "rbx" => context.Rbx,
        "rcx" => context.Rcx,
        "rdx" => context.Rdx,
        "rsi" => context.Rsi,
        "rdi" => context.Rdi,
        "rip" => context.Rip,
        "rsp" => context.Rsp,
        "rbp" => context.Rbp,
        "r8" => context.R8,
        "r9" => context.R9,
        "r10" => context.R10,
        "r11" => context.R11,
        "r12" => context.R12,
        "r13" => context.R13,
        "r14" => context.R14,
        "r15" => context.R15,
        "eflags" => context.EFlags as u64,
        _ => return Err("Unrecognized register".to_string())
    };
    Ok(val)
}


================================================
FILE: src/source.rs
================================================
use std::path::{Path, PathBuf};

use pdb::{FallibleIterator, LineProgram, Rva, StringTable};
use anyhow::{Result, anyhow};

use crate::process::Process;

fn line_program_references_file(line_program: &LineProgram, src_file: &str, string_table: &StringTable) -> Result<bool> {
    let mut files = line_program.files();
    while let Some(file) = files.next()? {
        let cur_file_name = string_table.get(file.name)?.to_string();
        if cur_file_name == src_file {
            return Ok(true);
        }
    }
    Ok(false)
}

pub fn resolve_source_line_to_address(module_name: &str, src_file: &str, src_line: u32, process: &mut Process) -> Result<u64> {
    let process_module = process.get_module_by_name_mut(module_name).ok_or(anyhow!("Module not found"))?;
    let pdb = process_module.pdb.as_mut().ok_or(anyhow!("Symbols not available"))?;
    let address_map = process_module.address_map.as_mut().ok_or(anyhow!("Address map not found for module"))?;
    let string_table = pdb.string_table()?;
    let dbi = pdb.debug_information()?;
    let mut modules = dbi.modules()?;
    while let Some(module) = modules.next()? {
        if let Ok(Some(mi)) = pdb.module_info(&module) {
            if let Ok(line_program) = mi.line_program() {
                if line_program_references_file(&line_program, src_file, &string_table)? {
                    let mut lines = line_program.lines();
                    while let Some(line) = lines.next()? {
                        let cur_file_name = string_table.get(line_program.get_file_info(line.file_index)?.name)?.to_string();
                        if cur_file_name == src_file && line.line_start <= src_line && src_line <= line.line_end {
                            let rva = line.offset.to_rva(&address_map).ok_or(anyhow!("Could not map source entry to RVA"))?;
                            let address = process_module.address + rva.0 as u64;
                            return Ok(address);
                        }
                    }
                }
            }
        }
    }

    Err(anyhow!("Source line not found"))
}

pub fn resolve_address_to_source_line(address: u64, process: &mut Process) -> Result<(String, u32)> {
    let module = process.get_containing_module_mut(address).ok_or(anyhow!("Module not found"))?;
    let pdb = module.pdb.as_mut().ok_or(anyhow!("Symbols not available"))?;

    let address_map = module.address_map.as_mut().ok_or(anyhow!("Address map not found for module"))?;
    let rva: u32 = (address - module.address).try_into()?;
    let rva = Rva(rva);
    let offset = rva.to_internal_offset(address_map).ok_or(anyhow!("Couldn't map address"))?;

    let dbi = pdb.debug_information()?;
    let mut modules = dbi.modules()?;
    while let Some(module) = modules.next()? {
        if let Ok(Some(mi)) = pdb.module_info(&module) {
            if let Ok(lp) = mi.line_program() {
                let mut lines = lp.lines_for_symbol(offset);
                while let Some(line) = lines.next()? {
                    if line.offset.offset <= offset.offset {
                        let diff = offset.offset - line.offset.offset;
                        if diff < line.length.unwrap_or(0) {
                            let file_info = lp.get_file_info(line.file_index)?;
                            let string_table = pdb.string_table()?;
                            let file_name = string_table.get(file_info.name)?;
                            return Ok((file_name.to_string().into(), line.line_start))
                        }
                        
                    }
                }
            }
        }
    }
    Err(anyhow!("Address not found"))
}

pub fn find_source_file_match(file: &str, search_paths: &Vec<String>) -> Result<PathBuf> {
    let file_path = Path::new(file);

    // If the file path is absolute and exists, return it immediately.
    if file_path.is_absolute() && file_path.exists() {
        return Ok(file_path.to_path_buf());
    }

    // Get all subsets of the input path.
    let components: Vec<&str> = file_path.components().map(|c| c.as_os_str().to_str().unwrap()).collect();

    for search_path in search_paths {
        let search_path = Path::new(search_path);

        for i in 0..components.len() {
            // Join the search path with the subset of the input path.
            let test_path: PathBuf = search_path.join(components[i..].iter().collect::<PathBuf>());
            if test_path.exists() {
                return Ok(test_path.to_path_buf());
            }
        }
    }

    Err(anyhow!("File not found"))
}

================================================
FILE: src/stack.rs
================================================
use windows::Win32::System::Diagnostics::Debug::IMAGE_DIRECTORY_ENTRY_EXCEPTION;
use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;
use crate::{process::Process, memory::{MemorySource, read_memory_full_array, read_memory_data}};

#[repr(C)]
#[derive(Default, Clone)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct RUNTIME_FUNCTION {
    pub BeginAddress: u32,
    pub EndAddress: u32,
    pub UnwindInfo: u32,
}

#[repr(C)]
#[derive(Default, Clone, Copy)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct UNWIND_INFO {
    pub version_flags: u8,
    pub size_of_prolog: u8,
    pub count_of_codes: u8,
    pub frame_register_offset: u8,
}

#[repr(C)]
#[derive(Default, Clone, Copy)]
#[allow(non_snake_case, non_camel_case_types)]
pub struct UNWIND_CODE {
    pub code_offset: u8,
    pub unwind_op_info: u8,
}

const UWOP_PUSH_NONVOL: u8 = 0;     /* info == register number */
const UWOP_ALLOC_LARGE: u8 = 1;     /* no info, alloc size in next 2 slots */
const UWOP_ALLOC_SMALL: u8 = 2;     /* info == size of allocation / 8 - 1 */
const UWOP_SET_FPREG: u8 = 3;       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
const UWOP_SAVE_NONVOL: u8 = 4;     /* info == register number, offset in next slot */
const UWOP_SAVE_NONVOL_FAR: u8 = 5; /* info == register number, offset in next 2 slots */
const UWOP_SAVE_XMM128: u8 = 8;     /* info == XMM reg number, offset in next slot */
const UWOP_SAVE_XMM128_FAR: u8 = 9; /* info == XMM reg number, offset in next 2 slots */
const UWOP_PUSH_MACHFRAME: u8 = 10; /* info == 0: no error-code, 1: error-code */

const UNW_FLAG_NHANDLER: u8 = 0x0;
const UNW_FLAG_EHANDLER: u8 = 0x1;
const UNW_FLAG_UHANDLER: u8 = 0x2;
const UNW_FLAG_CHAININFO: u8 = 0x4;

// These represent the logical operations, so large/small and far/near are merged
#[derive(Debug)]
enum UnwindOp {
    PushNonVolatile { reg: u8 },
    Alloc { size: u32 },
    SetFpreg { frame_register: u8, frame_offset: u16 },
    SaveNonVolatile { reg: u8, offset: u32 },
    SaveXmm128 { reg: u8, offset: u32 },
    PushMachFrame { error_code: bool }
}

// Does not directly correspond to UNWIND_CODE
#[derive(Debug)]
struct UnwindCode {
    code_offset: u8,
    op: UnwindOp,
}

fn find_runtime_function(addr: u32, function_list: &[RUNTIME_FUNCTION]) -> Option<&RUNTIME_FUNCTION> {
    let index = function_list.binary_search_by(|func| func.BeginAddress.cmp(&addr));

    match index {
        // Exact match
        Ok(pos) => function_list.get(pos),
        // Inexact match
        Err(pos) => {
            if pos > 0 && function_list.get(pos - 1).map_or(false, |func| func.BeginAddress <= addr && addr < func.EndAddress) {
                function_list.get(pos - 1)
            } else if pos < function_list.len() && function_list.get(pos).map_or(false, |func| func.BeginAddress <= addr && addr < func.EndAddress) {
                function_list.get(pos)
            } else {
                None
            }
        }
    }
}

// Splits an integer up that represents bitfields so that each field can be stored in a tuple. Specify the
// size of the fields from low bits to high bits. For instance, let (x, y, z) = split_up!(q => 3, 6, 7) will put the low 3 bits into x
macro_rules! split_up {
    ($value:expr => $($len:expr),+) => {
        {
            let mut value = $value;
            // Use a tuple to collect the fields
            ( $(
                {
                    let field = value & ((1 << $len) - 1); // Mask the value to get the field
                    value >>= $len; // Shift the value for the next field
                    field
                }
            ),+ ) // The '+' sign indicates one or more repetitions
        }
    };
}

fn get_unwind_ops(code_slots: &[u16], frame_register: u8, frame_offset: u16) -> Result<Vec<UnwindCode>, &'static str> {
    let mut ops = Vec::<UnwindCode>::new();

    let mut i = 0;
    while i < code_slots.len() {
        let (code_offset, unwind_op, op_info) = split_up!(code_slots[i] => 8, 4, 4);
        let code_offset = code_offset as u8;
        let unwind_op = unwind_op as u8;
        let op_info = op_info as u8;
        match unwind_op {
            UWOP_PUSH_NONVOL => {
                ops.push(UnwindCode { code_offset, op: UnwindOp::PushNonVolatile { reg: op_info } });
            }
            UWOP_ALLOC_LARGE if op_info == 0 => {
                if i + 1 >= code_slots.len() {
                    return Err("UWOP_ALLOC_LARGE was incomplete");
                }
                let size = (code_slots[i + 1] as u32) * 8;
                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });
                i += 1;
            }
            UWOP_ALLOC_LARGE if op_info == 1 => {
                if i + 2 >= code_slots.len() {
                    return Err("UWOP_ALLOC_LARGE was incomplete");
                }
                let size = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);
                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });
                i += 2;
            }
            UWOP_ALLOC_SMALL => {
                let size = (op_info as u32) * 8 + 8;
                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });
            }
            UWOP_SET_FPREG => {
                ops.push(UnwindCode { code_offset, op: UnwindOp::SetFpreg { frame_register, frame_offset } });
            }
            UWOP_SAVE_NONVOL => {
                if i + 1 >= code_slots.len() {
                    return Err("UWOP_SAVE_NONVOL was incomplete");
                }
                let offset = code_slots[i + 1] as u32;
                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveNonVolatile { reg: op_info, offset } });
                i += 1;
            }
            UWOP_SAVE_NONVOL_FAR => {
                if i + 2 >= code_slots.len() {
                    return Err("UWOP_SAVE_NONVOL_FAR was incomplete");
                }
                let offset = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);
                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveNonVolatile { reg: op_info, offset } });
                i += 2;
            }
            UWOP_SAVE_XMM128 => {
                if i + 1 >= code_slots.len() {
                    return Err("UWOP_SAVE_XMM128 was incomplete");
                }
                let offset = code_slots[i + 1] as u32;
                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveXmm128 { reg: op_info, offset: offset } });
                i += 1;
            }
            UWOP_SAVE_XMM128_FAR => {
                if i + 2 >= code_slots.len() {
                    return Err("UWOP_SAVE_XMM128_FAR was incomplete");
                }
                let offset = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);
                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveXmm128 { reg: op_info, offset: offset } });
                i += 2;
            }
            _ => return Err("Unrecognized unwind op")
        }
        i += 1;
    }

    Ok(ops)
}

fn get_op_register<'a>(context: &'a mut CONTEXT, reg: u8) -> &'a mut u64 {
    match reg {
        0 => &mut context.Rax,
        1 => &mut context.Rcx,
        2 => &mut context.Rdx,
        3 => &mut context.Rbx,
        4 => &mut context.Rsp,
        5 => &mut context.Rbp,
        6 => &mut context.Rsi,
        7 => &mut context.Rdi,
        8 => &mut context.R8,
        9 => &mut context.R9,
        10 => &mut context.R10,
        11 => &mut context.R11,
        12 => &mut context.R12,
        13 => &mut context.R13,
        14 => &mut context.R14,
        15 => &mut context.R15,
        _ => panic!("Bad register given to get_op_register()")
    }
}

fn apply_unwind_ops(context: &CONTEXT, unwind_ops: &[UnwindCode], func_address: u64, memory_source: &dyn MemorySource) -> Result<Option<CONTEXT>, &'static str> {
    let mut unwound_context = context.clone();
    for unwind in unwind_ops.iter() {
        let func_offset = unwound_context.Rip - func_address;
        if unwind.code_offset as u64 <= func_offset {
            match unwind.op {
                UnwindOp::Alloc { size } => {
                    unwound_context.Rsp += size as u64;
                }
                UnwindOp::PushNonVolatile { reg } => {
                    let addr = unwound_context.Rsp;
                    let val = read_memory_data::<u64>(memory_source, addr)?;
                    *get_op_register(&mut unwound_context, reg) = val;
                    unwound_context.Rsp += 8;
                }
                UnwindOp::SaveNonVolatile { reg, offset } => {
                    let addr = unwound_context.Rsp + offset as u64;
                    let val = read_memory_data::<u64>(memory_source, addr)?;
                    *get_op_register(&mut unwound_context, reg) = val;
                }
                UnwindOp::SetFpreg { frame_register, frame_offset } => {
                    unwound_context.Rsp = *get_op_register(&mut unwound_context, frame_register) - (frame_offset as u64);
                }
                _ => panic!("NYI unwind op")
            }
        }
    }
    Ok(Some(unwound_context))
}

pub fn unwind_context(process: &mut Process, context: CONTEXT, memory_source: &dyn MemorySource) -> Result<Option<CONTEXT>, &'static str> {
    let module = process.get_containing_module_mut(context.Rip);
    if let Some(module) = module {
        let data_directory = module.get_data_directory(IMAGE_DIRECTORY_ENTRY_EXCEPTION);
        if data_directory.VirtualAddress != 0 && data_directory.Size != 0 {
            let count = data_directory.Size as usize / std::mem::size_of::<RUNTIME_FUNCTION>();
            let table_address = module.address + data_directory.VirtualAddress as u64;

            // Note: In a real debugger you might want to cache these.
            let functions: Vec<RUNTIME_FUNCTION> = read_memory_full_array(memory_source, table_address, count)?;

            let rva = context.Rip - module.address;
            let func = find_runtime_function(rva as u32, &functions);

            if let Some(func) = func {
                // We have unwind data!
                let info_addr = module.address + func.UnwindInfo as u64;
                let info = read_memory_data::<UNWIND_INFO>(memory_source, info_addr)?;
                let (_version, flags) = split_up!(info.version_flags => 3, 5);
                if flags & UNW_FLAG_CHAININFO == UNW_FLAG_CHAININFO {
                    return Err("NYI: Chained info");
                }

                let (frame_register, frame_offset) = split_up!(info.frame_register_offset => 4, 4);
                let frame_offset = (frame_offset as u16) * 16;
                // The codes are UNWIND_CODE, but we'll have to break them up in different ways anyway based on the operation, so we might as well just
                // read them as u16 and then parse out the fields as needed.
                let codes = read_memory_full_array::<u16>(memory_source, info_addr + 4, info.count_of_codes as usize)?;
                let unwind_ops = get_unwind_ops(&codes, frame_register, frame_offset)?;
                match apply_unwind_ops(&context, &unwind_ops, module.address + func.BeginAddress as u64, memory_source)? {
                    Some(ctx) => {
                        let mut ctx = ctx;
                        ctx.Rip = read_memory_data::<u64>(memory_source, ctx.Rsp)?;
                        ctx.Rsp += 8;

                        // TODO: There are other conditions that should be checked
                        if ctx.Rip == 0 {
                            return Ok(None);
                        }
                        return Ok(Some(ctx))
                    },
                    _ => return Ok(None)
                }
                
            } else {
                // Leaf function: the return address is simply at [RSP]
                let mut ctx = context;
                ctx.Rip = read_memory_data::<u64>(memory_source, ctx.Rsp)?;
                ctx.Rsp += 8;
                return Ok(Some(ctx));
            }
        }
    }
    
    Ok(None)
}

================================================
FILE: src/unassemble.rs
================================================
use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, MasmFormatter};

use crate::memory::MemorySource;

pub fn unassemble(memory_source: &dyn MemorySource, va: u64, lines: usize) -> u64 {

    // We'll never need more than lines * 15
    let bytes = memory_source.read_raw_memory(va, lines * 15);
    if bytes.len() == 0 {
        println!("Failed to read memory at {:X}", va);
    }

    let code_bitness = 64;
    let hexbytes_column_byte_length = 10;
    let mut decoder = Decoder::with_ip(
        code_bitness,
        bytes.as_slice(),
        va,
        DecoderOptions::NONE,
    );

    // Formatters: Masm*, Nasm*, Gas* (AT&T) and Intel* (XED).
    // For fastest code, see `SpecializedFormatter` which is ~3.3x faster. Use it if formatting
    // speed is more important than being able to re-assemble formatted instructions.
    let mut formatter = MasmFormatter::new();

    // Change some options, there are many more
    //formatter.options_mut().set_digit_separator("`");
    formatter.options_mut().set_first_operand_char_index(10);

    // String implements FormatterOutput
    let mut output = String::new();

    // Initialize this outside the loop because decode_out() writes to every field
    let mut instruction = Instruction::default();

    // The decoder also implements Iterator/IntoIterator so you could use a for loop:
    //      for instruction in &mut decoder { /* ... */ }
    // or collect():
    //      let instructions: Vec<_> = decoder.into_iter().collect();
    // but can_decode()/decode_out() is a little faster:
    let mut instruction_count = 0;
    let mut last_rip = 0;
    while decoder.can_decode() && instruction_count < lines {
        // There's also a decode() method that returns an instruction but that also
        // means it copies an instruction (40 bytes):
        //     instruction = decoder.decode();
        decoder.decode_out(&mut instruction);

        // Format the instruction ("disassemble" it)
        output.clear();
        formatter.format(&instruction, &mut output);

        // Eg. "00007FFAC46ACDB2 488DAC2400FFFFFF     lea       rbp,[rsp-100h]"
        print!("{:016X} ", instruction.ip());
        let start_index = (instruction.ip() - va) as usize;
        let instr_bytes = &bytes[start_index..start_index + instruction.len()];
        for b in instr_bytes.iter() {
            print!("{:02X}", b);
        }
        if instr_bytes.len() < hexbytes_column_byte_length {
            for _ in 0..hexbytes_column_byte_length - instr_bytes.len() {
                print!("  ");
            }
        }
        println!(" {}", output);
        instruction_count += 1;
        last_rip = instruction.ip() + instr_bytes.len() as u64;
    }
    last_rip
}


================================================
FILE: src/util.rs
================================================
use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;
use windows_sys::Win32::Foundation::*;

// Not sure why these are missing from windows_sys, but the definitions are in winnt.h
pub const CONTEXT_AMD64: u32 = 0x00100000;
pub const CONTEXT_CONTROL: u32 = CONTEXT_AMD64 | 0x00000001;
pub const CONTEXT_INTEGER: u32 = CONTEXT_AMD64 | 0x00000002;
pub const CONTEXT_SEGMENTS: u32 = CONTEXT_AMD64 | 0x00000004;
pub const CONTEXT_FLOATING_POINT: u32 = CONTEXT_AMD64 | 0x00000008;
pub const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_AMD64 | 0x00000010;
#[allow(dead_code)]
pub const CONTEXT_FULL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT;
pub const CONTEXT_ALL: u32 = CONTEXT_CONTROL
        | CONTEXT_INTEGER
        | CONTEXT_SEGMENTS
        | CONTEXT_FLOATING_POINT
        | CONTEXT_DEBUG_REGISTERS;

#[repr(align(16))]
pub struct AlignedContext {
    pub context: CONTEXT,
}
        

pub struct AutoClosedHandle(pub HANDLE);

impl Drop for AutoClosedHandle {
    fn drop(&mut self) {
        unsafe {
            CloseHandle(self.0);
        }
    }
}

impl AutoClosedHandle {
    pub fn handle(&self) -> HANDLE {
        self.0
    }
}
Download .txt
gitextract_ezw1bihx/

├── .gitignore
├── .vscode/
│   ├── launch.json
│   └── tasks.json
├── Cargo.toml
├── LICENSE
├── build.rs
└── src/
    ├── breakpoint.rs
    ├── command.rs
    ├── eval.rs
    ├── event.rs
    ├── main.rs
    ├── memory.rs
    ├── module.rs
    ├── name_resolution.rs
    ├── process.rs
    ├── registers.rs
    ├── source.rs
    ├── stack.rs
    ├── unassemble.rs
    └── util.rs
Download .txt
SYMBOL INDEX (123 symbols across 15 files)

FILE: build.rs
  function main (line 3) | fn main() {

FILE: src/breakpoint.rs
  constant DR7_LEN_BIT (line 13) | const DR7_LEN_BIT: [usize; 4] = [19, 23, 27, 31];
  constant DR7_RW_BIT (line 14) | const DR7_RW_BIT: [usize; 4] = [17, 21, 25, 29];
  constant DR7_LE_BIT (line 15) | const DR7_LE_BIT: [usize; 4] = [0, 2, 4, 6];
  constant DR7_GE_BIT (line 16) | const DR7_GE_BIT: [usize; 4] = [1, 3, 5, 7];
  constant DR7_LEN_SIZE (line 18) | const DR7_LEN_SIZE: usize = 2;
  constant DR7_RW_SIZE (line 19) | const DR7_RW_SIZE: usize = 2;
  constant DR6_B_BIT (line 21) | const DR6_B_BIT: [usize; 4] = [0, 1, 2, 3];
  constant EFLAG_RF (line 23) | const EFLAG_RF: usize = 16;
  type Breakpoint (line 25) | struct Breakpoint {
  type BreakpointManager (line 30) | pub struct BreakpointManager {
    method new (line 53) | pub fn new() -> BreakpointManager {
    method get_free_id (line 57) | fn get_free_id(&self) -> u32 {
    method add_breakpoint (line 66) | pub fn add_breakpoint(&mut self, addr: u64) {
    method list_breakpoints (line 71) | pub fn list_breakpoints(&self, process: &mut Process) {
    method clear_breakpoint (line 81) | pub fn clear_breakpoint(&mut self, id: u32) {
    method was_breakpoint_hit (line 85) | pub fn was_breakpoint_hit(&self, thread_context: &CONTEXT) -> Option<u...
    method apply_breakpoints (line 94) | pub fn apply_breakpoints(&mut self, process: &mut Process, resume_thre...
  function set_bits (line 34) | fn set_bits<T: PrimInt>(val: &mut T, set_val: T, start_bit: usize, bit_c...
  function get_bit (line 45) | fn get_bit<T: PrimInt>(val: T, bit_index: usize) -> bool {

FILE: src/command.rs
  type CommandExpr (line 11) | pub enum CommandExpr {
  type EvalExpr (line 31) | pub enum EvalExpr {
  type Whitespace (line 44) | struct Whitespace {
  function parse_int (line 49) | fn parse_int(text: &str) -> u64 {
  function parse_sym (line 59) | fn parse_sym(text: &str) -> String {
  function parse_path (line 63) | fn parse_path(text: &str) -> String {
  function parse_source_line (line 67) | fn parse_source_line(text: &str) -> (String, String, u32) {
  function convert_parse_error_to_diagnostics (line 81) | fn convert_parse_error_to_diagnostics(
  function read_command (line 130) | pub fn read_command() -> grammar::CommandExpr {

FILE: src/eval.rs
  type EvalContext (line 8) | pub struct EvalContext<'a> {
  function evaluate_expression (line 14) | pub fn evaluate_expression(expr: EvalExpr, context: &mut EvalContext) ->...

FILE: src/event.rs
  type DebugEvent (line 9) | pub enum DebugEvent {
  type EventContext (line 20) | pub struct EventContext {
  function wait_for_next_debug_event (line 25) | pub fn wait_for_next_debug_event(mem_source: &dyn MemorySource) -> (Even...

FILE: src/main.rs
  constant TRAP_FLAG (line 31) | const TRAP_FLAG: u32 = 1 << 8;
  function show_usage (line 33) | fn show_usage(error_message: &str) {
  function wcslen (line 38) | unsafe fn wcslen(ptr: *const u16) -> usize {
  function parse_command_line (line 50) | fn parse_command_line() -> Result<Vec<u16>, &'static str> {
  function load_module_at_address (line 79) | fn load_module_at_address(process: &mut Process, memory_source: &dyn Mem...
  function main_debugger_loop (line 85) | fn main_debugger_loop(process: HANDLE) {
  function main (line 317) | fn main() {

FILE: src/memory.rs
  type MemorySource (line 4) | pub trait MemorySource {
    method read_memory (line 6) | fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u...
    method read_raw_memory (line 8) | fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8>;
    method read_memory (line 99) | fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u...
    method read_raw_memory (line 138) | fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8> {
  function read_memory_array (line 11) | pub fn read_memory_array<T: Sized + Default>(
  function read_memory_full_array (line 33) | pub fn read_memory_full_array<T: Sized + Default>(
  function read_memory_data (line 47) | pub fn read_memory_data<T: Sized + Default + Copy>(
  function read_memory_string (line 55) | pub fn read_memory_string(
  function read_memory_string_indirect (line 80) | pub fn read_memory_string_indirect(
  type LiveMemorySource (line 90) | struct LiveMemorySource {
  function make_live_memory_source (line 94) | pub fn make_live_memory_source(hprocess: Foundation::HANDLE) -> Box<dyn ...

FILE: src/module.rs
  type Module (line 8) | pub struct Module {
    method from_memory_view (line 59) | pub fn from_memory_view(module_address: u64, module_name: Option<Strin...
    method contains_address (line 101) | pub fn contains_address(&self, address: u64) -> bool {
    method read_debug_info (line 106) | fn read_debug_info(pe_header: &IMAGE_NT_HEADERS64, module_address: u64...
    method get_data_directory (line 142) | pub fn get_data_directory(&self, entry: IMAGE_DIRECTORY_ENTRY) -> IMAG...
    method read_exports (line 146) | fn read_exports(pe_header: &IMAGE_NT_HEADERS64, module_address: u64, m...
  type Export (line 20) | pub struct Export {
  method to_string (line 28) | fn to_string(&self) -> String {
  type ExportTarget (line 37) | pub enum ExportTarget {
  type PdbInfo (line 44) | pub struct PdbInfo {
    method clone (line 53) | fn clone(&self) -> Self {

FILE: src/name_resolution.rs
  type AddressMatch (line 7) | enum AddressMatch<'a> {
  function is_none (line 13) | fn is_none(&self) -> bool {
  function resolve_name_to_address (line 21) | pub fn resolve_name_to_address(sym: &str, process: &mut Process) -> Resu...
  function resolve_function_in_module (line 43) | pub fn resolve_function_in_module(module: &mut Module, func: &str) -> Op...
  function resolve_export_in_module (line 53) | fn resolve_export_in_module(module: &mut Module, func: &str) -> Option<u...
  function resolve_symbol_name_in_module (line 68) | fn resolve_symbol_name_in_module(module: &mut Module, func: &str) -> Res...
  function resolve_address_to_name (line 92) | pub fn resolve_address_to_name(address: u64, process: &mut Process) -> O...

FILE: src/process.rs
  type Process (line 5) | pub struct Process {
    method new (line 11) | pub fn new() -> Process {
    method add_module (line 15) | pub fn add_module(&mut self, address: u64, name: Option<String>, memor...
    method add_thread (line 21) | pub fn add_thread(&mut self, thread_id: u32) {
    method remove_thread (line 25) | pub fn remove_thread(&mut self, thread_id: u32) {
    method iterate_threads (line 29) | pub fn iterate_threads(&self) -> core::slice::Iter<'_, u32> {
    method _get_containing_module (line 33) | pub fn _get_containing_module(&self, address: u64) -> Option<&Module> {
    method get_containing_module_mut (line 43) | pub fn get_containing_module_mut(&mut self, address: u64) -> Option<&m...
    method get_module_by_name_mut (line 53) | pub fn get_module_by_name_mut(&mut self, module_name: &str) -> Option<...

FILE: src/registers.rs
  function display_all (line 5) | pub fn display_all(context: &CONTEXT) {
  function display_named (line 14) | pub fn display_named(context: &CONTEXT, reg_name: &str) {
  function get_register (line 22) | pub fn get_register(context: &CONTEXT, reg_name: &str) -> Result<u64, St...

FILE: src/source.rs
  function line_program_references_file (line 8) | fn line_program_references_file(line_program: &LineProgram, src_file: &s...
  function resolve_source_line_to_address (line 19) | pub fn resolve_source_line_to_address(module_name: &str, src_file: &str,...
  function resolve_address_to_source_line (line 47) | pub fn resolve_address_to_source_line(address: u64, process: &mut Proces...
  function find_source_file_match (line 80) | pub fn find_source_file_match(file: &str, search_paths: &Vec<String>) ->...

FILE: src/stack.rs
  type RUNTIME_FUNCTION (line 8) | pub struct RUNTIME_FUNCTION {
  type UNWIND_INFO (line 17) | pub struct UNWIND_INFO {
  type UNWIND_CODE (line 27) | pub struct UNWIND_CODE {
  constant UWOP_PUSH_NONVOL (line 32) | const UWOP_PUSH_NONVOL: u8 = 0;
  constant UWOP_ALLOC_LARGE (line 33) | const UWOP_ALLOC_LARGE: u8 = 1;
  constant UWOP_ALLOC_SMALL (line 34) | const UWOP_ALLOC_SMALL: u8 = 2;
  constant UWOP_SET_FPREG (line 35) | const UWOP_SET_FPREG: u8 = 3;
  constant UWOP_SAVE_NONVOL (line 36) | const UWOP_SAVE_NONVOL: u8 = 4;
  constant UWOP_SAVE_NONVOL_FAR (line 37) | const UWOP_SAVE_NONVOL_FAR: u8 = 5;
  constant UWOP_SAVE_XMM128 (line 38) | const UWOP_SAVE_XMM128: u8 = 8;
  constant UWOP_SAVE_XMM128_FAR (line 39) | const UWOP_SAVE_XMM128_FAR: u8 = 9;
  constant UWOP_PUSH_MACHFRAME (line 40) | const UWOP_PUSH_MACHFRAME: u8 = 10;
  constant UNW_FLAG_NHANDLER (line 42) | const UNW_FLAG_NHANDLER: u8 = 0x0;
  constant UNW_FLAG_EHANDLER (line 43) | const UNW_FLAG_EHANDLER: u8 = 0x1;
  constant UNW_FLAG_UHANDLER (line 44) | const UNW_FLAG_UHANDLER: u8 = 0x2;
  constant UNW_FLAG_CHAININFO (line 45) | const UNW_FLAG_CHAININFO: u8 = 0x4;
  type UnwindOp (line 49) | enum UnwindOp {
  type UnwindCode (line 60) | struct UnwindCode {
  function find_runtime_function (line 65) | fn find_runtime_function(addr: u32, function_list: &[RUNTIME_FUNCTION]) ...
  function get_unwind_ops (line 102) | fn get_unwind_ops(code_slots: &[u16], frame_register: u8, frame_offset: ...
  function get_op_register (line 178) | fn get_op_register<'a>(context: &'a mut CONTEXT, reg: u8) -> &'a mut u64 {
  function apply_unwind_ops (line 200) | fn apply_unwind_ops(context: &CONTEXT, unwind_ops: &[UnwindCode], func_a...
  function unwind_context (line 230) | pub fn unwind_context(process: &mut Process, context: CONTEXT, memory_so...

FILE: src/unassemble.rs
  function unassemble (line 5) | pub fn unassemble(memory_source: &dyn MemorySource, va: u64, lines: usiz...

FILE: src/util.rs
  constant CONTEXT_AMD64 (line 5) | pub const CONTEXT_AMD64: u32 = 0x00100000;
  constant CONTEXT_CONTROL (line 6) | pub const CONTEXT_CONTROL: u32 = CONTEXT_AMD64 | 0x00000001;
  constant CONTEXT_INTEGER (line 7) | pub const CONTEXT_INTEGER: u32 = CONTEXT_AMD64 | 0x00000002;
  constant CONTEXT_SEGMENTS (line 8) | pub const CONTEXT_SEGMENTS: u32 = CONTEXT_AMD64 | 0x00000004;
  constant CONTEXT_FLOATING_POINT (line 9) | pub const CONTEXT_FLOATING_POINT: u32 = CONTEXT_AMD64 | 0x00000008;
  constant CONTEXT_DEBUG_REGISTERS (line 10) | pub const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_AMD64 | 0x00000010;
  constant CONTEXT_FULL (line 12) | pub const CONTEXT_FULL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEX...
  constant CONTEXT_ALL (line 13) | pub const CONTEXT_ALL: u32 = CONTEXT_CONTROL
  type AlignedContext (line 20) | pub struct AlignedContext {
  type AutoClosedHandle (line 25) | pub struct AutoClosedHandle(pub HANDLE);
    method handle (line 36) | pub fn handle(&self) -> HANDLE {
  method drop (line 28) | fn drop(&mut self) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (84K chars).
[
  {
    "path": ".gitignore",
    "chars": 6,
    "preview": "target"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1326,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 204,
    "preview": "{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"type\": \"cargo\",\n\t\t\t\"command\": \"build\",\n\t\t\t\"problemMatcher\": [\n\t\t\t\t\"$rustc\",\n\t"
  },
  {
    "path": "Cargo.toml",
    "chars": 889,
    "preview": "[package]\nname = \"dbgrs\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-l"
  },
  {
    "path": "LICENSE",
    "chars": 1049,
    "preview": "Copyright 2023 Tim Misiak\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software"
  },
  {
    "path": "build.rs",
    "chars": 154,
    "preview": "use std::path::PathBuf;\n\nfn main() {\n    println!(\"cargo:rerun-if-changed=src\");\n    rust_sitter_tool::build_parsers(&Pa"
  },
  {
    "path": "src/breakpoint.rs",
    "chars": 5239,
    "preview": "use windows_sys::Win32::System::Diagnostics::Debug::GetThreadContext;\nuse windows_sys::Win32::System::Diagnostics::Debug"
  },
  {
    "path": "src/command.rs",
    "chars": 6416,
    "preview": "use std::io::Write;\nuse regex::Regex;\n\nuse codemap::CodeMap;\nuse codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, "
  },
  {
    "path": "src/eval.rs",
    "chars": 1189,
    "preview": "use crate::command::grammar::EvalExpr;\nuse crate::process::Process;\nuse crate::name_resolution::resolve_name_to_address;"
  },
  {
    "path": "src/event.rs",
    "chars": 4740,
    "preview": "use std::os::windows::prelude::OsStringExt;\n\nuse windows_sys::Win32::{System::{Diagnostics::Debug::{DEBUG_EVENT, WaitFor"
  },
  {
    "path": "src/main.rs",
    "chars": 14261,
    "preview": "use event::DebugEvent;\nuse memory::MemorySource;\nuse windows_sys::{\n    Win32::Foundation::*,\n    Win32::System::Environ"
  },
  {
    "path": "src/memory.rs",
    "chars": 4870,
    "preview": "use core::ffi::c_void;\nuse windows_sys::{Win32::Foundation, Win32::System::Diagnostics::Debug::*};\n\npub trait MemorySour"
  },
  {
    "path": "src/module.rs",
    "chars": 9208,
    "preview": "use crate::memory::{*, self};\nuse windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE_AMD64;\nuse windows::Win3"
  },
  {
    "path": "src/name_resolution.rs",
    "chars": 5704,
    "preview": "use pdb::FallibleIterator;\nuse pdb::SymbolData;\n\nuse crate::{process::Process, module::{Export, ExportTarget, Module}};\n"
  },
  {
    "path": "src/process.rs",
    "chars": 2632,
    "preview": "use windows_sys::Win32::Foundation;\n\nuse crate::{module::Module, memory::MemorySource};\n\npub struct Process {\n    module"
  },
  {
    "path": "src/registers.rs",
    "chars": 1710,
    "preview": "use windows_sys::{\n    Win32::System::{Diagnostics::Debug::*},\n};\n\npub fn display_all(context: &CONTEXT) {\n    println!("
  },
  {
    "path": "src/source.rs",
    "chars": 4572,
    "preview": "use std::path::{Path, PathBuf};\n\nuse pdb::{FallibleIterator, LineProgram, Rva, StringTable};\nuse anyhow::{Result, anyhow"
  },
  {
    "path": "src/stack.rs",
    "chars": 12138,
    "preview": "use windows::Win32::System::Diagnostics::Debug::IMAGE_DIRECTORY_ENTRY_EXCEPTION;\nuse windows_sys::Win32::System::Diagnos"
  },
  {
    "path": "src/unassemble.rs",
    "chars": 2738,
    "preview": "use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, MasmFormatter};\n\nuse crate::memory::MemorySource;\n\npub f"
  },
  {
    "path": "src/util.rs",
    "chars": 1166,
    "preview": "use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;\nuse windows_sys::Win32::Foundation::*;\n\n// Not sure why the"
  }
]

About this extraction

This page contains the full source code of the TimMisiak/dbgrs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (78.3 KB), approximately 18.9k tokens, and a symbol index with 123 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!