diff options
-rw-r--r-- | CMakeLists.txt | 18 | ||||
-rw-r--r-- | LICENSE.txt | 21 | ||||
-rw-r--r-- | common.cmake | 90 | ||||
-rw-r--r-- | include/pdb/address.hpp | 13 | ||||
-rw-r--r-- | include/pdb/all.hpp | 15 | ||||
-rw-r--r-- | include/pdb/dbghelp.hpp | 44 | ||||
-rw-r--r-- | include/pdb/error.hpp | 42 | ||||
-rw-r--r-- | include/pdb/handle.hpp | 29 | ||||
-rw-r--r-- | include/pdb/module.hpp | 65 | ||||
-rw-r--r-- | include/pdb/repo.hpp | 49 | ||||
-rw-r--r-- | include/pdb/symbol.hpp | 81 | ||||
-rw-r--r-- | include/pdb/utils/file.hpp | 18 | ||||
-rw-r--r-- | src/dbghelp.cpp | 166 | ||||
-rw-r--r-- | src/error.cpp | 53 | ||||
-rw-r--r-- | src/repo.cpp | 128 | ||||
-rw-r--r-- | src/utils/file.cpp | 51 | ||||
-rw-r--r-- | utils/CMakeLists.txt | 14 | ||||
-rw-r--r-- | utils/addr2name.cpp | 130 | ||||
-rw-r--r-- | utils/command_line.hpp | 86 | ||||
-rw-r--r-- | utils/enum_symbols.cpp | 117 | ||||
-rw-r--r-- | utils/name2addr.cpp | 132 | ||||
-rw-r--r-- | utils/pdb_descr.hpp | 86 |
22 files changed, 1448 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..67194a1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +project(pdb_repo CXX) + +include(common.cmake) + +file(GLOB_RECURSE pdb_repo_include "include/*.hpp") +file(GLOB_RECURSE pdb_repo_src "src/*.cpp") +add_library(pdb_repo ${pdb_repo_include} ${pdb_repo_src}) +target_compile_definitions(pdb_repo PRIVATE NOMINMAX PUBLIC _NO_CVCONST_H) +target_include_directories(pdb_repo PUBLIC include/) +target_link_libraries(pdb_repo PRIVATE DbgHelp) + +if(MSVC_VERSION EQUAL 1900) + # These annoying DbgHelp.h warnings: + # https://connect.microsoft.com/VisualStudio/feedback/details/888527/warnings-on-dbghelp-h + target_compile_options(pdb_repo PUBLIC /wd4091) +endif() + +add_subdirectory(utils) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..67ff5f1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common.cmake b/common.cmake new file mode 100644 index 0000000..474deb4 --- /dev/null +++ b/common.cmake @@ -0,0 +1,90 @@ +# It's a CMake code snippet I use in all of my CMake projects. +# It makes targets link the runtime statically by default + strips debug +# symbols in release builds. + +get_directory_property(parent_directory PARENT_DIRECTORY) +set(is_root_project $<NOT:parent_directory>) + +set(USE_STATIC_RUNTIME "${is_root_project}" CACHE BOOL "Link the runtime statically") +set(STRIP_SYMBOL_TABLE "${is_root_project}" CACHE BOOL "Strip symbol tables") + +if(is_root_project) + if(MSVC) + add_compile_options(/MP /W4) + elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + add_compile_options(-Wall -Wextra) + endif() +endif() + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +function(use_static_runtime_msvc target) + get_target_property(target_type "${target}" TYPE) + if(target_type STREQUAL INTERFACE_LIBRARY) + else() + target_compile_options("${target}" PRIVATE + $<$<CONFIG:Debug>:/MTd> + $<$<NOT:$<CONFIG:Debug>>:/MT>) + endif() +endfunction() + +function(use_static_runtime_gcc target) + get_target_property(target_type "${target}" TYPE) + if(target_type STREQUAL EXECUTABLE) + target_link_libraries("${target}" PRIVATE -static) + endif() +endfunction() + +function(use_static_runtime target) + if(MSVC) + use_static_runtime_msvc("${target}") + elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + use_static_runtime_gcc("${target}") + else() + message(WARNING "Unrecognized toolset") + endif() +endfunction() + +function(strip_symbol_table_gcc target) + get_target_property(target_type "${target}" TYPE) + set(release_build $<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>>) + if(target_type STREQUAL INTERFACE_LIBRARY) + else() + target_link_libraries("${target}" PRIVATE $<${release_build}:-s>) + endif() +endfunction() + +function(strip_symbol_table target) + if(MSVC) + elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + strip_symbol_table_gcc("${target}") + else() + message(WARNING "Unrecognized toolset") + endif() +endfunction() + +function(apply_common_settings target) + if(TARGET "${target}") + get_target_property(target_imported "${target}" IMPORTED) + if(target_imported STREQUAL NOTFOUND OR NOT target_imported) + if(STRIP_SYMBOL_TABLE) + strip_symbol_table("${target}") + endif() + if(USE_STATIC_RUNTIME) + use_static_runtime("${target}") + endif() + endif() + endif() +endfunction() + +macro(add_executable target) + _add_executable(${ARGV}) + apply_common_settings("${target}") +endmacro() + +macro(add_library target) + _add_library(${ARGV}) + apply_common_settings("${target}") +endmacro() diff --git a/include/pdb/address.hpp b/include/pdb/address.hpp new file mode 100644 index 0000000..53b25e8 --- /dev/null +++ b/include/pdb/address.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include <Windows.h> + +namespace pdb +{ + typedef DWORD64 Address; +} diff --git a/include/pdb/all.hpp b/include/pdb/all.hpp new file mode 100644 index 0000000..84be09e --- /dev/null +++ b/include/pdb/all.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "address.hpp" +#include "dbghelp.hpp" +#include "error.hpp" +#include "handle.hpp" +#include "module.hpp" +#include "repo.hpp" +#include "symbol.hpp" +#include "utils/file.hpp" diff --git a/include/pdb/dbghelp.hpp b/include/pdb/dbghelp.hpp new file mode 100644 index 0000000..7b018bc --- /dev/null +++ b/include/pdb/dbghelp.hpp @@ -0,0 +1,44 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "address.hpp" +#include "module.hpp" +#include "symbol.hpp" + +#include <Windows.h> + +#include <functional> +#include <string> + +namespace pdb +{ + class DbgHelp + { + public: + DbgHelp(); + ~DbgHelp(); + + ModuleInfo load_pdb(const std::string& path) const; + + typedef std::function<void (const SymbolInfo&)> OnSymbol; + void enum_symbols(const ModuleInfo&, const OnSymbol&) const; + + SymbolInfo resolve_symbol(Address) const; + SymbolInfo resolve_symbol(const std::string&) const; + + void close(); + + private: + ModuleInfo get_module_info(Address offline_base) const; + + const HANDLE id = GetCurrentProcess(); + bool closed = false; + + DbgHelp(const DbgHelp&) = delete; + DbgHelp& operator=(const DbgHelp&) = delete; + }; +} diff --git a/include/pdb/error.hpp b/include/pdb/error.hpp new file mode 100644 index 0000000..6640907 --- /dev/null +++ b/include/pdb/error.hpp @@ -0,0 +1,42 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include <Windows.h> + +#include <string> +#include <system_error> + +namespace pdb +{ + namespace error + { + class CategoryWindows : public std::error_category + { + public: + CategoryWindows() = default; + + const char* name() const noexcept { return "Windows"; } + + std::string message(int) const; + }; + + inline const CategoryWindows& category_windows() + { + static const CategoryWindows instance; + return instance; + } + + inline std::system_error windows(DWORD code) + { + static_assert(sizeof(DWORD) == sizeof(int), "Aren't DWORDs the same size as ints?"); + + return std::system_error{ + static_cast<int>(code), + category_windows()}; + } + } +} diff --git a/include/pdb/handle.hpp b/include/pdb/handle.hpp new file mode 100644 index 0000000..52fb805 --- /dev/null +++ b/include/pdb/handle.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include <Windows.h> + +#include <cassert> + +#include <memory> + +namespace pdb +{ + struct CloseHandle + { + void operator()(HANDLE raw) const + { + if (raw == NULL || raw == INVALID_HANDLE_VALUE) + return; + const auto ret = ::CloseHandle(raw); + assert(ret); + UNREFERENCED_PARAMETER(ret); + } + }; + + typedef std::unique_ptr<void, CloseHandle> Handle; +} diff --git a/include/pdb/module.hpp b/include/pdb/module.hpp new file mode 100644 index 0000000..118a53b --- /dev/null +++ b/include/pdb/module.hpp @@ -0,0 +1,65 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "address.hpp" + +#include <Windows.h> +#include <DbgHelp.h> + +#include <cstring> + +#include <string> + +namespace pdb +{ + class ModuleInfo + { + public: + typedef IMAGEHLP_MODULE64 Raw; + + ModuleInfo() + : raw{prepare_buffer()} + { } + + explicit ModuleInfo(const Raw& raw) + : raw{raw} + { } + + explicit operator Raw&() { return raw; } + + explicit operator const Raw&() const { return raw; } + + Address get_offline_base() const { return raw.BaseOfImage; } + + std::string get_name() const { return raw.ModuleName; } + + private: + static Raw prepare_buffer() + { + Raw raw; + std::memset(&raw, 0, sizeof(raw)); + raw.SizeOfStruct = sizeof(raw); + return raw; + } + + Raw raw; + }; + + class Module : public ModuleInfo + { + public: + Module(Address online_base, const ModuleInfo& info) + : ModuleInfo{info} + , online_base{online_base} + { } + + Address get_online_base() const { return online_base; } + + private: + const Address online_base; + }; +} diff --git a/include/pdb/repo.hpp b/include/pdb/repo.hpp new file mode 100644 index 0000000..239bd7e --- /dev/null +++ b/include/pdb/repo.hpp @@ -0,0 +1,49 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "address.hpp" +#include "dbghelp.hpp" +#include "module.hpp" +#include "symbol.hpp" + +#include <functional> +#include <map> +#include <string> + +namespace pdb +{ + class Repo + { + public: + Repo() = default; + + Address add_pdb(Address online_base, const std::string& path); + + typedef std::function<void (const Symbol&)> OnSymbol; + void enum_symbols(const OnSymbol&) const; + void enum_symbols(Address offline_base, const OnSymbol&) const; + void enum_symbols(const Module&, const OnSymbol&) const; + + Symbol resolve_symbol(Address) const; + Symbol resolve_symbol(const std::string&) const; + + private: + Symbol symbol_from_buffer(const SymbolInfo&) const; + Symbol symbol_from_buffer(const Module&, const SymbolInfo&) const; + + const Module& module_from_online_address(Address) const; + const Module& module_from_offline_address(Address) const; + + Address address_offline_to_online(Address) const; + Address address_online_to_offline(Address) const; + + const DbgHelp dbghelp; + + std::map<Address, Module> online_modules; + std::map<Address, const Module&> offline_modules; + }; +} diff --git a/include/pdb/symbol.hpp b/include/pdb/symbol.hpp new file mode 100644 index 0000000..9e11d04 --- /dev/null +++ b/include/pdb/symbol.hpp @@ -0,0 +1,81 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "module.hpp" + +#include <Windows.h> +#include <DbgHelp.h> + +#include <cstring> + +#include <string> + +namespace pdb +{ + class SymbolInfo + { + public: + typedef SYMBOL_INFO Raw; + + SymbolInfo() + : raw{*reinterpret_cast<Raw*>(buffer)} + { + raw.SizeOfStruct = sizeof(Raw); + raw.MaxNameLen = MAX_SYM_NAME; + } + + SymbolInfo(const Raw& raw) + : SymbolInfo{} + { + std::memcpy(buffer, &raw, raw.SizeOfStruct + raw.NameLen - 1); + } + + explicit operator Raw&() { return raw; } + + explicit operator const Raw&() const { return raw; } + + std::string get_name() const { return {raw.Name, raw.NameLen}; } + + Address get_offline_base() const { return raw.ModBase; } + + Address get_offline_address() const { return raw.Address; } + + typedef ULONG Tag; + + Tag get_tag() const { return raw.Tag; } + + enum class Type : Tag + { + Function = SymTagFunction, + RESERVED = SymTagMax, + }; + + Type get_type() const { return static_cast<Type>(get_tag()); } + + bool is_function() const { return get_type() == Type::Function; } + + private: + unsigned char buffer[sizeof(Raw) + MAX_SYM_NAME - 1]; + + protected: + Raw& raw; + }; + + class Symbol : public SymbolInfo + { + public: + Symbol(Address online_address, const SymbolInfo& info) + : SymbolInfo{info} + , online_address{online_address} + { } + + Address get_online_address() const { return online_address; } + + private: + const Address online_address; + }; +} diff --git a/include/pdb/utils/file.hpp b/include/pdb/utils/file.hpp new file mode 100644 index 0000000..5b83f2b --- /dev/null +++ b/include/pdb/utils/file.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include <cstddef> + +#include <string> + +namespace pdb +{ + namespace file + { + std::size_t get_size(const std::string&); + } +} diff --git a/src/dbghelp.cpp b/src/dbghelp.cpp new file mode 100644 index 0000000..c319fee --- /dev/null +++ b/src/dbghelp.cpp @@ -0,0 +1,166 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "pdb/all.hpp" + +#include <Windows.h> +#include <DbgHelp.h> + +#include <cstddef> +#include <cstring> + +#include <limits> +#include <stdexcept> +#include <string> + +namespace pdb +{ + namespace + { + void enable_debug_output() + { + SymSetOptions(SymGetOptions() | SYMOPT_DEBUG | SYMOPT_UNDNAME); + } + + void initialize(HANDLE id) + { + enable_debug_output(); + const auto ret = SymInitialize(id, NULL, FALSE); + if (!ret) + throw error::windows(GetLastError()); + } + + void clean_up(HANDLE id) + { + const auto ret = SymCleanup(id); + if (!ret) + throw error::windows(GetLastError()); + } + + Address gen_next_offline_base(std::size_t pdb_size) + { + static Address id = 0x10000000; + const auto next = id; + id += pdb_size; + return next; + } + + BOOL CALLBACK enum_symbols_callback( + SYMBOL_INFO *info, + ULONG, + VOID *raw_callback_ptr) + { + const auto callback_ptr = reinterpret_cast<DbgHelp::OnSymbol*>(raw_callback_ptr); + const auto& callback = *callback_ptr; + callback(SymbolInfo{*info}); + return TRUE; + } + } + + DbgHelp::DbgHelp() + { + initialize(id); + } + + ModuleInfo DbgHelp::load_pdb(const std::string& path) const + { + const auto size = file::get_size(path); + + if (size > std::numeric_limits<DWORD>::max()) + throw std::range_error{"PDB file size is too large"}; + + const auto offline_base = SymLoadModule64( + id, + NULL, + path.c_str(), + NULL, + gen_next_offline_base(size), + static_cast<DWORD>(size)); + + if (!offline_base) + throw error::windows(GetLastError()); + + return get_module_info(offline_base); + } + + ModuleInfo DbgHelp::get_module_info(Address offline_base) const + { + ModuleInfo info; + + const auto ret = SymGetModuleInfo64( + id, + offline_base, + &static_cast<ModuleInfo::Raw&>(info)); + + if (!ret) + throw error::windows(GetLastError()); + + return info; + } + + void DbgHelp::enum_symbols(const ModuleInfo& module, const OnSymbol& callback) const + { + const auto ret = SymEnumSymbols( + id, + module.get_offline_base(), + NULL, + &enum_symbols_callback, + const_cast<OnSymbol*>(&callback)); + + if (!ret) + throw error::windows(GetLastError()); + } + + SymbolInfo DbgHelp::resolve_symbol(Address online) const + { + DWORD64 displacement = 0; + SymbolInfo symbol; + + const auto ret = SymFromAddr( + id, + online, + &displacement, + &static_cast<SYMBOL_INFO&>(symbol)); + + if (!ret) + throw error::windows(GetLastError()); + + return symbol; + } + + SymbolInfo DbgHelp::resolve_symbol(const std::string& name) const + { + SymbolInfo symbol; + + const auto ret = SymFromName( + id, + name.c_str(), + &static_cast<SYMBOL_INFO&>(symbol)); + + if (!ret) + throw error::windows(GetLastError()); + + return symbol; + } + + void DbgHelp::close() + { + if (!closed) + { + clean_up(id); + closed = true; + } + } + + DbgHelp::~DbgHelp() + { + try + { + close(); + } + catch (...) + { } + } +} diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..10b668b --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "pdb/all.hpp" + +#include <Windows.h> + +#include <string> + +namespace pdb +{ + namespace error + { + namespace + { + std::string trim_trailing_newline(const std::string& s) + { + const auto last_pos = s.find_last_not_of("\r\n"); + if (std::string::npos == last_pos) + return {}; + return s.substr(0, last_pos + 1); + } + } + + std::string CategoryWindows::message(int code) const + { + char* buf; + + const auto nbwritten = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast<char*>(&buf), + 0, + NULL); + + if (0 == nbwritten) + { + LocalFree(buf); + return "Couldn't format the error message"; + } + + std::string msg{buf, nbwritten}; + LocalFree(buf); + return trim_trailing_newline(msg); + } + } +} diff --git a/src/repo.cpp b/src/repo.cpp new file mode 100644 index 0000000..0b28d5e --- /dev/null +++ b/src/repo.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "pdb/all.hpp" + +#include <map> +#include <stdexcept> +#include <string> +#include <utility> + +namespace pdb +{ + namespace + { + template <typename Value> + const Module& guess_module( + const std::map<Address, Value>& modules, + Address pivot) + { + if (modules.empty()) + throw std::range_error{"there're no modules to choose from"}; + + auto it = modules.lower_bound(pivot); + + if (it == modules.cend()) + { + --it; + return it->second; + } + + if (it->first > pivot) + { + if (it == modules.cbegin()) + throw std::range_error{"couldn't choose a module"}; + --it; + return it->second; + } + + return it->second; + } + + Address address_online_to_offline(const Module& module, Address online) + { + return module.get_offline_base() + online - module.get_online_base(); + } + + Address address_offline_to_online(const Module& module, Address offline) + { + return module.get_online_base() + offline - module.get_offline_base(); + } + } + + Address Repo::add_pdb(Address online_base, const std::string& path) + { + Module module{online_base, dbghelp.load_pdb(path)}; + const auto offline_base = module.get_offline_base(); + const auto it = online_modules.emplace(online_base, std::move(module)); + offline_modules.emplace(offline_base, it.first->second); + return offline_base; + } + + void Repo::enum_symbols(const OnSymbol& callback) const + { + for (const auto& it : offline_modules) + enum_symbols(it.second, callback); + } + + void Repo::enum_symbols(Address offline_base, const OnSymbol& callback) const + { + const auto it = offline_modules.find(offline_base); + if (it == offline_modules.cend()) + throw std::runtime_error{"unknown module"}; + enum_symbols(it->second, callback); + } + + void Repo::enum_symbols(const Module& module, const OnSymbol& callback) const + { + dbghelp.enum_symbols(module, [&] (const SymbolInfo& raw) + { + callback(symbol_from_buffer(module, raw)); + }); + } + + Symbol Repo::resolve_symbol(Address online) const + { + return symbol_from_buffer(dbghelp.resolve_symbol(address_online_to_offline(online))); + } + + Symbol Repo::resolve_symbol(const std::string& name) const + { + return symbol_from_buffer(dbghelp.resolve_symbol(name)); + } + + Symbol Repo::symbol_from_buffer(const SymbolInfo& raw) const + { + const auto it = offline_modules.find(raw.get_offline_base()); + if (it == offline_modules.cend()) + throw std::runtime_error{"symbol's module is unknown"}; + return symbol_from_buffer(it->second, raw); + } + + Symbol Repo::symbol_from_buffer(const Module& module, const SymbolInfo& raw) const + { + return {pdb::address_offline_to_online(module, raw.get_offline_address()), raw}; + } + + Address Repo::address_online_to_offline(Address online) const + { + return pdb::address_online_to_offline(module_from_online_address(online), online); + } + + Address Repo::address_offline_to_online(Address offline) const + { + return pdb::address_offline_to_online(module_from_offline_address(offline), offline); + } + + const Module& Repo::module_from_online_address(Address online) const + { + return guess_module(online_modules, online); + } + + const Module& Repo::module_from_offline_address(Address offline) const + { + return guess_module(offline_modules, offline); + } +} diff --git a/src/utils/file.cpp b/src/utils/file.cpp new file mode 100644 index 0000000..4150685 --- /dev/null +++ b/src/utils/file.cpp @@ -0,0 +1,51 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "pdb/all.hpp" + +#include <safeint.h> + +#include <Windows.h> + +#include <cstddef> + +#include <stdexcept> +#include <string> + +namespace pdb +{ + namespace file + { + std::size_t get_size(const std::string& path) + { + const Handle handle{CreateFileA( + path.c_str(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)}; + + if (handle.get() == INVALID_HANDLE_VALUE) + throw error::windows(GetLastError()); + + LARGE_INTEGER size; + + if (!GetFileSizeEx(handle.get(), &size)) + throw error::windows(GetLastError()); + + try + { + const msl::utilities::SafeInt<decltype(size.QuadPart)> safe_size{size.QuadPart}; + return static_cast<std::size_t>(safe_size); + } + catch (const msl::utilities::SafeIntException&) + { + throw std::range_error{"invalid file size"}; + } + } + } +} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 0000000..60f5b9f --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,14 @@ +find_package(Boost REQUIRED COMPONENTS filesystem program_options system) + +add_executable(enum_symbols enum_symbols.cpp command_line.hpp pdb_descr.hpp) +target_compile_definitions(enum_symbols PRIVATE _NO_CVCONST_H) +target_include_directories(enum_symbols SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(enum_symbols PRIVATE pdb_repo ${Boost_LIBRARIES}) + +add_executable(name2addr name2addr.cpp) +target_include_directories(name2addr SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(name2addr PRIVATE pdb_repo ${Boost_LIBRARIES}) + +add_executable(addr2name addr2name.cpp command_line.hpp pdb_descr.hpp) +target_include_directories(addr2name SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(addr2name PRIVATE pdb_repo ${Boost_LIBRARIES}) diff --git a/utils/addr2name.cpp b/utils/addr2name.cpp new file mode 100644 index 0000000..a01f61e --- /dev/null +++ b/utils/addr2name.cpp @@ -0,0 +1,130 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "command_line.hpp" +#include "pdb_descr.hpp" + +#include "pdb/all.hpp" + +#include <boost/program_options.hpp> + +#include <exception> +#include <iostream> +#include <string> +#include <vector> + +namespace +{ + class Addr2Name : public SettingsParser + { + public: + explicit Addr2Name(const std::string& argv0) + : SettingsParser{argv0, build_options(), build_args()} + { } + + bool exit_with_usage() const { return help_flag; } + + const char* get_short_description() const override + { + return "[-h|--help] [--pdb ADDR,PATH]... [--] [ADDR]..."; + } + + std::vector<PDB> pdbs; + + std::vector<pdb::Address> addresses; + + private: + Options build_options() + { + namespace program_options = boost::program_options; + Options descr{"options"}; + descr.add_options() + ("help,h", + program_options::bool_switch(&help_flag), + "show this message and exit") + ("pdb", + program_options::value<std::vector<PDB>>(&pdbs) + ->value_name("ADDR,PATH"), + "load a PDB file") + ("address", + program_options::value<std::vector<pdb::Address>>(&addresses) + ->value_name("ADDR"), + "add an address to resolve"); + return descr; + } + + static Arguments build_args() + { + Arguments descr; + descr.add("address", -1); + return descr; + } + + bool help_flag = false; + }; + + std::string format_address(pdb::Address address) + { + std::ostringstream oss; + oss << std::showbase << std::hex << address; + return oss.str(); + } + + void dump_error(const std::exception& e) + { + std::cerr << "error: " << e.what() << '\n'; + } + + void resolve_symbol(const pdb::Repo& repo, pdb::Address address) + { + try + { + std::cout << repo.resolve_symbol(address).get_name() << '\n'; + } + catch (const std::exception& e) + { + dump_error(e); + std::cout << format_address(address) << '\n'; + } + } +} + +int main(int argc, char* argv[]) +{ + try + { + const Addr2Name settings{argv[0]}; + + try + { + settings.parse(argc, argv); + } + catch (const boost::program_options::error& e) + { + settings.usage_error(e); + return 1; + } + + if (settings.exit_with_usage()) + { + settings.usage(); + return 0; + } + + pdb::Repo repo; + + for (const auto& pdb : settings.pdbs) + repo.add_pdb(pdb.online_base, pdb.path); + + for (const auto& address : settings.addresses) + resolve_symbol(repo, address); + } + catch (const std::exception& e) + { + dump_error(e); + return 1; + } + return 0; +} diff --git a/utils/command_line.hpp b/utils/command_line.hpp new file mode 100644 index 0000000..79fb673 --- /dev/null +++ b/utils/command_line.hpp @@ -0,0 +1,86 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include <boost/filesystem.hpp> +#include <boost/program_options.hpp> + +#include <exception> +#include <iostream> +#include <ostream> +#include <string> + +namespace +{ + class SettingsParser + { + public: + typedef boost::program_options::options_description Options; + typedef boost::program_options::positional_options_description Arguments; + + explicit SettingsParser(const std::string& argv0) + : prog_name{extract_filename(argv0)} + { } + + SettingsParser(const std::string& argv0, const Options& options) + : prog_name{extract_filename(argv0)} + , options{options} + { } + + SettingsParser(const std::string& argv0, const Options& options, const Arguments& args) + : prog_name{extract_filename(argv0)} + , options{options} + , args{args} + { } + + virtual ~SettingsParser() = default; + + virtual const char* get_short_description() const { return "[OPTION]..."; } + + void parse(int argc, char* argv[]) const + { + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::command_line_parser{argc, argv} + .options(options) + .positional(args) + .run(), + vm); + boost::program_options::notify(vm); + } + + void usage() const + { + std::cout << *this; + } + + void usage_error(const std::exception& e) const + { + std::cerr << "usage error: " << e.what() << '\n'; + std::cerr << *this; + } + + private: + static std::string extract_filename(const std::string& path) + { + return boost::filesystem::path{path}.filename().string(); + } + + const std::string prog_name; + const Options options; + const Arguments args; + + friend std::ostream& operator<<(std::ostream&, const SettingsParser&); + }; + + std::ostream& operator<<(std::ostream& os, const SettingsParser& cmd_parser) + { + const auto short_descr = cmd_parser.get_short_description(); + os << "usage: " << cmd_parser.prog_name << ' ' << short_descr << '\n'; + os << cmd_parser.options; + return os; + } +} diff --git a/utils/enum_symbols.cpp b/utils/enum_symbols.cpp new file mode 100644 index 0000000..d7fd417 --- /dev/null +++ b/utils/enum_symbols.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "command_line.hpp" +#include "pdb_descr.hpp" + +#include "pdb/all.hpp" + +#include <boost/program_options.hpp> + +#include <exception> +#include <iostream> +#include <string> +#include <vector> + +namespace +{ + class EnumSymbols : public SettingsParser + { + public: + explicit EnumSymbols(const std::string& argv0) + : SettingsParser{argv0, build_options()} + { } + + bool exit_with_usage() const { return help_flag; } + + const char* get_short_description() const override + { + return "[-h|--help] [--pdb ADDR,PATH]... [--functions]"; + } + + std::vector<PDB> pdbs; + + bool type_specified() const + { + return tag != reserved_tag; + } + + pdb::Symbol::Type get_type() const + { + return static_cast<pdb::Symbol::Type>(tag); + } + + private: + Options build_options() + { + namespace program_options = boost::program_options; + Options descr{"options"}; + descr.add_options() + ("help,h", + program_options::bool_switch(&help_flag), + "show this message and exit") + ("pdb", + program_options::value<std::vector<PDB>>(&pdbs) + ->value_name("ADDR,PATH"), + "load a PDB file") + ("functions", + program_options::value<pdb::Symbol::Tag>(&tag) + ->implicit_value(function_tag) + ->zero_tokens(), + "only list functions"); + return descr; + } + + bool help_flag = false; + + static const auto reserved_tag = static_cast<pdb::Symbol::Tag>(pdb::Symbol::Type::RESERVED); + static const auto function_tag = static_cast<pdb::Symbol::Tag>(pdb::Symbol::Type::Function); + + pdb::Symbol::Tag tag = reserved_tag; + }; +} + +int main(int argc, char* argv[]) +{ + try + { + const EnumSymbols settings{argv[0]}; + + try + { + settings.parse(argc, argv); + } + catch (const boost::program_options::error& e) + { + settings.usage_error(e); + return 1; + } + + if (settings.exit_with_usage()) + { + settings.usage(); + return 0; + } + + pdb::Repo repo; + + for (const auto& pdb : settings.pdbs) + { + const auto id = repo.add_pdb(pdb.online_base, pdb.path); + + repo.enum_symbols(id, [&] (const pdb::Symbol& symbol) + { + if (!settings.type_specified() || settings.get_type() == symbol.get_type()) + std::cout << symbol.get_name() << '\n'; + }); + } + } + catch (const std::exception& e) + { + std::cerr << "error: " << e.what() << '\n'; + return 1; + } + return 0; +} diff --git a/utils/name2addr.cpp b/utils/name2addr.cpp new file mode 100644 index 0000000..055b469 --- /dev/null +++ b/utils/name2addr.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#include "command_line.hpp" +#include "pdb_descr.hpp" + +#include "pdb/all.hpp" + +#include <boost/program_options.hpp> + +#include <exception> +#include <iostream> +#include <sstream> +#include <string> +#include <vector> + +namespace +{ + class Name2Addr : public SettingsParser + { + public: + explicit Name2Addr(const std::string& argv0) + : SettingsParser{argv0, build_options(), build_args()} + { } + + bool exit_with_usage() const { return help_flag; } + + const char* get_short_description() const override + { + return "[-h|--help] [--pdb ADDR,PATH]... [--] [NAME]..."; + } + + std::vector<PDB> pdbs; + + std::vector<std::string> names; + + private: + Options build_options() + { + namespace program_options = boost::program_options; + Options descr{"options"}; + descr.add_options() + ("help,h", + program_options::bool_switch(&help_flag), + "show this message and exit") + ("pdb", + program_options::value<std::vector<PDB>>(&pdbs) + ->value_name("ADDR,PATH"), + "load a PDB file") + ("name", + program_options::value<std::vector<std::string>>(&names) + ->value_name("NAME"), + "add a name to resolve"); + return descr; + } + + static Arguments build_args() + { + Arguments descr; + descr.add("name", -1); + return descr; + } + + bool help_flag = false; + }; + + std::string format_address(pdb::Address address) + { + std::ostringstream oss; + oss << std::showbase << std::hex << address; + return oss.str(); + } + + void dump_error(const std::exception& e) + { + std::cerr << "error: " << e.what() << '\n'; + } + + void resolve_symbol(const pdb::Repo& repo, const std::string& name) + { + try + { + const auto address = repo.resolve_symbol(name).get_online_address(); + std::cout << format_address(address) << '\n'; + } + catch (const std::exception& e) + { + dump_error(e); + std::cout << name << '\n'; + } + } +} + +int main(int argc, char* argv[]) +{ + try + { + const Name2Addr settings{argv[0]}; + + try + { + settings.parse(argc, argv); + } + catch (const boost::program_options::error& e) + { + settings.usage_error(e); + return 1; + } + + if (settings.exit_with_usage()) + { + settings.usage(); + return 0; + } + + pdb::Repo repo; + + for (const auto& pdb : settings.pdbs) + repo.add_pdb(pdb.online_base, pdb.path); + + for (const auto& name : settings.names) + resolve_symbol(repo, name); + } + catch (const std::exception& e) + { + dump_error(e); + return 1; + } + return 0; +} diff --git a/utils/pdb_descr.hpp b/utils/pdb_descr.hpp new file mode 100644 index 0000000..c57a0e9 --- /dev/null +++ b/utils/pdb_descr.hpp @@ -0,0 +1,86 @@ +// Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> +// This file is part of the "PDB repository" project. +// For details, see https://github.com/egor-tensin/pdb-repo. +// Distributed under the MIT License. + +#pragma once + +#include "pdb/all.hpp" + +#include <boost/program_options.hpp> + +#include <sstream> +#include <string> +#include <vector> + +namespace +{ + struct PDB + { + PDB(pdb::Address online_base, const std::string& path) + : online_base{online_base} + , path{path} + { } + + pdb::Address online_base; + std::string path; + + static PDB parse(std::string src) + { + static constexpr auto sep = ','; + const auto sep_pos = src.find(sep); + if (sep_pos == std::string::npos) + boost::throw_exception(boost::program_options::invalid_option_value{src}); + pdb::Address online_base; + if (!parse_address(online_base, src.substr(0, sep_pos))) + boost::throw_exception(boost::program_options::invalid_option_value{src}); + return {online_base, src.substr(sep_pos + 1)}; + } + + static bool parse_address(pdb::Address& dest, const std::string& src) + { + std::istringstream iss{src}; + iss >> std::hex; + char c; + return iss >> dest && !iss.get(c); + } + + static pdb::Address parse_address(const std::string& src) + { + pdb::Address dest; + if (!parse_address(dest, src)) + boost::throw_exception(boost::program_options::invalid_option_value{src}); + return dest; + } + }; +} + +namespace boost +{ + namespace program_options + { + template <typename charT> + void validate( + boost::any& dest, + const std::vector<std::basic_string<charT>>& src_tokens, + PDB*, + int) + { + validators::check_first_occurrence(dest); + const auto& src_token = validators::get_single_string(src_tokens); + dest = any{PDB::parse(src_token)}; + } + + template <typename charT> + void validate( + boost::any& dest, + const std::vector<std::basic_string<charT>>& src_tokens, + pdb::Address*, + int) + { + validators::check_first_occurrence(dest); + const auto& src_token = validators::get_single_string(src_tokens); + dest = any{PDB::parse_address(src_token)}; + } + } +} |