From 4ccdf7fde5f9fa33a853e034e38ef344d494de78 Mon Sep 17 00:00:00 2001 From: Michael Maltsev <4129781+m417z@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:23:34 +0300 Subject: [PATCH] Added command line options --- MainDlg.cpp | 157 +++++-------------------- symbols_from_binary.cpp | 115 ++++++++++++++++++ symbols_from_binary.h | 15 +++ windhawk-symbol-helper.cpp | 88 ++++++++++++++ windhawk-symbol-helper.vcxproj | 2 + windhawk-symbol-helper.vcxproj.filters | 6 + 6 files changed, 254 insertions(+), 129 deletions(-) create mode 100644 symbols_from_binary.cpp create mode 100644 symbols_from_binary.h diff --git a/MainDlg.cpp b/MainDlg.cpp index 489d0b5..47032b4 100644 --- a/MainDlg.cpp +++ b/MainDlg.cpp @@ -3,6 +3,7 @@ #include "MainDlg.h" #include "symbol_enum.h" +#include "symbols_from_binary.h" #define WSH_VERSION L"1.0.3" @@ -291,142 +292,40 @@ void CMainDlg::OnOK(UINT uNotifyCode, int nID, CWindow wndCtl) { return; } - struct { - CString engineDir; - CString symbolsDir; - CString symbolServer; - CString targetExecutable; - bool undecorated; - bool decorated; - bool log; - } threadParams; - - GetDlgItemText(IDC_ENGINE_DIR, threadParams.engineDir); - GetDlgItemText(IDC_SYMBOLS_DIR, threadParams.symbolsDir); - GetDlgItemText(IDC_SYMBOL_SERVER, threadParams.symbolServer); - GetDlgItemText(IDC_TARGET_EXECUTABLE, threadParams.targetExecutable); - threadParams.undecorated = + SymbolsFromBinaryOptions options; + + GetDlgItemText(IDC_ENGINE_DIR, options.engineDir); + GetDlgItemText(IDC_SYMBOLS_DIR, options.symbolsDir); + GetDlgItemText(IDC_SYMBOL_SERVER, options.symbolServer); + GetDlgItemText(IDC_TARGET_EXECUTABLE, options.targetExecutable); + options.undecorated = CButton(GetDlgItem(IDC_UNDECORATED)).GetCheck() != BST_UNCHECKED; - threadParams.decorated = + options.decorated = CButton(GetDlgItem(IDC_DECORATED)).GetCheck() != BST_UNCHECKED; - threadParams.log = CButton(GetDlgItem(IDC_LOG)).GetCheck() != BST_UNCHECKED; + bool log = CButton(GetDlgItem(IDC_LOG)).GetCheck() != BST_UNCHECKED; SetDlgItemText(IDOK, L"Cancel"); - m_enumSymbolsThread = std::jthread([threadParams = std::move(threadParams), - &enumSymbolsResult = - m_enumSymbolsResult, - hWnd = - m_hWnd](std::stop_token stopToken) { - CStringA logOutput; - - try { - // Use a set for sorting and to avoid duplicate chunks in the - // output. Duplicates can happen if the same symbol is listed with - // multiple types, such as SymTagFunction and SymTagPublicSymbol, or - // if two variants of a decorated name are identical when - // undecorated. - std::set resultListChunks; - size_t resultListTotalLength = 0; - size_t count = 0; - - { - SymbolEnum::Callbacks callbacks{ - .queryCancel = - [&stopToken]() { return stopToken.stop_requested(); }, - .notifyProgress = - [hWnd](int progress) { - CWindow(hWnd).PostMessage(UWM_PROGRESS, - (WPARAM)progress); - }, - }; - - if (threadParams.log) { - callbacks.notifyLog = [&logOutput](PCSTR message) { - CStringA messageStr = message; - // Convert all newlines to CRLF and trim trailing - // newlines. - messageStr.Replace("\r\n", "\n"); - messageStr.Replace('\r', '\n'); - messageStr.TrimRight("\n"); - messageStr.Replace("\n", "\r\n"); - - logOutput += messageStr; - logOutput += "\r\n"; - }; - } - - CString addressPrefix; - CString chunk; - - SymbolEnum symbolEnum(threadParams.targetExecutable.GetString(), - nullptr, - threadParams.engineDir.GetString(), - threadParams.symbolsDir.GetString(), - threadParams.symbolServer.GetString(), - threadParams.undecorated - ? SymbolEnum::UndecorateMode::Default - : SymbolEnum::UndecorateMode::None, - callbacks); - - while (auto iter = symbolEnum.GetNextSymbol()) { - if (stopToken.stop_requested()) { - throw std::runtime_error("Canceled"); - } - - if (!threadParams.decorated && !threadParams.undecorated) { - count++; - continue; - } - - if (!iter->nameDecorated && !iter->name) { - continue; - } - - addressPrefix.Format(L"[%08" TEXT(PRIXPTR) L"] ", - iter->address); - - chunk.Empty(); - - if (threadParams.decorated) { - chunk += addressPrefix; - chunk += iter->nameDecorated ? iter->nameDecorated - : L""; - chunk += L"\r\n"; - } - - if (threadParams.undecorated) { - chunk += addressPrefix; - chunk += - iter->name ? iter->name : L""; - chunk += L"\r\n"; - } - - auto [_, inserted] = resultListChunks.insert(chunk); - if (inserted) { - resultListTotalLength += chunk.GetLength(); - count++; - } - } - } - - enumSymbolsResult.Preallocate( - logOutput.GetLength() + - static_cast(resultListTotalLength) + 128); - - enumSymbolsResult.Format(L"Found %zu symbols\r\n%S", count, - logOutput.GetString()); - - for (const auto& chunk : resultListChunks) { - enumSymbolsResult += chunk; + m_enumSymbolsThread = std::jthread( + [options = std::move(options), &enumSymbolsResult = m_enumSymbolsResult, + hWnd = m_hWnd, log](std::stop_token stopToken) { + CStringA logOutput; + + try { + enumSymbolsResult = SymbolsFromBinary( + std::move(options), log ? &logOutput : nullptr, + [hWnd](int progress) { + CWindow(hWnd).PostMessage(UWM_PROGRESS, + (WPARAM)progress); + }, + &stopToken); + } catch (const std::exception& e) { + enumSymbolsResult.Format(L"Error: %S\r\n%S", e.what(), + logOutput.GetString()); } - } catch (const std::exception& e) { - enumSymbolsResult.Format(L"Error: %S\r\n%S", e.what(), - logOutput.GetString()); - } - CWindow(hWnd).PostMessage(UWM_ENUM_SYMBOLS_DONE); - }); + CWindow(hWnd).PostMessage(UWM_ENUM_SYMBOLS_DONE); + }); } void CMainDlg::OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl) { diff --git a/symbols_from_binary.cpp b/symbols_from_binary.cpp new file mode 100644 index 0000000..b6acd29 --- /dev/null +++ b/symbols_from_binary.cpp @@ -0,0 +1,115 @@ +#include "stdafx.h" + +#include "symbols_from_binary.h" + +#include "symbol_enum.h" + +CString SymbolsFromBinary(SymbolsFromBinaryOptions options, + CStringA* logOutput, + std::function progressCallback, + std::stop_token* stopToken) { + CString enumSymbolsResult; + + // Use a set for sorting and to avoid duplicate chunks in the + // output. Duplicates can happen if the same symbol is listed with + // multiple types, such as SymTagFunction and SymTagPublicSymbol, or + // if two variants of a decorated name are identical when + // undecorated. + std::set resultListChunks; + size_t resultListTotalLength = 0; + size_t count = 0; + + { + SymbolEnum::Callbacks callbacks; + + if (stopToken) { + callbacks.queryCancel = [&stopToken]() { + return stopToken->stop_requested(); + }; + } + + if (progressCallback) { + callbacks.notifyProgress = progressCallback; + } + + if (logOutput) { + callbacks.notifyLog = [&logOutput](PCSTR message) { + CStringA messageStr = message; + // Convert all newlines to CRLF and trim trailing + // newlines. + messageStr.Replace("\r\n", "\n"); + messageStr.Replace('\r', '\n'); + messageStr.TrimRight("\n"); + messageStr.Replace("\n", "\r\n"); + + *logOutput += messageStr; + *logOutput += "\r\n"; + }; + } + + CString addressPrefix; + CString chunk; + + SymbolEnum symbolEnum( + options.targetExecutable.GetString(), nullptr, + options.engineDir.GetString(), options.symbolsDir.GetString(), + options.symbolServer.GetString(), + options.undecorated ? SymbolEnum::UndecorateMode::Default + : SymbolEnum::UndecorateMode::None, + callbacks); + + while (auto iter = symbolEnum.GetNextSymbol()) { + if (stopToken && stopToken->stop_requested()) { + throw std::runtime_error("Canceled"); + } + + if (!options.decorated && !options.undecorated) { + count++; + continue; + } + + if (!iter->nameDecorated && !iter->name) { + continue; + } + + addressPrefix.Format(L"[%08" TEXT(PRIXPTR) L"] ", iter->address); + + chunk.Empty(); + + if (options.decorated) { + chunk += addressPrefix; + chunk += iter->nameDecorated ? iter->nameDecorated + : L""; + chunk += L"\r\n"; + } + + if (options.undecorated) { + chunk += addressPrefix; + chunk += iter->name ? iter->name : L""; + chunk += L"\r\n"; + } + + auto [_, inserted] = resultListChunks.insert(chunk); + if (inserted) { + resultListTotalLength += chunk.GetLength(); + count++; + } + } + } + + enumSymbolsResult.Preallocate((logOutput ? logOutput->GetLength() : 0) + + static_cast(resultListTotalLength) + + 128); + + enumSymbolsResult.Format(L"Found %zu symbols\r\n", count); + + if (logOutput) { + enumSymbolsResult += *logOutput; + } + + for (const auto& chunk : resultListChunks) { + enumSymbolsResult += chunk; + } + + return enumSymbolsResult; +} diff --git a/symbols_from_binary.h b/symbols_from_binary.h new file mode 100644 index 0000000..b1ee8a0 --- /dev/null +++ b/symbols_from_binary.h @@ -0,0 +1,15 @@ +#pragma once + +struct SymbolsFromBinaryOptions { + CString engineDir; + CString symbolsDir; + CString symbolServer; + CString targetExecutable; + bool undecorated; + bool decorated; +}; + +CString SymbolsFromBinary(SymbolsFromBinaryOptions options, + CStringA* logOutput, + std::function progressCallback, + std::stop_token* stopToken); diff --git a/windhawk-symbol-helper.cpp b/windhawk-symbol-helper.cpp index 8b9d23e..0c43a1d 100644 --- a/windhawk-symbol-helper.cpp +++ b/windhawk-symbol-helper.cpp @@ -2,12 +2,100 @@ #include "MainDlg.h" +#include "symbols_from_binary.h" + CAppModule _Module; +namespace { + +struct CommandLineOptions { + SymbolsFromBinaryOptions symbolsFromBinaryOptions; + std::wstring outputFile; +}; + +std::optional OptionsFromCommandLine() { + if (__argc != 8) { + return std::nullopt; + } + + const auto is_true = [](const WCHAR* value) { + return _wcsicmp(value, L"true") == 0 || _wcsicmp(value, L"1") == 0; + }; + + return CommandLineOptions{ + .symbolsFromBinaryOptions = + { + .engineDir = __wargv[1], + .symbolsDir = __wargv[2], + .symbolServer = __wargv[3], + .targetExecutable = __wargv[4], + .undecorated = is_true(__wargv[5]), + .decorated = is_true(__wargv[6]), + }, + .outputFile = __wargv[7], + }; +} + +// https://stackoverflow.com/a/69410299 +std::string WideStringToString(PCWSTR wide_string) { + int wide_string_len = static_cast(wcslen(wide_string)); + int size_needed = WideCharToMultiByte( + CP_UTF8, 0, wide_string, wide_string_len, nullptr, 0, nullptr, nullptr); + if (size_needed <= 0) { + throw std::runtime_error("WideCharToMultiByte() failed: " + + std::to_string(size_needed)); + } + + std::string result(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, wide_string, wide_string_len, result.data(), + size_needed, nullptr, nullptr); + return result; +} + +bool WriteContentsToFile(PCWSTR path, const void* data, DWORD size) { + HANDLE hFile = CreateFile(path, GENERIC_WRITE, FILE_SHARE_READ, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + DWORD dwBytesWritten; + bool written = WriteFile(hFile, data, size, &dwBytesWritten, nullptr); + CloseHandle(hFile); + + return written; +} + +} // namespace + int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int nCmdShow) { + if (auto options = OptionsFromCommandLine()) { + try { + auto result = + SymbolsFromBinary(std::move(options->symbolsFromBinaryOptions), + nullptr, nullptr, nullptr); + + auto resultUtf8 = WideStringToString(result); + + DWORD resultUtf8Size = static_cast(resultUtf8.size()); + if (resultUtf8Size < resultUtf8.size()) { + throw std::runtime_error("Size too large"); + } + + if (!WriteContentsToFile(options->outputFile.c_str(), + resultUtf8.data(), resultUtf8Size)) { + throw std::runtime_error("WriteFile() failed"); + } + + return 0; + } catch (const std::exception&) { + return 1; + } + } + HRESULT hRes = ::CoInitialize(nullptr); ATLASSERT(SUCCEEDED(hRes)); diff --git a/windhawk-symbol-helper.vcxproj b/windhawk-symbol-helper.vcxproj index e5b9930..4874c34 100644 --- a/windhawk-symbol-helper.vcxproj +++ b/windhawk-symbol-helper.vcxproj @@ -226,6 +226,7 @@ Create Create + @@ -234,6 +235,7 @@ + diff --git a/windhawk-symbol-helper.vcxproj.filters b/windhawk-symbol-helper.vcxproj.filters index f3b4e87..6eadaa3 100644 --- a/windhawk-symbol-helper.vcxproj.filters +++ b/windhawk-symbol-helper.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -47,6 +50,9 @@ Header Files + + Header Files +