Fix existing issues and implement dump analysis class#4
Fix existing issues and implement dump analysis class#4ShallowFeather merged 7 commits intoShallowFeather:mainfrom
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR adds dump analysis capabilities through a new Debugger class, fixes a memory leak by converting raw pointers to std::shared_ptr for object management, and introduces a temporary hooking utility. However, several critical bugs were introduced during the refactoring.
- Implemented
Debugger.cppandDebugger.hfor enhanced dump file parsing using Windows Debug Engine API - Migrated
objectListfrom raw pointers tostd::shared_ptrto prevent memory leaks - Added
HookManagerclass for temporary code execution monitoring - Replaced
std::mapwithstd::unordered_mapfor better performance
Reviewed Changes
Copilot reviewed 11 out of 14 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| KDemu/Debugger.h | Defines new Debugger_t class interface for dump analysis with module enumeration and symbol resolution |
| KDemu/Debugger.cpp | Implements debugger functionality with several critical bugs in error handling, resource cleanup, and cache logic |
| KDemu/LoadPE.hpp | Adds Debugger integration, converts objectList to shared_ptr, changes maps to unordered_map |
| KDemu/LoadPE.cpp | Implements new MapAllDriversFromKdmp using Debugger, updates LoadDmp to return bool, updates object creation to use shared_ptr |
| KDemu/unicorn.cpp | Refactors match function to use memcmp (with incorrect argument order) |
| KDemu/KDemu.cpp | Updates object hook registration to use shared_ptr, adds error handling for LoadDmp |
| KDemu/Emulate.hpp | Adds HookManager class for temporary hooks |
| KDemu/Emulate.cpp | Replaces printf-style formatting implementations with incomplete hook-based logging, breaking functionality |
| KDemu/KDemu.vcxproj | Adds Debugger source files to project |
| KDemu/KDemu.vcxproj.filters | Adds Debugger files to filters |
| .gitignore | Adds Release build directories to ignore list |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| auto returnAddress = emu->qword(emu->rsp()); | ||
| g_TmpHooks.add_temporary_hook(uc, | ||
| [](uc_engine* uc, uint64_t addr, uint32_t size, const std::vector<uint64_t>& savedArgs) { | ||
| auto emu = Emu(uc); | ||
|
|
||
| if (j < format.size() && format[j] == L'0' && j + 1 < format.size() && iswdigit(format[j + 1])) { | ||
| j++; | ||
| while (j < format.size() && iswdigit(format[j])) { | ||
| zero_pad = zero_pad * 10 + (format[j] - L'0'); | ||
| j++; | ||
| } | ||
| } | ||
| auto buffer_ptr = savedArgs.at(0); | ||
|
|
||
| if (j + 1 < format.size() && format[j] == L'h' && format[j + 1] == L'u') { | ||
| if (arg_idx < guest_args.size()) { | ||
| uint16_t val = static_cast<uint16_t>(guest_args[arg_idx++]); | ||
| ws << std::setfill(L'0') << std::setw(zero_pad) << val; | ||
| } | ||
| i = j + 1; | ||
| } | ||
| else if (format[j] == L'u' && arg_idx < guest_args.size()) { | ||
| uint32_t val = static_cast<uint32_t>(guest_args[arg_idx++]); | ||
| ws << std::setfill(L'0') << std::setw(zero_pad) << val; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'X' && arg_idx < guest_args.size()) { | ||
| uint32_t val = static_cast<uint32_t>(guest_args[arg_idx++]); | ||
| ws << std::uppercase << std::hex << std::setfill(L'0') << std::setw(zero_pad) << val << std::dec; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'S' && arg_idx < guest_args.size()) { | ||
| char tmp[512] = {}; | ||
| if (emu->try_read(guest_args[arg_idx++], tmp, sizeof(tmp))) { | ||
| std::string ansi_tmp(tmp); | ||
| std::wstring wide_tmp; | ||
| ANSIToUnicode(ansi_tmp, wide_tmp); | ||
| ws << wide_tmp.c_str(); | ||
| } | ||
| i = j; | ||
| } | ||
| else if (format[j] == L's' && arg_idx < guest_args.size()) { | ||
| wchar_t tmp[512] = {}; | ||
| if (emu->try_read(guest_args[arg_idx++], tmp, sizeof(tmp))) { | ||
| ws << tmp; | ||
| std::wstring result; | ||
| for (uint64_t addr = buffer_ptr;; addr += sizeof(wchar_t)) { | ||
| wchar_t ch = 0; | ||
| if (!emu->try_read(addr, &ch, sizeof(ch))) { | ||
| break; | ||
| } | ||
| i = j; | ||
| } | ||
| else if ((format[j] == L'd' || format[j] == L'i') && arg_idx < guest_args.size()) { | ||
| int64_t val = static_cast<int64_t>(guest_args[arg_idx++]); | ||
| ws << std::setfill(L'0') << std::setw(zero_pad) << val; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'x' && arg_idx < guest_args.size()) { | ||
| int64_t val = static_cast<int64_t>(guest_args[arg_idx++]); | ||
| ws << std::hex << std::setfill(L'0') << std::setw(zero_pad) << val << std::dec; | ||
| i = j; | ||
| } | ||
| else { | ||
| ws << format[i]; | ||
| result.push_back(ch); | ||
| if (ch == L'\0') break; | ||
| } | ||
| } | ||
| else { | ||
| ws << format[i]; | ||
| } | ||
| } | ||
|
|
||
| std::wstring result = ws.str(); | ||
| size_t required = result.size() + 1; | ||
| if (required > sizeInWords) { | ||
| wchar_t zero = L'\0'; | ||
| emu->write(buffer_ptr, &zero, sizeof(wchar_t)); | ||
| emu->rax(static_cast<uint64_t>(-1)); | ||
| RetHook(uc); | ||
| restore_threads(); | ||
| return; | ||
| } | ||
|
|
||
| Logger::Log(true, ConsoleColor::RED, L"%ls\n", result.c_str()); | ||
| emu->write(buffer_ptr, result.c_str(), required * sizeof(wchar_t)); | ||
| emu->rax(static_cast<uint64_t>(result.size())); | ||
| RetHook(uc); | ||
| restore_threads(); | ||
| Logger::Log(true, ConsoleColor::RED, L"%ls\n", result.c_str()); | ||
| }, returnAddress, returnAddress + 1, | ||
| { buffer_ptr, sizeInWords, format_ptr, va_args_ptr }); |
There was a problem hiding this comment.
The _vswprintf_s function implementation is incomplete. The entire printf-style formatting logic has been removed and replaced with a hook that only logs the result buffer after the function returns. However, the actual formatting and writing to the buffer is not performed, which means the emulated code won't work correctly. The function needs to either:
- Keep the original formatting implementation, or
- Allow the actual implementation to run and just log the result
Currently, neither the return value nor the formatted output is set properly.
| auto returnAddress = emu->qword(emu->rsp()); | ||
| g_TmpHooks.add_temporary_hook(uc, | ||
| [](uc_engine* uc, uint64_t addr, uint32_t size, const std::vector<uint64_t>& savedArgs) { | ||
| auto emu = Emu(uc); | ||
| auto buffer_ptr = savedArgs.at(0); | ||
|
|
||
| std::wstring result; | ||
| for (uint64_t addr = buffer_ptr;; addr += sizeof(wchar_t)) { | ||
| wchar_t ch = 0; | ||
| if (!emu->try_read(addr, &ch, sizeof(ch))) { | ||
| break; | ||
| } | ||
| i = j + 1; | ||
| } | ||
| else if (format[j] == L'u' && arg_idx < guest_args.size()) { | ||
| uint32_t val = static_cast<uint32_t>(guest_args[arg_idx++]); | ||
| ws << std::setfill(L'0') << std::setw(zero_pad) << val; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'X' && arg_idx < guest_args.size()) { | ||
| uint32_t val = static_cast<uint32_t>(guest_args[arg_idx++]); | ||
| ws << std::uppercase << std::hex << std::setfill(L'0') << std::setw(zero_pad) << val << std::dec; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'S' && arg_idx < guest_args.size()) { | ||
| char tmp[512] = {}; | ||
| auto tmpData = emu_vsnw->read(guest_args[arg_idx++], sizeof(tmp)); | ||
| memcpy(tmp, tmpData.data(), tmpData.size()); | ||
| ws << tmp; | ||
| i = j; | ||
| result.push_back(ch); | ||
| if (ch == L'\0') break; | ||
| } | ||
| else if (format[j] == L's' && arg_idx < guest_args.size()) { | ||
| wchar_t tmp[512] = {}; | ||
| auto tmpData = emu_vsnw->read(guest_args[arg_idx++], sizeof(tmp)); | ||
| memcpy(tmp, tmpData.data(), tmpData.size()); | ||
| ws << tmp; | ||
| i = j; | ||
| } | ||
| else if ((format[j] == L'd' || format[j] == L'i') && arg_idx < guest_args.size()) { | ||
| int64_t val = static_cast<int64_t>(guest_args[arg_idx++]); | ||
| ws << std::setfill(L'0') << std::setw(zero_pad) << val; | ||
| i = j; | ||
| } | ||
| else if (format[j] == L'x' && arg_idx < guest_args.size()) { | ||
| int64_t val = static_cast<int64_t>(guest_args[arg_idx++]); | ||
| ws << std::hex << std::setfill(L'0') << std::setw(zero_pad) << val << std::dec; | ||
| i = j; | ||
| } | ||
| else { | ||
| ws << format[i]; | ||
| } | ||
| } | ||
| else { | ||
| ws << format[i]; | ||
| } | ||
| } | ||
| std::wstring result = ws.str(); | ||
| std::string results; | ||
| UnicodeToANSI(result, results); | ||
| if (strcmp(results.c_str(), "\n") != 0) { | ||
| std::string s = results + " \n"; | ||
| Logger::Log(true, ConsoleColor::RED, "_vsnwprintf\n"); | ||
| Logger::Log(true, ConsoleColor::RED, s.c_str()); | ||
| } | ||
|
|
||
| size_t write_len = sizeInWords; | ||
| emu_vsnw->write(buffer_ptr, result.c_str(), result.size()); | ||
|
|
||
| int ret = ws.str().size(); | ||
| emu_vsnw->rax(static_cast<uint64_t>(ret)); | ||
|
|
||
|
|
||
| RetHook(uc); | ||
| for (auto& ti : loader->Threads) { | ||
| SetEvent(ti->Event); | ||
| loader->errorevent = nullptr; | ||
| } | ||
| Logger::Log(true, ConsoleColor::RED, L"%ls\n", result.c_str()); | ||
| }, returnAddress, returnAddress + 1, | ||
| { buffer_ptr, sizeInWords, format_ptr, va_args_ptr }); |
There was a problem hiding this comment.
The _vsnwprintf function implementation is incomplete. The entire printf-style formatting logic has been removed and replaced with a hook that only logs the result buffer after the function returns. However, the actual formatting and writing to the buffer is not performed, which means the emulated code won't work correctly. The function needs to either keep the original formatting implementation or allow the actual implementation to run.
|
|
||
| if (Symbols_) { | ||
| Symbols_->Release(); | ||
| } |
There was a problem hiding this comment.
The destructor is missing a null check and Release call for DataSpaces_. All other COM interface pointers (Client_, Control_, Registers_, Symbols_) are properly released in the destructor, but DataSpaces_ is missing. Add:
if (DataSpaces_) {
DataSpaces_->Release();
}| } | |
| } | |
| if (DataSpaces_) { | |
| DataSpaces_->Release(); | |
| } |
| IDebugClient4* Client_; | ||
| IDebugControl4* Control_; | ||
| IDebugRegisters2* Registers_; | ||
| IDebugDataSpaces4* DataSpaces_; | ||
| IDebugSymbols3* Symbols_; |
There was a problem hiding this comment.
The COM interface pointers (Client_, Control_, Registers_, DataSpaces_, Symbols_) are not initialized to nullptr in the constructor. With Debugger_t() = default, these raw pointers will have undefined values. The destructor checks if (Client_) etc., which will be unreliable if these pointers aren't initialized. Either:
- Initialize them in-class:
IDebugClient4* Client_ = nullptr;etc., or - Provide an explicit constructor that initializes them to nullptr
| IDebugClient4* Client_; | |
| IDebugControl4* Control_; | |
| IDebugRegisters2* Registers_; | |
| IDebugDataSpaces4* DataSpaces_; | |
| IDebugSymbols3* Symbols_; | |
| IDebugClient4* Client_ = nullptr; | |
| IDebugControl4* Control_ = nullptr; | |
| IDebugRegisters2* Registers_ = nullptr; | |
| IDebugDataSpaces4* DataSpaces_ = nullptr; | |
| IDebugSymbols3* Symbols_ = nullptr; |
| for (auto object : loader->objectList) { | ||
| uc_hook_add(ti->tuc, &t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); | ||
| // MOD_TEST | ||
| //uc_hook_add(ti->tuc, &t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); |
There was a problem hiding this comment.
[nitpick] Commented-out code (lines 2791-2792) should be removed. The original implementation using raw pointer casting is preserved in comments, but this adds clutter and should be handled through version control.
| //uc_hook_add(ti->tuc, &t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); |
|
|
||
| std::uint64_t Debugger_t::Evaluate64(const char* Expr) const { | ||
| DEBUG_VALUE Value; | ||
| Control_->Evaluate(Expr, DEBUG_VALUE_INT64, &Value, NULL); |
There was a problem hiding this comment.
The return value of Control_->Evaluate() is not checked. If the evaluation fails, Value.I64 will contain uninitialized data. Add error handling:
HRESULT hr = Control_->Evaluate(Expr, DEBUG_VALUE_INT64, &Value, NULL);
if (FAILED(hr)) {
return 0ull;
}
return Value.I64;| Control_->Evaluate(Expr, DEBUG_VALUE_INT64, &Value, NULL); | |
| HRESULT hr = Control_->Evaluate(Expr, DEBUG_VALUE_INT64, &Value, NULL); | |
| if (FAILED(hr)) { | |
| return 0ull; | |
| } |
| /*Object* cr3Obj = new Object("CR3", cr3, 0x1000); | ||
| this->objectList.emplace_back(cr3Obj);*/ |
There was a problem hiding this comment.
[nitpick] Commented-out code blocks (lines 454-457 and 510-511) should be removed. The original implementation using raw pointers is preserved in comments, but this should be handled through version control instead of inline comments.
| /*Object* cr3Obj = new Object("CR3", cr3, 0x1000); | |
| this->objectList.emplace_back(cr3Obj);*/ |
| for (auto object : peLoader.objectList) { | ||
| Emu(uc)->hook_add(&t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); | ||
| // MOD_TEST | ||
| // Emu(uc)->hook_add(&t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); |
There was a problem hiding this comment.
[nitpick] Commented-out code (lines 112-113) should be removed. The original implementation using raw pointer casting is preserved in comments, but this adds clutter and should be handled through version control.
| // Emu(uc)->hook_add(&t, UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE, (void*)Unicorn::hook_access_object, (void*)object, object->address, object->address + object->size); |
| // else | ||
| // Logger::Log(true, ConsoleColor::RED, "Page error : %llx\n", pe->Base + i * 0x1000); |
There was a problem hiding this comment.
The commented-out error logging (lines 858-859) should be removed or uncommented with a proper justification. If page errors should no longer be logged, remove the code entirely. If this is temporary debugging code, it should be removed before merging.
| // else | |
| // Logger::Log(true, ConsoleColor::RED, "Page error : %llx\n", pe->Base + i * 0x1000); |
| const std::vector<std::string_view> Dlls = { "dbghelp.dll", "symsrv.dll", | ||
| "dbgeng.dll", "dbgcore.dll" }; | ||
| const fs::path DefaultDbgDllLocation( | ||
| R"(c:\program Files (x86)\windows kits\10\debuggers\x64)"); |
There was a problem hiding this comment.
The path string has inconsistent capitalization. "program Files" should be "Program Files" to match the standard Windows directory naming convention. While Windows paths are case-insensitive, using the correct casing improves code clarity and consistency.
| R"(c:\program Files (x86)\windows kits\10\debuggers\x64)"); | |
| R"(c:\Program Files (x86)\windows kits\10\debuggers\x64)"); |
Debugger.cppandDebugger.hppto improve dump information parsingPELoader::objectList