[
  {
    "path": ".gitignore",
    "content": "target"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"type\": \"lldb\",\n            \"request\": \"launch\",\n            \"name\": \"Debug executable 'dbgrs'\",\n            \"cargo\": {\n                \"args\": [\n                    \"build\",\n                    \"--bin=dbgrs\",\n                    \"--package=dbgrs\"\n                ],\n                \"filter\": {\n                    \"name\": \"dbgrs\",\n                    \"kind\": \"bin\"\n                }\n            },\n            \"args\": [\"C:\\\\git\\\\HelloWorld\\\\hello.exe\"],\n            \"cwd\": \"${workspaceFolder}\"\n        },\n        {\n            \"type\": \"lldb\",\n            \"request\": \"launch\",\n            \"name\": \"Debug unit tests in executable 'dbgrs'\",\n            \"cargo\": {\n                \"args\": [\n                    \"test\",\n                    \"--no-run\",\n                    \"--bin=dbgrs\",\n                    \"--package=dbgrs\"\n                ],\n                \"filter\": {\n                    \"name\": \"dbgrs\",\n                    \"kind\": \"bin\"\n                }\n            },\n            \"args\": [],\n            \"cwd\": \"${workspaceFolder}\"\n        }\n    ]\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\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\t\t\t\"$rust-panic\"\n\t\t\t],\n\t\t\t\"group\": \"build\",\n\t\t\t\"label\": \"rust: cargo build\"\n\t\t}\n\t]\n}"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"dbgrs\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nrust-sitter = \"0.2.1\"\ncodemap = \"0.1.3\"\ncodemap-diagnostic = \"0.1.1\"\npdb = \"0.8.0\"\nnum-traits = \"0.2.15\"\niced-x86 = \"1.20.0\"\nanyhow = \"1.0.79\"\nregex = \"*\"\n\n[build-dependencies]\nrust-sitter-tool = \"0.2.1\"\n\n[dependencies.windows-sys]\nversion = \"0.48.0\"\nfeatures = [\n    \"Win32_Foundation\",\n    \"Win32_Security\",\n    \"Win32_Storage_FileSystem\",\n    \"Win32_System_Kernel\",\n    \"Win32_System_Threading\",\n    \"Win32_UI_WindowsAndMessaging\",\n    \"Win32_System_Diagnostics_Debug\",\n    \"Win32_System_Environment\",\n    \"Win32_System_WindowsProgramming\",\n]\n\n[dependencies.windows]\nversion = \"0.48.0\"\nfeatures = [\n    \"Win32_System_Diagnostics_Debug\",\n    \"Win32_System_SystemInformation\",\n    \"Win32_System_SystemServices\",\n]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2023 Tim Misiak\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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."
  },
  {
    "path": "build.rs",
    "content": "use std::path::PathBuf;\n\nfn main() {\n    println!(\"cargo:rerun-if-changed=src\");\n    rust_sitter_tool::build_parsers(&PathBuf::from(\"src/command.rs\"));\n}\n"
  },
  {
    "path": "src/breakpoint.rs",
    "content": "use windows_sys::Win32::System::Diagnostics::Debug::GetThreadContext;\nuse windows_sys::Win32::System::Diagnostics::Debug::SetThreadContext;\nuse windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;\nuse windows_sys::Win32::System::Threading::*;\nuse windows_sys::Win32::Foundation::*;\nuse num_traits::int::PrimInt;\n\nuse crate::memory::MemorySource;\nuse crate::process::Process;\nuse crate::name_resolution;\nuse crate::util::*;\n\nconst DR7_LEN_BIT: [usize; 4] = [19, 23, 27, 31];\nconst DR7_RW_BIT: [usize; 4] = [17, 21, 25, 29];\nconst DR7_LE_BIT: [usize; 4] = [0, 2, 4, 6];\nconst DR7_GE_BIT: [usize; 4] = [1, 3, 5, 7];\n\nconst DR7_LEN_SIZE: usize = 2;\nconst DR7_RW_SIZE: usize = 2;\n\nconst DR6_B_BIT: [usize; 4] = [0, 1, 2, 3];\n\nconst EFLAG_RF: usize = 16;\n\nstruct Breakpoint {\n    addr: u64,\n    id: u32,\n}\n\npub struct BreakpointManager {\n    breakpoints: Vec::<Breakpoint>,\n}\n\nfn set_bits<T: PrimInt>(val: &mut T, set_val: T, start_bit: usize, bit_count: usize) {\n    // First, mask out the relevant bits\n    let max_bits = std::mem::size_of::<T>() * 8;\n    let mask: T = T::max_value() << (max_bits - bit_count);\n    let mask: T = mask >> (max_bits - 1 - start_bit);\n    let inv_mask = !mask;\n\n    *val = *val & inv_mask;\n    *val = *val | (set_val << (start_bit + 1 - bit_count));\n}\n\nfn get_bit<T: PrimInt>(val: T, bit_index: usize) -> bool {\n    let mask = T::one() << bit_index;\n    let masked_val = val & mask;\n    masked_val != T::zero()\n}\n\nimpl BreakpointManager {\n\n    pub fn new() -> BreakpointManager {\n        BreakpointManager { breakpoints: Vec::new() }\n    }\n\n    fn get_free_id(&self) -> u32 {\n        for i in 0..4 {\n            if self.breakpoints.iter().find(|&x| x.id == i).is_none() {\n                return i;\n            }\n        }\n        panic!(\"Too many breakpoints!\")\n    }\n\n    pub fn add_breakpoint(&mut self, addr: u64) {\n        self.breakpoints.push(Breakpoint{addr, id: self.get_free_id()});\n        self.breakpoints.sort_by(|a, b| a.id.cmp(&b.id));\n    }\n\n    pub fn list_breakpoints(&self, process: &mut Process) {\n        for bp in self.breakpoints.iter() {\n            if let Some(sym) = name_resolution::resolve_address_to_name(bp.addr, process) {\n                println!(\"{:3} {:#018x} ({})\", bp.id, bp.addr, sym)\n            } else {\n                println!(\"{:3} {:#018x}\", bp.id, bp.addr)\n            }            \n        }\n    }\n\n    pub fn clear_breakpoint(&mut self, id: u32) {\n        self.breakpoints.retain(|x| x.id != id)\n    }\n\n    pub fn was_breakpoint_hit(&self, thread_context: &CONTEXT) -> Option<u32> {\n        for idx in 0..self.breakpoints.len() {\n            if get_bit(thread_context.Dr6, DR6_B_BIT[idx]) {\n                return Some(idx as u32);\n            }\n        }\n        None\n    }\n\n    pub fn apply_breakpoints(&mut self, process: &mut Process, resume_thread_id: u32, _memory_source: &dyn MemorySource) {\n\n        for thread_id in process.iterate_threads() {\n            let mut ctx: AlignedContext = unsafe { std::mem::zeroed() };\n            ctx.context.ContextFlags = CONTEXT_ALL;            \n            let thread = AutoClosedHandle(unsafe {\n                OpenThread(\n                    THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,\n                    FALSE,\n                    *thread_id,\n                )\n            });\n            let ret = unsafe { GetThreadContext(thread.handle(), &mut ctx.context) };\n\n            if ret == 0 {\n                println!(\"Could not get thread context of thread {:x}\", thread_id);\n                continue;\n            }\n\n            // Currently there is a limit of 4 breakpoints, since we are using hardware breakpoints.\n            for idx in 0..4 {\n                if self.breakpoints.len() > idx {\n                    \n                    set_bits(&mut ctx.context.Dr7, 0, DR7_LEN_BIT[idx], DR7_LEN_SIZE);\n                    set_bits(&mut ctx.context.Dr7, 0, DR7_RW_BIT[idx], DR7_RW_SIZE);\n                    set_bits(&mut ctx.context.Dr7, 1, DR7_LE_BIT[idx], 1);\n                    match idx {\n                        0 => ctx.context.Dr0 = self.breakpoints[idx].addr,\n                        1 => ctx.context.Dr1 = self.breakpoints[idx].addr,\n                        2 => ctx.context.Dr2 = self.breakpoints[idx].addr,\n                        3 => ctx.context.Dr3 = self.breakpoints[idx].addr,\n                        _ => (),\n                    }\n                } else {\n                    // We'll assume that we own all breakpoints. This will cause problems with programs that expect to control their own debug registers.\n                    // As a result, we'll disable any breakpoints that we aren't using.\n                    set_bits(&mut ctx.context.Dr7, 0, DR7_LE_BIT[idx], 1);\n                    break;\n                }    \n            }\n\n            // This prevents the current thread from hitting a breakpoint on the current instruction\n            if *thread_id == resume_thread_id {\n                set_bits(&mut ctx.context.EFlags, 1, EFLAG_RF, 1);\n            }\n\n            let ret = unsafe { SetThreadContext(thread.handle(), &mut ctx.context) };\n            if ret == 0 {\n                println!(\"Could not set thread context of thread {:x}\", thread_id);\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/command.rs",
    "content": "use std::io::Write;\nuse regex::Regex;\n\nuse codemap::CodeMap;\nuse codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};\nuse rust_sitter::errors::{ParseError, ParseErrorReason};\n\n#[rust_sitter::grammar(\"command\")]\npub mod grammar {\n    #[rust_sitter::language]\n    pub enum CommandExpr {\n        StepInto(#[rust_sitter::leaf(text = \"t\")] ()),\n        Go(#[rust_sitter::leaf(text = \"g\")] ()),\n        SetBreakpoint(#[rust_sitter::leaf(text = \"bp\")] (), Box<EvalExpr>),\n        ListBreakpoints(#[rust_sitter::leaf(text = \"bl\")] ()),\n        ClearBreakpoint(#[rust_sitter::leaf(text = \"bc\")] (), Box<EvalExpr>),\n        DisplaySpecificRegister(#[rust_sitter::leaf(text = \"r\")] (), #[rust_sitter::leaf(pattern = \"([a-zA-Z]+)\", transform = parse_sym)] String),\n        DisplayRegisters(#[rust_sitter::leaf(text = \"r\")] ()),\n        StackWalk(#[rust_sitter::leaf(text = \"k\")] ()),\n        DisplayBytes(#[rust_sitter::leaf(text = \"db\")] (), Box<EvalExpr>),\n        Evaluate(#[rust_sitter::leaf(text = \"?\")] (), Box<EvalExpr>),\n        ListNearest(#[rust_sitter::leaf(text = \"ln\")] (), Box<EvalExpr>),\n        Unassemble(#[rust_sitter::leaf(text = \"u\")] (), Box<EvalExpr>),\n        UnassembleContinue(#[rust_sitter::leaf(text = \"u\")] ()),\n        ListSource(#[rust_sitter::leaf(text = \"lsa\")] (), Box<EvalExpr>),\n        SrcPath(#[rust_sitter::leaf(text = \".srcpath\")] (), #[rust_sitter::leaf(pattern = \"(.*)\", transform = parse_path)] String),\n        Quit(#[rust_sitter::leaf(text = \"q\")] ()),\n    }\n\n    #[rust_sitter::language]\n    pub enum EvalExpr {\n        Number(#[rust_sitter::leaf(pattern = r\"(\\d+|0x[0-9a-fA-F]+)\", transform = parse_int)] u64),\n        Symbol(#[rust_sitter::leaf(pattern = r\"(([a-zA-Z0-9_@#.]+!)?[a-zA-Z0-9_@#.]+)\", transform = parse_sym)] String),\n        SourceLine(#[rust_sitter::leaf(pattern = r\"(`[^`!]+!(?:[a-zA-Z]+:)[^`:!]+:\\d+`)\", transform = parse_source_line)] (String, String, u32)),\n        #[rust_sitter::prec_left(1)]\n        Add(\n            Box<EvalExpr>,\n            #[rust_sitter::leaf(text = \"+\")] (),\n            Box<EvalExpr>,\n        ),\n    }\n\n    #[rust_sitter::extra]\n    struct Whitespace {\n        #[rust_sitter::leaf(pattern = r\"\\s\")]\n        _whitespace: (),\n    }\n\n    fn parse_int(text: &str) -> u64 {\n        let text = text.trim();\n        if text.starts_with(\"0x\") {\n            let text = text.split_at(2).1;\n            u64::from_str_radix(text, 16).unwrap()\n        } else {\n            text.parse().unwrap()\n        }\n    }\n\n    fn parse_sym(text: &str) -> String {\n        text.to_owned()\n    }\n\n    fn parse_path(text: &str) -> String {\n        text.trim().to_owned()\n    }\n\n    fn parse_source_line(text: &str) -> (String, String, u32) {\n        let re = regex::Regex::new(r\"^`([^!]+)!((?:[a-zA-Z]+:)[^:]+):(\\d+)`$\").unwrap();\n        if let Some(captures) = re.captures(text) {\n            let module_name = captures.get(1).unwrap().as_str().to_string();\n            let file_name = captures.get(2).unwrap().as_str().to_string();\n            let line_number = captures.get(3).unwrap().as_str().parse::<u32>().unwrap();\n            (module_name, file_name, line_number)\n        } else {\n            panic!(\"Unexpected\")\n        }\n    }\n}\n\n// This came from https://github.com/hydro-project/rust-sitter/blob/main/example/src/main.rs\nfn convert_parse_error_to_diagnostics(\n    file_span: &codemap::Span,\n    error: &ParseError,\n    diagnostics: &mut Vec<Diagnostic>,\n) {\n    match &error.reason {\n        ParseErrorReason::MissingToken(tok) => diagnostics.push(Diagnostic {\n            level: Level::Error,\n            message: format!(\"Missing token: \\\"{tok}\\\"\"),\n            code: Some(\"S000\".to_string()),\n            spans: vec![SpanLabel {\n                span: file_span.subspan(error.start as u64, error.end as u64),\n                style: SpanStyle::Primary,\n                label: Some(format!(\"missing \\\"{tok}\\\"\")),\n            }],\n        }),\n\n        ParseErrorReason::UnexpectedToken(tok) => diagnostics.push(Diagnostic {\n            level: Level::Error,\n            message: format!(\"Unexpected token: \\\"{tok}\\\"\"),\n            code: Some(\"S000\".to_string()),\n            spans: vec![SpanLabel {\n                span: file_span.subspan(error.start as u64, error.end as u64),\n                style: SpanStyle::Primary,\n                label: Some(format!(\"unexpected \\\"{tok}\\\"\")),\n            }],\n        }),\n\n        ParseErrorReason::FailedNode(errors) => {\n            if errors.is_empty() {\n                diagnostics.push(Diagnostic {\n                    level: Level::Error,\n                    message: \"Failed to parse node\".to_string(),\n                    code: Some(\"S000\".to_string()),\n                    spans: vec![SpanLabel {\n                        span: file_span.subspan(error.start as u64, error.end as u64),\n                        style: SpanStyle::Primary,\n                        label: Some(\"failed\".to_string()),\n                    }],\n                })\n            } else {\n                for error in errors {\n                    convert_parse_error_to_diagnostics(file_span, error, diagnostics);\n                }\n            }\n        }\n    }\n}\n\npub fn read_command() -> grammar::CommandExpr {\n    let stdin = std::io::stdin();\n    loop {\n        print!(\"> \");\n        std::io::stdout().flush().unwrap();\n        let mut input = String::new();\n        stdin.read_line(&mut input).unwrap();\n        let input = input.trim().to_string();\n        if !input.is_empty() {\n            let cmd = grammar::parse(&input);\n            match cmd {\n                Ok(c) => return c,\n                Err(errs) => {\n                    // This came from https://github.com/hydro-project/rust-sitter/blob/main/example/src/main.rs\n                    let mut codemap = CodeMap::new();\n                    let file_span = codemap.add_file(\"<input>\".to_string(), input.to_string());\n                    let mut diagnostics = vec![];\n                    for error in errs {\n                        convert_parse_error_to_diagnostics(\n                            &file_span.span,\n                            &error,\n                            &mut diagnostics,\n                        );\n                    }\n\n                    let mut emitter = Emitter::stderr(ColorConfig::Always, Some(&codemap));\n                    emitter.emit(&diagnostics);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/eval.rs",
    "content": "use crate::command::grammar::EvalExpr;\nuse crate::process::Process;\nuse crate::name_resolution::resolve_name_to_address;\nuse crate::registers::get_register;\nuse crate::source::resolve_source_line_to_address;\nuse windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;\n\npub struct EvalContext<'a> {\n    pub process: &'a mut Process,\n    // TODO: This should really be an abstraction on top of the context\n    pub register_context: &'a CONTEXT,\n}\n\npub fn evaluate_expression(expr: EvalExpr, context: &mut EvalContext) -> Result<u64, anyhow::Error> {\n    match expr {\n        EvalExpr::Number(x) => Ok(x),\n        EvalExpr::Add(x, _, y) => Ok(evaluate_expression(*x, context)? + evaluate_expression(*y, context)?),\n        EvalExpr::Symbol(sym) => {\n            if sym.starts_with('@') {\n                if let Ok(val) = get_register(context.register_context, &sym[1..]) {\n                    return Ok(val);\n                }\n            }\n            resolve_name_to_address(&sym, context.process)\n        },\n        EvalExpr::SourceLine((src_module, src_file, src_line)) => {\n            resolve_source_line_to_address(&src_module, &src_file, src_line, context.process)\n        }\n    }\n}\n"
  },
  {
    "path": "src/event.rs",
    "content": "use std::os::windows::prelude::OsStringExt;\n\nuse 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};\n\nuse crate::memory::{MemorySource, self};\n\n#[allow(non_snake_case)]\n\npub enum DebugEvent {\n    Exception{first_chance: bool, exception_code: i32},\n    CreateProcess{exe_name: Option<String>, exe_base: u64},\n    CreateThread{thread_id: u32},\n    ExitThread{thread_id: u32},\n    LoadModule{module_name: Option<String>, module_base: u64},\n    OutputDebugString(String),\n    ExitProcess,\n    Other(String)\n}\n\npub struct EventContext {\n    pub process_id: u32,\n    pub thread_id: u32,\n}\n\npub fn wait_for_next_debug_event(mem_source: &dyn MemorySource) -> (EventContext, DebugEvent) {\n    let mut debug_event: DEBUG_EVENT = unsafe { std::mem::zeroed() };\n    unsafe {\n        WaitForDebugEventEx(&mut debug_event, INFINITE);\n    }\n\n    let ctx = EventContext{ process_id: debug_event.dwProcessId, thread_id: debug_event.dwThreadId };\n\n    match debug_event.dwDebugEventCode {\n        EXCEPTION_DEBUG_EVENT => {\n            let code = unsafe { debug_event.u.Exception.ExceptionRecord.ExceptionCode };\n            let first_chance = unsafe { debug_event.u.Exception.dwFirstChance };\n            (ctx, DebugEvent::Exception { first_chance: first_chance != 0, exception_code: code })\n        },\n        CREATE_THREAD_DEBUG_EVENT => {\n            let create_thread = unsafe { debug_event.u.CreateThread };\n            let thread_id = unsafe { GetThreadId(create_thread.hThread) };\n            unsafe { CloseHandle(create_thread.hThread) };\n            (ctx, DebugEvent::CreateThread { thread_id } )\n        },\n        EXIT_THREAD_DEBUG_EVENT => {\n            (ctx, DebugEvent::ExitThread { thread_id: debug_event.dwThreadId} )\n        },\n        CREATE_PROCESS_DEBUG_EVENT => {\n            let create_process = unsafe { debug_event.u.CreateProcessInfo };\n            let exe_base = create_process.lpBaseOfImage as u64;\n            let mut exe_name = vec![0u16; 260];\n            let exe_name_len = unsafe { GetFinalPathNameByHandleW(create_process.hFile, exe_name.as_mut_ptr(), 260, 0) } as usize;\n            let exe_name = if exe_name_len != 0 {\n                // This will be the full name, e.g. \\\\?\\C:\\git\\HelloWorld\\hello.exe\n                // It might be useful to have the full name, but it's not available for all\n                // modules in all cases.\n                let full_path = std::ffi::OsString::from_wide(&exe_name[0..exe_name_len]);\n                let file_name = std::path::Path::new(&full_path).file_name();\n\n                match file_name {\n                    None => None,\n                    Some(s) => Some(s.to_string_lossy().to_string())\n                }\n            } else {\n                None\n            };\n            \n            //load_module_at_address(&mut process, mem_source.as_ref(), exe_base, exe_name);\n            (ctx, DebugEvent::CreateProcess { exe_name, exe_base })\n        },\n        EXIT_PROCESS_DEBUG_EVENT => (ctx, DebugEvent::ExitProcess),\n        LOAD_DLL_DEBUG_EVENT => {\n            let load_dll = unsafe { debug_event.u.LoadDll };\n            let module_base: u64 = load_dll.lpBaseOfDll as u64;\n            let module_name = if load_dll.lpImageName == std::ptr::null_mut() {\n                None\n            } else {\n                let is_wide = load_dll.fUnicode != 0;\n                memory::read_memory_string_indirect(mem_source, load_dll.lpImageName as u64, 260, is_wide)\n                    .map_or(None, |x| Some(x))\n            };\n\n            //load_module_at_address(&mut process, mem_source.as_ref(), dll_base, dll_name);\n            (ctx, DebugEvent::LoadModule { module_name, module_base })\n        }\n        UNLOAD_DLL_DEBUG_EVENT => (ctx, DebugEvent::Other(\"UnloadDll\".to_string())),\n        OUTPUT_DEBUG_STRING_EVENT => {\n            let debug_string_info = unsafe { debug_event.u.DebugString };\n            let is_wide = debug_string_info.fUnicode != 0;\n            let address = debug_string_info.lpDebugStringData as u64;\n            let len = debug_string_info.nDebugStringLength as usize;\n            let debug_string =\n                memory::read_memory_string(mem_source, address, len, is_wide).unwrap();\n            (ctx, DebugEvent::OutputDebugString(debug_string))\n        }\n        RIP_EVENT => (ctx, DebugEvent::Other(\"RipEvent\".to_string())),\n        _ => panic!(\"Unexpected debug event\"),\n    }\n}"
  },
  {
    "path": "src/main.rs",
    "content": "use event::DebugEvent;\nuse memory::MemorySource;\nuse windows_sys::{\n    Win32::Foundation::*,\n    Win32::System::Environment::*,\n    Win32::System::{Diagnostics::Debug::*, Threading::*},\n};\n\nuse std::{fs::File, io::{self, BufRead}, mem::MaybeUninit, ptr::null, cmp::{max, min}};\n\nmod command;\nmod eval;\nmod memory;\nmod process;\nmod registers;\nmod stack;\nmod module;\nmod name_resolution;\nmod event;\nmod breakpoint;\nmod util;\nmod unassemble;\nmod source;\n\nuse process::Process;\nuse command::grammar::{CommandExpr, EvalExpr};\nuse breakpoint::BreakpointManager;\nuse util::*;\nuse source::resolve_address_to_source_line;\n\nconst TRAP_FLAG: u32 = 1 << 8;\n\nfn show_usage(error_message: &str) {\n    println!(\"Error: {msg}\", msg = error_message);\n    println!(\"Usage: DbgRs <Command Line>\");\n}\n\nunsafe fn wcslen(ptr: *const u16) -> usize {\n    let mut len = 0;\n    while *ptr.add(len) != 0 {\n        len += 1;\n    }\n    len\n}\n\n// 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\n// command line options such as attaching to processes.\n// Q: Why not just convert to UTF8?\n// 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.\nfn parse_command_line() -> Result<Vec<u16>, &'static str> {\n    let cmd_line = unsafe {\n        // As far as I can tell, standard rust command line argument parsing won't preserve spaces. So we'll call\n        // the win32 api directly and then parse it out.\n        let p = GetCommandLineW();\n        let len = wcslen(p);\n        std::slice::from_raw_parts(p, len + 1)\n    };\n\n    let mut cmd_line_iter = cmd_line.iter().copied();\n\n    let first = cmd_line_iter.next().ok_or(\"Command line was empty\")?;\n\n    // If the first character is a quote, we need to find a matching end quote. Otherwise, the first space.\n    let end_char = (if first == '\"' as u16 { '\"' } else { ' ' }) as u16;\n\n    loop {\n        let next = cmd_line_iter.next().ok_or(\"No arguments found\")?;\n        if next == end_char {\n            break;\n        }\n    }\n\n    // Now we need to skip any whitespace\n    let cmd_line_iter = cmd_line_iter.skip_while(|x| x == &(' ' as u16));\n\n    Ok(cmd_line_iter.collect())\n}\n\nfn load_module_at_address(process: &mut Process, memory_source: &dyn MemorySource, base_address: u64, module_name: Option<String>) {\n    let module = process.add_module(base_address, module_name, memory_source).unwrap();\n\n    println!(\"LoadDll: {:X}   {}\", base_address, module.name);\n}\n\nfn main_debugger_loop(process: HANDLE) {\n    let mut expect_step_exception = false;\n    let mem_source = memory::make_live_memory_source(process);\n    let mut process = Process::new();\n    let mut breakpoints = BreakpointManager::new();\n\n    let mut source_search_paths = Vec::new();\n\n    loop {\n        let (event_context, debug_event) = event::wait_for_next_debug_event(mem_source.as_ref());\n\n        // The thread context will be needed to determine what to do with some events\n        let thread = AutoClosedHandle(unsafe {\n            OpenThread(\n                THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,\n                FALSE,\n                event_context.thread_id,\n            )\n        });\n\n        let mut ctx: AlignedContext = unsafe { std::mem::zeroed() };\n        ctx.context.ContextFlags = CONTEXT_ALL;\n        let ret = unsafe { GetThreadContext(thread.handle(), &mut ctx.context) };\n        if ret == 0 {\n            panic!(\"GetThreadContext failed\");\n        }\n\n        let mut continue_status = DBG_CONTINUE;\n        let mut is_exit = false;\n        match debug_event {\n            DebugEvent::Exception { first_chance, exception_code } => {\n                let chance_string = if first_chance {\n                    \"first chance\"\n                } else {\n                    \"second chance\"\n                };\n\n                if expect_step_exception && exception_code == EXCEPTION_SINGLE_STEP {\n                    expect_step_exception = false;\n                    continue_status = DBG_CONTINUE;\n                } else if let Some(bp_index) = breakpoints.was_breakpoint_hit(&ctx.context) {\n                    println!(\"Breakpoint {} hit\", bp_index);\n                    continue_status = DBG_CONTINUE;\n                } else {\n                    println!(\"Exception code {:x} ({})\", exception_code, chance_string);\n                    continue_status = DBG_EXCEPTION_NOT_HANDLED;\n                }\n            },\n            DebugEvent::CreateProcess { exe_name, exe_base } => {\n                load_module_at_address(&mut process, mem_source.as_ref(), exe_base, exe_name);\n                process.add_thread(event_context.thread_id);\n            },\n            DebugEvent::CreateThread { thread_id } => {\n                process.add_thread(thread_id);\n                println!(\"Thread created: {:x}\", thread_id);\n            },\n            DebugEvent::ExitThread { thread_id } => {\n                process.remove_thread(thread_id);\n                println!(\"Thread exited: {:x}\", thread_id);\n            },\n            DebugEvent::LoadModule { module_name, module_base } => {\n                load_module_at_address(&mut process, mem_source.as_ref(), module_base, module_name);\n            },\n            DebugEvent::OutputDebugString(debug_string) => println!(\"DebugOut: {}\", debug_string),\n            DebugEvent::Other(msg) => println!(\"{}\", msg),\n            DebugEvent::ExitProcess => {\n                is_exit = true;\n                println!(\"ExitProcess\");\n            },\n        }\n\n        let mut next_unassemble_address = ctx.context.Rip;\n        let mut continue_execution = false;\n\n        while !continue_execution {\n\n            if let Some(sym) = name_resolution::resolve_address_to_name(ctx.context.Rip, &mut process) {\n                println!(\"[{:X}] {}\", event_context.thread_id, sym);\n            } else {\n                println!(\"[{:X}] {:#018x}\", event_context.thread_id, ctx.context.Rip);\n            }\n\n            let cmd = command::read_command();\n\n\n            let mut eval_expr = |expr: Box<EvalExpr>| -> Option<u64> {\n                let mut eval_context = eval::EvalContext{ process: &mut process, register_context: &ctx.context };\n                let result = eval::evaluate_expression(*expr, &mut eval_context);\n                match result {\n                    Ok(val) => Some(val),\n                    Err(e) => {\n                        print!(\"Could not evaluate expression: {}\", e);\n                        None\n                    }\n                }\n            };\n\n            match cmd {\n                CommandExpr::StepInto(_) => {\n                    ctx.context.EFlags |= TRAP_FLAG;\n                    let ret = unsafe { SetThreadContext(thread.handle(), &ctx.context) };\n                    if ret == 0 {\n                        panic!(\"SetThreadContext failed\");\n                    }\n                    expect_step_exception = true;\n                    continue_execution = true;\n                }\n                CommandExpr::Go(_) => {\n                    continue_execution = true;\n                }\n                CommandExpr::DisplayRegisters(_) => {\n                    registers::display_all(&ctx.context);\n                }\n                CommandExpr::DisplaySpecificRegister(_, reg) => {\n                    registers::display_named(&ctx.context, &reg);\n                }\n                CommandExpr::DisplayBytes(_, expr) => {\n                    if let Some(address) = eval_expr(expr) {\n                        let bytes = mem_source.read_raw_memory(address, 16);\n                        for byte in bytes {\n                            print!(\"{:02X} \", byte);\n                        }\n                        println!();\n                    }\n                }\n                CommandExpr::Evaluate(_, expr) => {\n                    if let Some(val) = eval_expr(expr) {\n                        println!(\" = 0x{:X}\", val);\n                    }\n                }\n                CommandExpr::ListNearest(_, expr) => {\n                    if let Some(val) = eval_expr(expr) {\n                        if let Some(sym) = name_resolution::resolve_address_to_name(val, &mut process) {\n                            println!(\"{}\", sym);\n                        } else {\n                            println!(\"No symbol found\");\n                        }\n                    }\n                }\n                CommandExpr::Unassemble(_, expr) => {\n                    if let Some(addr) = eval_expr(expr) {\n                        next_unassemble_address = unassemble::unassemble(mem_source.as_ref(), addr, 16);\n                    }\n                }\n                CommandExpr::UnassembleContinue(_) => {\n                    next_unassemble_address = unassemble::unassemble(mem_source.as_ref(), next_unassemble_address, 16);\n                }\n                CommandExpr::ListSource(_, expr) => {\n                    if let Some(val) = eval_expr(expr) {\n                        match resolve_address_to_source_line(val, &mut process) {\n                            Ok((file_name, line_number)) => {\n                                println!(\"LSA: {}:{}\", file_name, line_number);\n                                if let Ok(file_name) = source::find_source_file_match(&file_name, &source_search_paths) {\n                                    if let Ok(file) = File::open(&file_name) {\n                                        println!(\"Found matching file: {}\", file_name.display());\n                                        let reader = io::BufReader::new(file);\n                                        let lines: Vec<_> = reader.lines().map(|l| l.unwrap_or(\"\".to_string())) .collect();\n                                        for print_line_num in (max(1, line_number - 2))..=(min(lines.len() as u32, line_number + 2)) {\n                                            if print_line_num == line_number {\n                                                println!(\">{:4}: {}\", print_line_num, lines[print_line_num as usize - 1]);\n                                            } else {\n                                                println!(\"{:5}: {}\", print_line_num, lines[print_line_num as usize - 1]);\n                                            }\n                                        }\n                                    } else {\n                                        println!(\"Couldn't open file: {}\", file_name.display());\n                                    }\n                                }\n                            },\n                            Err(e) => {\n                                println!(\"Couldn't look up source: {}\", e);\n                            }\n                        }                        \n                    }\n                }\n                CommandExpr::SrcPath(_, path) => {\n                    source_search_paths.clear();\n                    source_search_paths.extend(path.split(';').map(|s| s.to_string()));\n                }\n                CommandExpr::SetBreakpoint(_, expr) => {\n                    if let Some(addr) = eval_expr(expr) {\n                        breakpoints.add_breakpoint(addr);\n                    }\n                }\n                CommandExpr::ListBreakpoints(_) => {\n                    breakpoints.list_breakpoints(&mut process);\n                }\n                CommandExpr::ClearBreakpoint(_, expr) => {\n                    if let Some(id) = eval_expr(expr) {\n                        breakpoints.clear_breakpoint(id as u32);\n                    }\n                }\n                CommandExpr::StackWalk(_) => {\n                    let mut context = ctx.context.clone();\n                    println!(\" #   RSP              Call Site\");\n                    let mut frame_number = 0;\n                    loop {\n                        if let Some(sym) = name_resolution::resolve_address_to_name(context.Rip, &mut process) {\n                            println!(\"{:02X} 0x{:016X} {}\", frame_number, context.Rsp, sym);\n                        } else {\n                            println!(\"{:02X} 0x{:016X} 0x{:X}\", frame_number, context.Rsp, context.Rip);\n                        }\n                        match stack::unwind_context(&mut process, context, mem_source.as_ref()) {\n                            Ok(Some(unwound_context)) => context = unwound_context,\n                            _ => break\n                        }\n                        frame_number += 1;\n                    }\n                }\n                CommandExpr::Quit(_) => {\n                    // The process will be terminated since we didn't detach.\n                    return;\n                }\n            }\n        }\n\n        if is_exit {\n            break;\n        }\n\n        breakpoints.apply_breakpoints(&mut process, event_context.thread_id, mem_source.as_ref());\n\n        unsafe {\n            ContinueDebugEvent(\n                event_context.process_id,\n                event_context.thread_id,\n                continue_status,\n            );\n        }\n    }\n}\n\nfn main() {\n    let target_command_line_result = parse_command_line();\n\n    let mut command_line_buffer = match target_command_line_result {\n        Ok(i) => i,\n        Err(msg) => {\n            show_usage(msg);\n            return;\n        }\n    };\n\n    println!(\n        \"Command line was: '{str}'\",\n        str = String::from_utf16_lossy(&command_line_buffer)\n    );\n\n    let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };\n    si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;\n    let mut pi: MaybeUninit<PROCESS_INFORMATION> = MaybeUninit::uninit();\n    let ret = unsafe {\n        CreateProcessW(\n            null(),\n            command_line_buffer.as_mut_ptr(),\n            null(),\n            null(),\n            FALSE,\n            DEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE,\n            null(),\n            null(),\n            &mut si.StartupInfo,\n            pi.as_mut_ptr(),\n        )\n    };\n\n    if ret == 0 {\n        panic!(\"CreateProcessW failed\");\n    }\n\n    let pi = unsafe { pi.assume_init() };\n\n    unsafe { CloseHandle(pi.hThread) };\n\n    // Later, we'll need to pass in a process handle.\n    main_debugger_loop(pi.hProcess);\n\n    unsafe { CloseHandle(pi.hProcess) };\n}\n"
  },
  {
    "path": "src/memory.rs",
    "content": "use core::ffi::c_void;\nuse windows_sys::{Win32::Foundation, Win32::System::Diagnostics::Debug::*};\n\npub trait MemorySource {\n    // Read up to \"len\" bytes, and return Option<u8> to represent what bytes are available in the range\n    fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u8>>, &'static str>;\n    // Read up to \"len\" bytes, and stop at the first failure\n    fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8>;\n}\n\npub fn read_memory_array<T: Sized + Default>(\n    source: &dyn MemorySource,\n    address: u64,\n    max_count: usize,\n) -> Result<Vec<T>, &'static str> {\n    let element_size = ::core::mem::size_of::<T>();\n    let max_bytes = max_count * element_size;\n    let raw_bytes = source.read_raw_memory(address, max_bytes);\n    let mut data: Vec<T> = Vec::new();\n    let mut offset: usize = 0;\n    while offset + element_size <= raw_bytes.len() {\n        let mut item: T = T::default();\n        let dst: *mut u8 = unsafe { std::mem::transmute(&mut item) };\n        let src = &raw_bytes[offset] as *const u8;\n        unsafe { std::ptr::copy_nonoverlapping(src, dst, element_size) };\n        data.push(item);\n        offset += element_size;\n    }\n\n    Ok(data)\n}\n\npub fn read_memory_full_array<T: Sized + Default>(\n    source: &dyn MemorySource,\n    address: u64,\n    count: usize,\n) -> Result<Vec<T>, &'static str> {\n    let arr = read_memory_array(source, address, count)?;\n\n    if arr.len() != count {\n        Err(\"Could not read all items\")\n    } else {\n        Ok(arr)\n    }\n}\n\npub fn read_memory_data<T: Sized + Default + Copy>(\n    source: &dyn MemorySource,\n    address: u64,\n) -> Result<T, &'static str> {\n    let data = read_memory_array::<T>(source, address, 1)?;\n    Ok(data[0])\n}\n\npub fn read_memory_string(\n    source: &dyn MemorySource,\n    address: u64,\n    max_count: usize,\n    is_wide: bool,\n) -> Result<String, &'static str> {\n    let result: String = if is_wide {\n        let mut words = read_memory_array::<u16>(source, address, max_count)?;\n        let null_pos = words.iter().position(|&v| v == 0);\n        if let Some(null_pos) = null_pos {\n            words.truncate(null_pos);\n        }\n        String::from_utf16_lossy(&words)\n    } else {\n        let mut bytes = read_memory_array::<u8>(source, address, max_count)?;\n        let null_pos = bytes.iter().position(|&v| v == 0);\n        if let Some(null_pos) = null_pos {\n            bytes.truncate(null_pos);\n        }\n        // TODO: This is not quite right. Technically most strings read here are encoded as ASCII.\n        String::from_utf8(bytes).unwrap()\n    };\n    Ok(result)\n}\n\npub fn read_memory_string_indirect(\n    source: &dyn MemorySource,\n    address: u64,\n    max_count: usize,\n    is_wide: bool,\n) -> Result<String, &'static str> {\n    let string_address = read_memory_data::<u64>(source, address)?;\n    read_memory_string(source, string_address, max_count, is_wide)\n}\n\nstruct LiveMemorySource {\n    hprocess: Foundation::HANDLE,\n}\n\npub fn make_live_memory_source(hprocess: Foundation::HANDLE) -> Box<dyn MemorySource> {\n    Box::new(LiveMemorySource { hprocess })\n}\n\nimpl MemorySource for LiveMemorySource {\n    fn read_memory(&self, address: u64, len: usize) -> Result<Vec<Option<u8>>, &'static str> {\n        let mut buffer: Vec<u8> = vec![0; len];\n        let mut data: Vec<Option<u8>> = vec![None; len];\n        let mut offset: usize = 0;\n\n        while offset < len {\n            let mut bytes_read: usize = 0;\n            let len_left = len - offset;\n            let cur_address = address + (offset as u64);\n\n            let result = unsafe {\n                ReadProcessMemory(\n                    self.hprocess,\n                    cur_address as *const c_void,\n                    buffer.as_mut_ptr() as *mut c_void,\n                    len_left,\n                    &mut bytes_read as *mut usize,\n                )\n            };\n\n            if result == 0 {\n                return Err(\"ReadProcessMemory failed\");\n            };\n\n            for index in 0..bytes_read {\n                let data_index = offset + index;\n                data[data_index] = Some(buffer[index]);\n            }\n\n            if bytes_read > 0 {\n                offset += bytes_read;\n            } else {\n                offset += 1;\n            }\n        }\n\n        Ok(data)\n    }\n\n    fn read_raw_memory(&self, address: u64, len: usize) -> Vec<u8> {\n        let mut buffer: Vec<u8> = vec![0; len];\n        let mut bytes_read: usize = 0;\n\n        let result = unsafe {\n            ReadProcessMemory(\n                self.hprocess,\n                address as *const c_void,\n                buffer.as_mut_ptr() as *mut c_void,\n                len,\n                &mut bytes_read as *mut usize,\n            )\n        };\n\n        if result == 0 {\n            bytes_read = 0;\n        }\n\n        buffer.truncate(bytes_read);\n\n        buffer\n    }\n}\n"
  },
  {
    "path": "src/module.rs",
    "content": "use crate::memory::{*, self};\nuse windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE_AMD64;\nuse windows::Win32::System::SystemServices::*;\nuse windows::Win32::System::Diagnostics::Debug::{*, IMAGE_DATA_DIRECTORY};\nuse pdb::{PDB, AddressMap};\nuse std::fs::File;\n\npub struct Module {\n    pub name: String,\n    pub address: u64,\n    pub size: u64,\n    pub exports: Vec::<Export>,\n    pub pdb_name: Option<String>,\n    pub pdb_info: Option<PdbInfo>,\n    pub pdb: Option<PDB<'static, File>>,\n    pub address_map: Option<AddressMap<'static>>,\n    pe_header: IMAGE_NT_HEADERS64,\n}\n\npub struct Export {\n    pub name: Option<String>,\n    // This is the \"biased\" ordinal\n    pub ordinal: u32,\n    pub target: ExportTarget,\n}\n\nimpl ToString for Export {\n    fn to_string(&self) -> String {\n        if let Some(str) = &self.name {\n            str.to_string()\n        } else {\n            format!(\"Ordinal{}\", self.ordinal)\n        }\n    }\n}\n\npub enum ExportTarget {\n    RVA(u64),\n    Forwarder(String)\n}\n\n#[derive(Default)]\n#[repr(C)]\npub struct PdbInfo {\n    pub signature: u32,\n    pub guid: windows::core::GUID,\n    pub age: u32,\n    // Null terminated name goes after the end\n}\n\nimpl ::core::marker::Copy for PdbInfo {}\nimpl ::core::clone::Clone for PdbInfo {\n    fn clone(&self) -> Self {\n        *self\n    }\n}\n\nimpl Module {\n    pub fn from_memory_view(module_address: u64, module_name: Option<String>, memory_source: &dyn MemorySource) -> Result<Module, &'static str> {\n\n        let dos_header: IMAGE_DOS_HEADER = memory::read_memory_data(memory_source, module_address)?;\n\n        // NOTE: Do we trust that the headers are accurate, even if it means we could read outside the bounds of the\n        //       module? For this debugger, we'll trust the data, but a real debugger should do sanity checks and \n        //       report discrepancies to the user in some way.\n        let pe_header_addr = module_address + dos_header.e_lfanew as u64;\n\n        // NOTE: This should be IMAGE_NT_HEADERS32 for 32-bit modules, but the FileHeader lines up for both structures.\n        let pe_header: IMAGE_NT_HEADERS64 = memory::read_memory_data(memory_source, pe_header_addr)?;\n        let size = pe_header.OptionalHeader.SizeOfImage as u64;\n\n        if pe_header.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 {\n            return Err(\"Unsupported machine architecture for module\");\n        }\n        \n        let (pdb_info, pdb_name, mut pdb) = Module::read_debug_info(&pe_header, module_address, memory_source)?;\n        let (exports, export_table_module_name) = Module::read_exports(&pe_header, module_address, memory_source)?;\n\n        let module_name = module_name.or(export_table_module_name);\n         let module_name = match module_name {\n            Some(s) => s,\n            None => {\n                format!(\"module_{:X}\", module_address)\n            }\n        };\n        let address_map = pdb.as_mut().and_then(|pdb| pdb.address_map().ok());\n\n        Ok(Module{\n            name: module_name,\n            address: module_address,\n            size,\n            exports,\n            pdb_info,\n            pdb_name,\n            pdb,\n            address_map,\n            pe_header\n        })\n    }\n\n    pub fn contains_address(&self, address: u64) -> bool {\n        let end = self.address + self.size;\n        self.address <= address && address < end\n    }\n\n    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> {\n        let mut pdb_info: Option<PdbInfo> = None;\n        let mut pdb_name: Option<String> = None;\n        let mut pdb: Option<PDB<File>> = None;\n        \n\n        let debug_table_info = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG.0 as usize];\n        if debug_table_info.VirtualAddress != 0 {\n            let dir_size = std::mem::size_of::<IMAGE_DEBUG_DIRECTORY>() as u64;\n            // We'll arbitrarily limit to 20 entries to keep it sane.\n            let count: u64 = std::cmp::min(debug_table_info.Size as u64 / dir_size, 20);\n            for dir_index in 0..count {\n                let debug_directory_address = module_address + (debug_table_info.VirtualAddress as u64) + (dir_index * dir_size);\n                let debug_directory: IMAGE_DEBUG_DIRECTORY = memory::read_memory_data(memory_source, debug_directory_address)?;\n                if debug_directory.Type == IMAGE_DEBUG_TYPE_CODEVIEW {\n                    let pdb_info_address = debug_directory.AddressOfRawData as u64 + module_address;\n                    pdb_info = Some(memory::read_memory_data(memory_source, pdb_info_address)?);\n                    // We could check that pdb_info.signature is RSDS here.\n                    let pdb_name_address = pdb_info_address + std::mem::size_of::<PdbInfo>() as u64;\n                    let max_size = debug_directory.SizeOfData as usize - std::mem::size_of::<PdbInfo>();\n                    pdb_name = Some(memory::read_memory_string(memory_source, pdb_name_address, max_size, false)?);\n\n                    let pdb_file = File::open(pdb_name.as_ref().unwrap());\n                    if let Ok(pdb_file) = pdb_file {\n                        let pdb_data = PDB::open(pdb_file);\n                        if let Ok(pdb_data) = pdb_data {\n                            pdb = Some(pdb_data);\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok((pdb_info, pdb_name, pdb))\n    }\n\n    pub fn get_data_directory(&self, entry: IMAGE_DIRECTORY_ENTRY) -> IMAGE_DATA_DIRECTORY {\n        self.pe_header.OptionalHeader.DataDirectory[entry.0 as usize]\n    }\n\n    fn read_exports(pe_header: &IMAGE_NT_HEADERS64, module_address: u64, memory_source: &dyn MemorySource) -> Result<(Vec::<Export>, Option<String>), &'static str> {\n        let mut exports = Vec::<Export>::new();\n        let mut module_name: Option<String> = None;\n        let export_table_info = pe_header.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];\n        if export_table_info.VirtualAddress != 0 {\n            let export_table_addr = module_address + export_table_info.VirtualAddress as u64;\n            let export_table_end = export_table_addr + export_table_info.Size as u64;\n            let export_directory: IMAGE_EXPORT_DIRECTORY = memory::read_memory_data(memory_source, export_table_addr)?;\n\n            // This is a fallback that lets us find a name if none was available.\n            if export_directory.Name != 0 {\n                let name_addr = module_address + export_directory.Name as u64;\n                module_name = Some(memory::read_memory_string(memory_source, name_addr, 512, false)?);\n            }\n\n            // We'll read the name table first, which is essentially a list of (ordinal, name) pairs that give names \n            // to some or all of the exports. The table is stored as parallel arrays of orindals and name pointers\n            let ordinal_array_address = module_address + export_directory.AddressOfNameOrdinals as u64;\n            let ordinal_array = memory::read_memory_full_array::<u16>(memory_source, ordinal_array_address, export_directory.NumberOfNames as usize)?;\n            let name_array_address = module_address + export_directory.AddressOfNames as u64;\n            let name_array = memory::read_memory_full_array::<u32>(memory_source, name_array_address, export_directory.NumberOfNames as usize)?;\n\n            let address_table_address = module_address + export_directory.AddressOfFunctions as u64;\n            let address_table = memory::read_memory_full_array::<u32>(memory_source, address_table_address, export_directory.NumberOfFunctions as usize)?;\n\n            for (unbiased_ordinal, function_address) in address_table.iter().enumerate() {\n                let ordinal = export_directory.Base + unbiased_ordinal as u32;\n                let target_address = module_address + *function_address as u64;\n\n                let name_index = ordinal_array.iter().position(|&o| o == unbiased_ordinal as u16);\n                let export_name = match name_index {\n                    None => None,\n                    Some(idx) => {\n                        let name_address = module_address + name_array[idx] as u64;\n                        Some(memory::read_memory_string(memory_source, name_address, 4096, false)?)\n                    }\n                };\n\n                // An address that falls inside the export directory is actually a forwarder\n                if target_address >= export_table_addr && target_address < export_table_end {\n                    // I don't know that there actually is a max size for a forwader name, but 4K is probably reasonable.\n                    let forwarding_name = memory::read_memory_string(memory_source, target_address, 4096, false)?;\n                    exports.push(Export {name: export_name, ordinal, target: ExportTarget::Forwarder(forwarding_name)});                    \n                } else {\n                    exports.push(Export{name: export_name, ordinal, target: ExportTarget::RVA(target_address)});\n                }\n            }\n        };\n\n        Ok((exports, module_name))\n    }\n}"
  },
  {
    "path": "src/name_resolution.rs",
    "content": "use pdb::FallibleIterator;\nuse pdb::SymbolData;\n\nuse crate::{process::Process, module::{Export, ExportTarget, Module}};\nuse anyhow::anyhow;\n\nenum AddressMatch<'a> {\n    None,\n    Export(&'a Export),\n    Public(String)\n}\nimpl AddressMatch<'_> {\n    fn is_none(&self) -> bool {\n        match self {\n            AddressMatch::None => true,\n            _ => false\n        }\n    }\n}\n\npub fn resolve_name_to_address(sym: &str, process: &mut Process) -> Result<u64, anyhow::Error> {\n    match sym.chars().position(|c| c == '!') {\n        None => {\n            // Search all modules\n            Err(anyhow!(\"Not yet implemented\"))\n        },\n        Some(pos) => {\n            let module_name = &sym[..pos];\n            let func_name = &sym[pos + 1..];\n            if let Some(module) = process.get_module_by_name_mut(module_name) {\n                if let Some(addr) = resolve_function_in_module(module, func_name) {\n                    Ok(addr)\n                } else {\n                    Err(anyhow!(\"Could not find {} in module {}\", func_name, module_name))\n                }\n            } else {\n                Err(anyhow!(\"Could not find module {}\", module_name))\n            }\n        },\n    }\n}\n\npub fn resolve_function_in_module(module: &mut Module, func: &str) -> Option<u64> {\n    // We'll search exports first and private symbols next\n    let export_resolution = resolve_export_in_module(module, func);\n    if export_resolution.is_some() {\n        return export_resolution;\n    }\n\n    resolve_symbol_name_in_module(module, func).unwrap_or(None)\n}\n\nfn resolve_export_in_module(module: &mut Module, func: &str) -> Option<u64> {\n    // We'll search exports first and private symbols next\n    for export in module.exports.iter() {\n        if let Some(export_name) = &export.name {\n            if *export_name == *func {\n                // Just support direct exports for now, rather than forwarded functions.\n                if let ExportTarget::RVA(export_addr) = export.target {\n                    return Some(export_addr)\n                }\n            }\n        }\n    }\n    None\n}\n\nfn resolve_symbol_name_in_module(module: &mut Module, func: &str) -> Result<Option<u64>, anyhow::Error> {\n    let pdb = module.pdb.as_mut().ok_or(anyhow!(\"No PDB loaded\"))?;\n    let dbi = pdb.debug_information()?;\n    let mut modules = dbi.modules()?;\n    let address_map = module.address_map.as_mut().ok_or(anyhow!(\"No address map available\"))?;\n    while let Some(pdb_module) = modules.next()? {\n        let mi = pdb.module_info(&pdb_module)?.ok_or(anyhow!(\"Couldn't get module info\"))?;\n        let mut symbols = mi.symbols()?;\n        while let Some(sym) = symbols.next()? {\n            if let Ok(parsed) = sym.parse() {\n                if let SymbolData::Procedure(proc_data) = parsed {\n                    if proc_data.name.to_string() == func {\n                        let rva = proc_data.offset.to_rva(address_map).ok_or(anyhow!(\"Couldn't convert procedure offset to RVA\"))?;\n                        let address = module.address + rva.0 as u64;\n                        return Ok(Some(address));\n                    }\n                }\n            }\n        }\n    }\n    Ok(None)\n}\n\n\npub fn resolve_address_to_name(address: u64, process: &mut Process) -> Option<String> {\n    let module = match process.get_containing_module_mut(address) {\n        Some(module) => module,\n        None => return None\n    };\n\n    let mut closest: AddressMatch = AddressMatch::None;\n    let mut closest_addr: u64 = 0;\n    // This could be faster if we were always in sorted order\n    for export in module.exports.iter() {\n        if let ExportTarget::RVA(export_addr) = export.target {\n            if export_addr <= address {\n                if closest.is_none() || closest_addr < export_addr {\n                    closest = AddressMatch::Export(export);\n                    closest_addr = export_addr;\n                }\n            }\n        };\n    }\n\n    if let Some(pdb) = module.pdb.as_mut() {\n        if let Ok(symbol_table) = pdb.global_symbols() {\n            if let Ok(address_map) = pdb.address_map() {\n                let mut symbols = symbol_table.iter();\n                while let Ok(Some(symbol)) = symbols.next() {\n                    match symbol.parse() {\n                        Ok(pdb::SymbolData::Public(data)) if data.function => {\n                            let rva = data.offset.to_rva(&address_map).unwrap_or_default();\n                            let global_addr = module.address + rva.0 as u64;\n                            if global_addr <= address && (closest.is_none() || closest_addr <= global_addr) {\n                                // TODO: Take a reference to the data?\n                                closest = AddressMatch::Public(data.name.to_string().to_string());\n                                closest_addr = global_addr;\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            }\n        }\n    }\n\n    if let AddressMatch::Export(closest) = closest {\n        let offset = address - closest_addr;\n        let sym_with_offset = if offset == 0 {\n            format!(\"{}!{}\", &module.name, closest.to_string())\n        } else {\n            format!(\"{}!{}+0x{:X}\", &module.name, closest.to_string(), offset)\n        };\n        return Some(sym_with_offset)\n    }\n\n    if let AddressMatch::Public(closest) = closest {\n        let offset = address - closest_addr;\n        let sym_with_offset = if offset == 0 {\n            format!(\"{}!{}\", &module.name, closest)\n        } else {\n            format!(\"{}!{}+0x{:X}\", &module.name, closest, offset)\n        };\n        return Some(sym_with_offset)\n    }\n    \n    None\n}"
  },
  {
    "path": "src/process.rs",
    "content": "use windows_sys::Win32::Foundation;\n\nuse crate::{module::Module, memory::MemorySource};\n\npub struct Process {\n    module_list: std::vec::Vec<Module>,\n    thread_list: std::vec::Vec<u32>,\n}\n\nimpl Process {\n    pub fn new() -> Process {\n        Process { module_list: Vec::new(), thread_list: Vec::new() }\n    }\n\n    pub fn add_module(&mut self, address: u64, name: Option<String>, memory_source: &dyn MemorySource) -> Result<&Module, &'static str> {\n        let module = Module::from_memory_view(address, name, memory_source)?;\n        self.module_list.push(module);\n        Ok(self.module_list.last().unwrap())\n    }\n\n    pub fn add_thread(&mut self, thread_id: u32) {\n        self.thread_list.push(thread_id);\n    }\n\n    pub fn remove_thread(&mut self, thread_id: u32) {\n        self.thread_list.retain(|x| *x != thread_id);\n    }\n\n    pub fn iterate_threads(&self) -> core::slice::Iter<'_, u32> {\n        self.thread_list.iter()\n    }\n\n    pub fn _get_containing_module(&self, address: u64) -> Option<&Module> {\n        for module in self.module_list.iter() {\n            if module.contains_address(address) {\n                return Some(&module);\n            }\n        };\n\n        None\n    }\n\n    pub fn get_containing_module_mut(&mut self, address: u64) -> Option<&mut Module> {\n        for module in self.module_list.iter_mut() {\n            if module.contains_address(address) {\n                return Some(module);\n            }\n        };\n\n        None\n    }\n\n    pub fn get_module_by_name_mut(&mut self, module_name: &str) -> Option<&mut Module> {\n        let mut potential_trimmed_match = None;\n        let mut potential_trimmed_noext_match = None;\n    \n        for module in self.module_list.iter_mut() {\n            if module.name == module_name {\n                return Some(module);\n            }\n    \n            let trimmed = module.name.rsplitn(2, '\\\\').next().unwrap_or(&module.name);\n            if potential_trimmed_match.is_none() && trimmed.to_lowercase() == module_name.to_lowercase() {\n                potential_trimmed_match = Some(module);\n            } else if potential_trimmed_noext_match.is_none() {\n                let parts: Vec<&str> = trimmed.rsplitn(2, '.').collect();\n                let trimmed_noext = if parts.len() == 2 {\n                    parts[1]\n                } else {\n                    parts[0]\n                };\n                if trimmed_noext.to_lowercase() == module_name.to_lowercase() {\n                    potential_trimmed_noext_match = Some(module);\n                }\n            }\n        };\n    \n        potential_trimmed_match.or(potential_trimmed_noext_match)\n    }\n}\n"
  },
  {
    "path": "src/registers.rs",
    "content": "use windows_sys::{\n    Win32::System::{Diagnostics::Debug::*},\n};\n\npub fn display_all(context: &CONTEXT) {\n    println!(\"rax={:#018x} rbx={:#018x} rcx={:#018x}\", context.Rax, context.Rbx, context.Rcx);\n    println!(\"rdx={:#018x} rsi={:#018x} rdi={:#018x}\", context.Rdx, context.Rsi, context.Rdi);\n    println!(\"rip={:#018x} rsp={:#018x} rbp={:#018x}\", context.Rip, context.Rsp, context.Rbp);\n    println!(\" r8={:#018x}  r9={:#018x} r10={:#018x}\", context.R8, context.R9, context.R10);\n    println!(\"r11={:#018x} r12={:#018x} r13={:#018x}\", context.R11, context.R12, context.R13);\n    println!(\"r14={:#018x} r15={:#018x} eflags={:#010x}\", context.R14, context.R15, context.EFlags);\n}\n\npub fn display_named(context: &CONTEXT, reg_name: &str) {\n    if let Ok(val) = get_register(context, reg_name) {\n        println!(\"{}={:#018x}\", reg_name.to_lowercase(), val);\n    } else {\n        println!(\"Unrecognized register name: {}\", reg_name);\n    }\n}\n\npub fn get_register(context: &CONTEXT, reg_name: &str) -> Result<u64, String> {\n    let val = match reg_name.to_lowercase().as_str() {\n        \"rax\" => context.Rax,\n        \"rbx\" => context.Rbx,\n        \"rcx\" => context.Rcx,\n        \"rdx\" => context.Rdx,\n        \"rsi\" => context.Rsi,\n        \"rdi\" => context.Rdi,\n        \"rip\" => context.Rip,\n        \"rsp\" => context.Rsp,\n        \"rbp\" => context.Rbp,\n        \"r8\" => context.R8,\n        \"r9\" => context.R9,\n        \"r10\" => context.R10,\n        \"r11\" => context.R11,\n        \"r12\" => context.R12,\n        \"r13\" => context.R13,\n        \"r14\" => context.R14,\n        \"r15\" => context.R15,\n        \"eflags\" => context.EFlags as u64,\n        _ => return Err(\"Unrecognized register\".to_string())\n    };\n    Ok(val)\n}\n"
  },
  {
    "path": "src/source.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse pdb::{FallibleIterator, LineProgram, Rva, StringTable};\nuse anyhow::{Result, anyhow};\n\nuse crate::process::Process;\n\nfn line_program_references_file(line_program: &LineProgram, src_file: &str, string_table: &StringTable) -> Result<bool> {\n    let mut files = line_program.files();\n    while let Some(file) = files.next()? {\n        let cur_file_name = string_table.get(file.name)?.to_string();\n        if cur_file_name == src_file {\n            return Ok(true);\n        }\n    }\n    Ok(false)\n}\n\npub fn resolve_source_line_to_address(module_name: &str, src_file: &str, src_line: u32, process: &mut Process) -> Result<u64> {\n    let process_module = process.get_module_by_name_mut(module_name).ok_or(anyhow!(\"Module not found\"))?;\n    let pdb = process_module.pdb.as_mut().ok_or(anyhow!(\"Symbols not available\"))?;\n    let address_map = process_module.address_map.as_mut().ok_or(anyhow!(\"Address map not found for module\"))?;\n    let string_table = pdb.string_table()?;\n    let dbi = pdb.debug_information()?;\n    let mut modules = dbi.modules()?;\n    while let Some(module) = modules.next()? {\n        if let Ok(Some(mi)) = pdb.module_info(&module) {\n            if let Ok(line_program) = mi.line_program() {\n                if line_program_references_file(&line_program, src_file, &string_table)? {\n                    let mut lines = line_program.lines();\n                    while let Some(line) = lines.next()? {\n                        let cur_file_name = string_table.get(line_program.get_file_info(line.file_index)?.name)?.to_string();\n                        if cur_file_name == src_file && line.line_start <= src_line && src_line <= line.line_end {\n                            let rva = line.offset.to_rva(&address_map).ok_or(anyhow!(\"Could not map source entry to RVA\"))?;\n                            let address = process_module.address + rva.0 as u64;\n                            return Ok(address);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Err(anyhow!(\"Source line not found\"))\n}\n\npub fn resolve_address_to_source_line(address: u64, process: &mut Process) -> Result<(String, u32)> {\n    let module = process.get_containing_module_mut(address).ok_or(anyhow!(\"Module not found\"))?;\n    let pdb = module.pdb.as_mut().ok_or(anyhow!(\"Symbols not available\"))?;\n\n    let address_map = module.address_map.as_mut().ok_or(anyhow!(\"Address map not found for module\"))?;\n    let rva: u32 = (address - module.address).try_into()?;\n    let rva = Rva(rva);\n    let offset = rva.to_internal_offset(address_map).ok_or(anyhow!(\"Couldn't map address\"))?;\n\n    let dbi = pdb.debug_information()?;\n    let mut modules = dbi.modules()?;\n    while let Some(module) = modules.next()? {\n        if let Ok(Some(mi)) = pdb.module_info(&module) {\n            if let Ok(lp) = mi.line_program() {\n                let mut lines = lp.lines_for_symbol(offset);\n                while let Some(line) = lines.next()? {\n                    if line.offset.offset <= offset.offset {\n                        let diff = offset.offset - line.offset.offset;\n                        if diff < line.length.unwrap_or(0) {\n                            let file_info = lp.get_file_info(line.file_index)?;\n                            let string_table = pdb.string_table()?;\n                            let file_name = string_table.get(file_info.name)?;\n                            return Ok((file_name.to_string().into(), line.line_start))\n                        }\n                        \n                    }\n                }\n            }\n        }\n    }\n    Err(anyhow!(\"Address not found\"))\n}\n\npub fn find_source_file_match(file: &str, search_paths: &Vec<String>) -> Result<PathBuf> {\n    let file_path = Path::new(file);\n\n    // If the file path is absolute and exists, return it immediately.\n    if file_path.is_absolute() && file_path.exists() {\n        return Ok(file_path.to_path_buf());\n    }\n\n    // Get all subsets of the input path.\n    let components: Vec<&str> = file_path.components().map(|c| c.as_os_str().to_str().unwrap()).collect();\n\n    for search_path in search_paths {\n        let search_path = Path::new(search_path);\n\n        for i in 0..components.len() {\n            // Join the search path with the subset of the input path.\n            let test_path: PathBuf = search_path.join(components[i..].iter().collect::<PathBuf>());\n            if test_path.exists() {\n                return Ok(test_path.to_path_buf());\n            }\n        }\n    }\n\n    Err(anyhow!(\"File not found\"))\n}"
  },
  {
    "path": "src/stack.rs",
    "content": "use windows::Win32::System::Diagnostics::Debug::IMAGE_DIRECTORY_ENTRY_EXCEPTION;\nuse windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;\nuse crate::{process::Process, memory::{MemorySource, read_memory_full_array, read_memory_data}};\n\n#[repr(C)]\n#[derive(Default, Clone)]\n#[allow(non_snake_case, non_camel_case_types)]\npub struct RUNTIME_FUNCTION {\n    pub BeginAddress: u32,\n    pub EndAddress: u32,\n    pub UnwindInfo: u32,\n}\n\n#[repr(C)]\n#[derive(Default, Clone, Copy)]\n#[allow(non_snake_case, non_camel_case_types)]\npub struct UNWIND_INFO {\n    pub version_flags: u8,\n    pub size_of_prolog: u8,\n    pub count_of_codes: u8,\n    pub frame_register_offset: u8,\n}\n\n#[repr(C)]\n#[derive(Default, Clone, Copy)]\n#[allow(non_snake_case, non_camel_case_types)]\npub struct UNWIND_CODE {\n    pub code_offset: u8,\n    pub unwind_op_info: u8,\n}\n\nconst UWOP_PUSH_NONVOL: u8 = 0;     /* info == register number */\nconst UWOP_ALLOC_LARGE: u8 = 1;     /* no info, alloc size in next 2 slots */\nconst UWOP_ALLOC_SMALL: u8 = 2;     /* info == size of allocation / 8 - 1 */\nconst UWOP_SET_FPREG: u8 = 3;       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */\nconst UWOP_SAVE_NONVOL: u8 = 4;     /* info == register number, offset in next slot */\nconst UWOP_SAVE_NONVOL_FAR: u8 = 5; /* info == register number, offset in next 2 slots */\nconst UWOP_SAVE_XMM128: u8 = 8;     /* info == XMM reg number, offset in next slot */\nconst UWOP_SAVE_XMM128_FAR: u8 = 9; /* info == XMM reg number, offset in next 2 slots */\nconst UWOP_PUSH_MACHFRAME: u8 = 10; /* info == 0: no error-code, 1: error-code */\n\nconst UNW_FLAG_NHANDLER: u8 = 0x0;\nconst UNW_FLAG_EHANDLER: u8 = 0x1;\nconst UNW_FLAG_UHANDLER: u8 = 0x2;\nconst UNW_FLAG_CHAININFO: u8 = 0x4;\n\n// These represent the logical operations, so large/small and far/near are merged\n#[derive(Debug)]\nenum UnwindOp {\n    PushNonVolatile { reg: u8 },\n    Alloc { size: u32 },\n    SetFpreg { frame_register: u8, frame_offset: u16 },\n    SaveNonVolatile { reg: u8, offset: u32 },\n    SaveXmm128 { reg: u8, offset: u32 },\n    PushMachFrame { error_code: bool }\n}\n\n// Does not directly correspond to UNWIND_CODE\n#[derive(Debug)]\nstruct UnwindCode {\n    code_offset: u8,\n    op: UnwindOp,\n}\n\nfn find_runtime_function(addr: u32, function_list: &[RUNTIME_FUNCTION]) -> Option<&RUNTIME_FUNCTION> {\n    let index = function_list.binary_search_by(|func| func.BeginAddress.cmp(&addr));\n\n    match index {\n        // Exact match\n        Ok(pos) => function_list.get(pos),\n        // Inexact match\n        Err(pos) => {\n            if pos > 0 && function_list.get(pos - 1).map_or(false, |func| func.BeginAddress <= addr && addr < func.EndAddress) {\n                function_list.get(pos - 1)\n            } else if pos < function_list.len() && function_list.get(pos).map_or(false, |func| func.BeginAddress <= addr && addr < func.EndAddress) {\n                function_list.get(pos)\n            } else {\n                None\n            }\n        }\n    }\n}\n\n// Splits an integer up that represents bitfields so that each field can be stored in a tuple. Specify the\n// 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\nmacro_rules! split_up {\n    ($value:expr => $($len:expr),+) => {\n        {\n            let mut value = $value;\n            // Use a tuple to collect the fields\n            ( $(\n                {\n                    let field = value & ((1 << $len) - 1); // Mask the value to get the field\n                    value >>= $len; // Shift the value for the next field\n                    field\n                }\n            ),+ ) // The '+' sign indicates one or more repetitions\n        }\n    };\n}\n\nfn get_unwind_ops(code_slots: &[u16], frame_register: u8, frame_offset: u16) -> Result<Vec<UnwindCode>, &'static str> {\n    let mut ops = Vec::<UnwindCode>::new();\n\n    let mut i = 0;\n    while i < code_slots.len() {\n        let (code_offset, unwind_op, op_info) = split_up!(code_slots[i] => 8, 4, 4);\n        let code_offset = code_offset as u8;\n        let unwind_op = unwind_op as u8;\n        let op_info = op_info as u8;\n        match unwind_op {\n            UWOP_PUSH_NONVOL => {\n                ops.push(UnwindCode { code_offset, op: UnwindOp::PushNonVolatile { reg: op_info } });\n            }\n            UWOP_ALLOC_LARGE if op_info == 0 => {\n                if i + 1 >= code_slots.len() {\n                    return Err(\"UWOP_ALLOC_LARGE was incomplete\");\n                }\n                let size = (code_slots[i + 1] as u32) * 8;\n                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });\n                i += 1;\n            }\n            UWOP_ALLOC_LARGE if op_info == 1 => {\n                if i + 2 >= code_slots.len() {\n                    return Err(\"UWOP_ALLOC_LARGE was incomplete\");\n                }\n                let size = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);\n                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });\n                i += 2;\n            }\n            UWOP_ALLOC_SMALL => {\n                let size = (op_info as u32) * 8 + 8;\n                ops.push(UnwindCode { code_offset, op: UnwindOp::Alloc { size } });\n            }\n            UWOP_SET_FPREG => {\n                ops.push(UnwindCode { code_offset, op: UnwindOp::SetFpreg { frame_register, frame_offset } });\n            }\n            UWOP_SAVE_NONVOL => {\n                if i + 1 >= code_slots.len() {\n                    return Err(\"UWOP_SAVE_NONVOL was incomplete\");\n                }\n                let offset = code_slots[i + 1] as u32;\n                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveNonVolatile { reg: op_info, offset } });\n                i += 1;\n            }\n            UWOP_SAVE_NONVOL_FAR => {\n                if i + 2 >= code_slots.len() {\n                    return Err(\"UWOP_SAVE_NONVOL_FAR was incomplete\");\n                }\n                let offset = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);\n                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveNonVolatile { reg: op_info, offset } });\n                i += 2;\n            }\n            UWOP_SAVE_XMM128 => {\n                if i + 1 >= code_slots.len() {\n                    return Err(\"UWOP_SAVE_XMM128 was incomplete\");\n                }\n                let offset = code_slots[i + 1] as u32;\n                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveXmm128 { reg: op_info, offset: offset } });\n                i += 1;\n            }\n            UWOP_SAVE_XMM128_FAR => {\n                if i + 2 >= code_slots.len() {\n                    return Err(\"UWOP_SAVE_XMM128_FAR was incomplete\");\n                }\n                let offset = code_slots[i + 1] as u32 + ((code_slots[i + 2] as u32) << 16);\n                ops.push(UnwindCode { code_offset, op: UnwindOp::SaveXmm128 { reg: op_info, offset: offset } });\n                i += 2;\n            }\n            _ => return Err(\"Unrecognized unwind op\")\n        }\n        i += 1;\n    }\n\n    Ok(ops)\n}\n\nfn get_op_register<'a>(context: &'a mut CONTEXT, reg: u8) -> &'a mut u64 {\n    match reg {\n        0 => &mut context.Rax,\n        1 => &mut context.Rcx,\n        2 => &mut context.Rdx,\n        3 => &mut context.Rbx,\n        4 => &mut context.Rsp,\n        5 => &mut context.Rbp,\n        6 => &mut context.Rsi,\n        7 => &mut context.Rdi,\n        8 => &mut context.R8,\n        9 => &mut context.R9,\n        10 => &mut context.R10,\n        11 => &mut context.R11,\n        12 => &mut context.R12,\n        13 => &mut context.R13,\n        14 => &mut context.R14,\n        15 => &mut context.R15,\n        _ => panic!(\"Bad register given to get_op_register()\")\n    }\n}\n\nfn apply_unwind_ops(context: &CONTEXT, unwind_ops: &[UnwindCode], func_address: u64, memory_source: &dyn MemorySource) -> Result<Option<CONTEXT>, &'static str> {\n    let mut unwound_context = context.clone();\n    for unwind in unwind_ops.iter() {\n        let func_offset = unwound_context.Rip - func_address;\n        if unwind.code_offset as u64 <= func_offset {\n            match unwind.op {\n                UnwindOp::Alloc { size } => {\n                    unwound_context.Rsp += size as u64;\n                }\n                UnwindOp::PushNonVolatile { reg } => {\n                    let addr = unwound_context.Rsp;\n                    let val = read_memory_data::<u64>(memory_source, addr)?;\n                    *get_op_register(&mut unwound_context, reg) = val;\n                    unwound_context.Rsp += 8;\n                }\n                UnwindOp::SaveNonVolatile { reg, offset } => {\n                    let addr = unwound_context.Rsp + offset as u64;\n                    let val = read_memory_data::<u64>(memory_source, addr)?;\n                    *get_op_register(&mut unwound_context, reg) = val;\n                }\n                UnwindOp::SetFpreg { frame_register, frame_offset } => {\n                    unwound_context.Rsp = *get_op_register(&mut unwound_context, frame_register) - (frame_offset as u64);\n                }\n                _ => panic!(\"NYI unwind op\")\n            }\n        }\n    }\n    Ok(Some(unwound_context))\n}\n\npub fn unwind_context(process: &mut Process, context: CONTEXT, memory_source: &dyn MemorySource) -> Result<Option<CONTEXT>, &'static str> {\n    let module = process.get_containing_module_mut(context.Rip);\n    if let Some(module) = module {\n        let data_directory = module.get_data_directory(IMAGE_DIRECTORY_ENTRY_EXCEPTION);\n        if data_directory.VirtualAddress != 0 && data_directory.Size != 0 {\n            let count = data_directory.Size as usize / std::mem::size_of::<RUNTIME_FUNCTION>();\n            let table_address = module.address + data_directory.VirtualAddress as u64;\n\n            // Note: In a real debugger you might want to cache these.\n            let functions: Vec<RUNTIME_FUNCTION> = read_memory_full_array(memory_source, table_address, count)?;\n\n            let rva = context.Rip - module.address;\n            let func = find_runtime_function(rva as u32, &functions);\n\n            if let Some(func) = func {\n                // We have unwind data!\n                let info_addr = module.address + func.UnwindInfo as u64;\n                let info = read_memory_data::<UNWIND_INFO>(memory_source, info_addr)?;\n                let (_version, flags) = split_up!(info.version_flags => 3, 5);\n                if flags & UNW_FLAG_CHAININFO == UNW_FLAG_CHAININFO {\n                    return Err(\"NYI: Chained info\");\n                }\n\n                let (frame_register, frame_offset) = split_up!(info.frame_register_offset => 4, 4);\n                let frame_offset = (frame_offset as u16) * 16;\n                // 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\n                // read them as u16 and then parse out the fields as needed.\n                let codes = read_memory_full_array::<u16>(memory_source, info_addr + 4, info.count_of_codes as usize)?;\n                let unwind_ops = get_unwind_ops(&codes, frame_register, frame_offset)?;\n                match apply_unwind_ops(&context, &unwind_ops, module.address + func.BeginAddress as u64, memory_source)? {\n                    Some(ctx) => {\n                        let mut ctx = ctx;\n                        ctx.Rip = read_memory_data::<u64>(memory_source, ctx.Rsp)?;\n                        ctx.Rsp += 8;\n\n                        // TODO: There are other conditions that should be checked\n                        if ctx.Rip == 0 {\n                            return Ok(None);\n                        }\n                        return Ok(Some(ctx))\n                    },\n                    _ => return Ok(None)\n                }\n                \n            } else {\n                // Leaf function: the return address is simply at [RSP]\n                let mut ctx = context;\n                ctx.Rip = read_memory_data::<u64>(memory_source, ctx.Rsp)?;\n                ctx.Rsp += 8;\n                return Ok(Some(ctx));\n            }\n        }\n    }\n    \n    Ok(None)\n}"
  },
  {
    "path": "src/unassemble.rs",
    "content": "use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, MasmFormatter};\n\nuse crate::memory::MemorySource;\n\npub fn unassemble(memory_source: &dyn MemorySource, va: u64, lines: usize) -> u64 {\n\n    // We'll never need more than lines * 15\n    let bytes = memory_source.read_raw_memory(va, lines * 15);\n    if bytes.len() == 0 {\n        println!(\"Failed to read memory at {:X}\", va);\n    }\n\n    let code_bitness = 64;\n    let hexbytes_column_byte_length = 10;\n    let mut decoder = Decoder::with_ip(\n        code_bitness,\n        bytes.as_slice(),\n        va,\n        DecoderOptions::NONE,\n    );\n\n    // Formatters: Masm*, Nasm*, Gas* (AT&T) and Intel* (XED).\n    // For fastest code, see `SpecializedFormatter` which is ~3.3x faster. Use it if formatting\n    // speed is more important than being able to re-assemble formatted instructions.\n    let mut formatter = MasmFormatter::new();\n\n    // Change some options, there are many more\n    //formatter.options_mut().set_digit_separator(\"`\");\n    formatter.options_mut().set_first_operand_char_index(10);\n\n    // String implements FormatterOutput\n    let mut output = String::new();\n\n    // Initialize this outside the loop because decode_out() writes to every field\n    let mut instruction = Instruction::default();\n\n    // The decoder also implements Iterator/IntoIterator so you could use a for loop:\n    //      for instruction in &mut decoder { /* ... */ }\n    // or collect():\n    //      let instructions: Vec<_> = decoder.into_iter().collect();\n    // but can_decode()/decode_out() is a little faster:\n    let mut instruction_count = 0;\n    let mut last_rip = 0;\n    while decoder.can_decode() && instruction_count < lines {\n        // There's also a decode() method that returns an instruction but that also\n        // means it copies an instruction (40 bytes):\n        //     instruction = decoder.decode();\n        decoder.decode_out(&mut instruction);\n\n        // Format the instruction (\"disassemble\" it)\n        output.clear();\n        formatter.format(&instruction, &mut output);\n\n        // Eg. \"00007FFAC46ACDB2 488DAC2400FFFFFF     lea       rbp,[rsp-100h]\"\n        print!(\"{:016X} \", instruction.ip());\n        let start_index = (instruction.ip() - va) as usize;\n        let instr_bytes = &bytes[start_index..start_index + instruction.len()];\n        for b in instr_bytes.iter() {\n            print!(\"{:02X}\", b);\n        }\n        if instr_bytes.len() < hexbytes_column_byte_length {\n            for _ in 0..hexbytes_column_byte_length - instr_bytes.len() {\n                print!(\"  \");\n            }\n        }\n        println!(\" {}\", output);\n        instruction_count += 1;\n        last_rip = instruction.ip() + instr_bytes.len() as u64;\n    }\n    last_rip\n}\n"
  },
  {
    "path": "src/util.rs",
    "content": "use windows_sys::Win32::System::Diagnostics::Debug::CONTEXT;\nuse windows_sys::Win32::Foundation::*;\n\n// Not sure why these are missing from windows_sys, but the definitions are in winnt.h\npub const CONTEXT_AMD64: u32 = 0x00100000;\npub const CONTEXT_CONTROL: u32 = CONTEXT_AMD64 | 0x00000001;\npub const CONTEXT_INTEGER: u32 = CONTEXT_AMD64 | 0x00000002;\npub const CONTEXT_SEGMENTS: u32 = CONTEXT_AMD64 | 0x00000004;\npub const CONTEXT_FLOATING_POINT: u32 = CONTEXT_AMD64 | 0x00000008;\npub const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_AMD64 | 0x00000010;\n#[allow(dead_code)]\npub const CONTEXT_FULL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT;\npub const CONTEXT_ALL: u32 = CONTEXT_CONTROL\n        | CONTEXT_INTEGER\n        | CONTEXT_SEGMENTS\n        | CONTEXT_FLOATING_POINT\n        | CONTEXT_DEBUG_REGISTERS;\n\n#[repr(align(16))]\npub struct AlignedContext {\n    pub context: CONTEXT,\n}\n        \n\npub struct AutoClosedHandle(pub HANDLE);\n\nimpl Drop for AutoClosedHandle {\n    fn drop(&mut self) {\n        unsafe {\n            CloseHandle(self.0);\n        }\n    }\n}\n\nimpl AutoClosedHandle {\n    pub fn handle(&self) -> HANDLE {\n        self.0\n    }\n}"
  }
]