From 87bc4022100020b2d01e5e8a97e3766db6a4ce5b Mon Sep 17 00:00:00 2001 From: Nelson Billing Date: Wed, 12 Jun 2019 14:19:57 -0700 Subject: [PATCH] Enable PE-only metadata dumping for 64bit (aka. PE32+ format) PEs files. - Implement in common_windows_lib-- added class "PESourceLineWriter". - Add command-line switch to tell dump_syms to use PESourceLineWriter. Symbol data created this way will contain information to correlate the module with ones found in minidumps, along with frame info that allows much higher quality stack-walking in those minidumps. - Significant refactor of PDBSourceLineWriter-- all code concerned with extracting metadata from PE files has been moved into utility functions. This is to allow sharing of this functionality with newly- added PESourceLineWriter. - Added a unit test to dump_syms for the PE-only scenario. Change-Id: If0855f05d424d32d23f484995be5f34232179a37 Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1525325 Reviewed-by: Ivan Penkov --- src/common/windows/common_windows.gyp | 5 + src/common/windows/module_info.h | 75 ++++ src/common/windows/pdb_source_line_writer.cc | 273 +----------- src/common/windows/pdb_source_line_writer.h | 48 +- src/common/windows/pe_source_line_writer.cpp | 77 ++++ src/common/windows/pe_source_line_writer.h | 69 +++ src/common/windows/pe_util.cpp | 418 ++++++++++++++++++ src/common/windows/pe_util.h | 67 +++ src/tools/windows/dump_syms/dump_syms.cc | 35 +- .../windows/dump_syms/dump_syms_unittest.cc | 67 ++- .../testdata/pe_only_symbol_test.dll | Bin 0 -> 51200 bytes .../testdata/pe_only_symbol_test.sym | 214 +++++++++ 12 files changed, 1020 insertions(+), 328 deletions(-) create mode 100644 src/common/windows/module_info.h create mode 100644 src/common/windows/pe_source_line_writer.cpp create mode 100644 src/common/windows/pe_source_line_writer.h create mode 100644 src/common/windows/pe_util.cpp create mode 100644 src/common/windows/pe_util.h create mode 100644 src/tools/windows/dump_syms/testdata/pe_only_symbol_test.dll create mode 100644 src/tools/windows/dump_syms/testdata/pe_only_symbol_test.sym diff --git a/src/common/windows/common_windows.gyp b/src/common/windows/common_windows.gyp index 6626c576..095ab9e2 100644 --- a/src/common/windows/common_windows.gyp +++ b/src/common/windows/common_windows.gyp @@ -77,11 +77,16 @@ 'guid_string.h', 'http_upload.cc', 'http_upload.h', + 'module_info.h' 'omap.cc', 'omap.h', 'omap_internal.h', 'pdb_source_line_writer.cc', 'pdb_source_line_writer.h', + 'pe_source_line_writer.cc', + 'pe_source_line_writer.h', + 'pe_util.h', + 'pe_util.cc', 'string_utils.cc', 'string_utils-inl.h', 'symbol_collector_client.cc', diff --git a/src/common/windows/module_info.h b/src/common/windows/module_info.h new file mode 100644 index 00000000..3dccc808 --- /dev/null +++ b/src/common/windows/module_info.h @@ -0,0 +1,75 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef COMMON_WINDOWS_MODULE_INFO_H_ +#define COMMON_WINDOWS_MODULE_INFO_H_ + +#include + +namespace google_breakpad { + +using std::wstring; +// A structure that carries information that identifies a module. +struct PDBModuleInfo { +public: + // The basename of the pe/pdb file from which information was loaded. + wstring debug_file; + + // The module's identifier. For recent pe/pdb files, the identifier consists + // of the pe/pdb's guid, in uppercase hexadecimal form without any dashes + // or separators, followed immediately by the pe/pdb's age, also in + // uppercase hexadecimal form. For older pe/pdb files which have no guid, + // the identifier is the pe/pdb's 32-bit signature value, in zero-padded + // hexadecimal form, followed immediately by the pe/pdb's age, in lowercase + // hexadecimal form. + wstring debug_identifier; + + // A string identifying the cpu that the pe/pdb is associated with. + // Currently, this may be "x86" or "unknown". + wstring cpu; +}; + +// A structure that carries information that identifies a PE file, +// either an EXE or a DLL. +struct PEModuleInfo { + // The basename of the PE file. + wstring code_file; + + // The PE file's code identifier, which consists of its timestamp + // and file size concatenated together into a single hex string. + // (The fields IMAGE_OPTIONAL_HEADER::SizeOfImage and + // IMAGE_FILE_HEADER::TimeDateStamp, as defined in the ImageHlp + // documentation.) This is not well documented, if it's documented + // at all, but it's what symstore does and what DbgHelp supports. + wstring code_identifier; +}; + +} // namespace google_breakpad + +#endif // COMMON_WINDOWS_MODULE_INFO_H_ diff --git a/src/common/windows/pdb_source_line_writer.cc b/src/common/windows/pdb_source_line_writer.cc index b7788ca0..4030a2e9 100644 --- a/src/common/windows/pdb_source_line_writer.cc +++ b/src/common/windows/pdb_source_line_writer.cc @@ -45,6 +45,7 @@ #include "common/windows/dia_util.h" #include "common/windows/guid_string.h" +#include "common/windows/pe_util.h" #include "common/windows/string_utils-inl.h" // This constant may be missing from DbgHelp.h. See the documentation for @@ -53,56 +54,6 @@ #define UNDNAME_NO_ECSU 0x8000 // Suppresses enum/class/struct/union. #endif // UNDNAME_NO_ECSU -/* - * Not defined in WinNT.h for some reason. Definitions taken from: - * http://uninformed.org/index.cgi?v=4&a=1&p=13 - * - */ -typedef unsigned char UBYTE; - -#if !defined(_WIN64) -#define UNW_FLAG_EHANDLER 0x01 -#define UNW_FLAG_UHANDLER 0x02 -#define UNW_FLAG_CHAININFO 0x04 -#endif - -union UnwindCode { - struct { - UBYTE offset_in_prolog; - UBYTE unwind_operation_code : 4; - UBYTE operation_info : 4; - }; - USHORT frame_offset; -}; - -enum UnwindOperationCodes { - UWOP_PUSH_NONVOL = 0, /* info == register number */ - UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */ - UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */ - UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */ - UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */ - UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */ - // XXX: these are missing from MSDN! - // See: http://www.osronline.com/ddkx/kmarch/64bitamd_4rs7.htm - UWOP_SAVE_XMM, - UWOP_SAVE_XMM_FAR, - UWOP_SAVE_XMM128, /* info == XMM reg number, offset in next slot */ - UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */ - UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */ -}; - -// See: http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx -// Note: some fields removed as we don't use them. -struct UnwindInfo { - UBYTE version : 3; - UBYTE flags : 5; - UBYTE size_of_prolog; - UBYTE count_of_codes; - UBYTE frame_register : 4; - UBYTE frame_offset : 4; - UnwindCode unwind_code[1]; -}; - namespace google_breakpad { namespace { @@ -157,21 +108,7 @@ void MaybeRecordSymbol(DWORD rva, } } -// A helper class to scope a PLOADED_IMAGE. -class AutoImage { - public: - explicit AutoImage(PLOADED_IMAGE img) : img_(img) {} - ~AutoImage() { - if (img_) - ImageUnload(img_); - } - operator PLOADED_IMAGE() { return img_; } - PLOADED_IMAGE operator->() { return img_; } - - private: - PLOADED_IMAGE img_; -}; bool SymbolsMatch(IDiaSymbol* a, IDiaSymbol* b) { DWORD a_section, a_offset, b_section, b_offset; @@ -223,6 +160,7 @@ PDBSourceLineWriter::PDBSourceLineWriter() : output_(NULL) { } PDBSourceLineWriter::~PDBSourceLineWriter() { + Close(); } bool PDBSourceLineWriter::SetCodeFile(const wstring &exe_file) { @@ -728,131 +666,7 @@ bool PDBSourceLineWriter::PrintFrameDataUsingEXE() { return false; } - // Convert wchar to native charset because ImageLoad only takes - // a PSTR as input. - string code_file; - if (!WindowsStringUtils::safe_wcstombs(code_file_, &code_file)) { - return false; - } - - AutoImage img(ImageLoad((PSTR)code_file.c_str(), NULL)); - if (!img) { - fprintf(stderr, "Failed to load %s\n", code_file.c_str()); - return false; - } - PIMAGE_OPTIONAL_HEADER64 optional_header = - &(reinterpret_cast(img->FileHeader))->OptionalHeader; - if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { - fprintf(stderr, "Not a PE32+ image\n"); - return false; - } - - // Read Exception Directory - DWORD exception_rva = optional_header-> - DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; - DWORD exception_size = optional_header-> - DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; - PIMAGE_RUNTIME_FUNCTION_ENTRY funcs = - static_cast( - ImageRvaToVa(img->FileHeader, - img->MappedAddress, - exception_rva, - &img->LastRvaSection)); - for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) { - DWORD unwind_rva = funcs[i].UnwindInfoAddress; - // handle chaining - while (unwind_rva & 0x1) { - unwind_rva ^= 0x1; - PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = - static_cast( - ImageRvaToVa(img->FileHeader, - img->MappedAddress, - unwind_rva, - &img->LastRvaSection)); - unwind_rva = chained_func->UnwindInfoAddress; - } - - UnwindInfo *unwind_info = static_cast( - ImageRvaToVa(img->FileHeader, - img->MappedAddress, - unwind_rva, - &img->LastRvaSection)); - - DWORD stack_size = 8; // minimal stack size is 8 for RIP - DWORD rip_offset = 8; - do { - for (UBYTE c = 0; c < unwind_info->count_of_codes; c++) { - UnwindCode *unwind_code = &unwind_info->unwind_code[c]; - switch (unwind_code->unwind_operation_code) { - case UWOP_PUSH_NONVOL: { - stack_size += 8; - break; - } - case UWOP_ALLOC_LARGE: { - if (unwind_code->operation_info == 0) { - c++; - if (c < unwind_info->count_of_codes) - stack_size += (unwind_code + 1)->frame_offset * 8; - } else { - c += 2; - if (c < unwind_info->count_of_codes) - stack_size += (unwind_code + 1)->frame_offset | - ((unwind_code + 2)->frame_offset << 16); - } - break; - } - case UWOP_ALLOC_SMALL: { - stack_size += unwind_code->operation_info * 8 + 8; - break; - } - case UWOP_SET_FPREG: - case UWOP_SAVE_XMM: - case UWOP_SAVE_XMM_FAR: - break; - case UWOP_SAVE_NONVOL: - case UWOP_SAVE_XMM128: { - c++; // skip slot with offset - break; - } - case UWOP_SAVE_NONVOL_FAR: - case UWOP_SAVE_XMM128_FAR: { - c += 2; // skip 2 slots with offset - break; - } - case UWOP_PUSH_MACHFRAME: { - if (unwind_code->operation_info) { - stack_size += 88; - } else { - stack_size += 80; - } - rip_offset += 80; - break; - } - } - } - if (unwind_info->flags & UNW_FLAG_CHAININFO) { - PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = - reinterpret_cast( - (unwind_info->unwind_code + - ((unwind_info->count_of_codes + 1) & ~1))); - - unwind_info = static_cast( - ImageRvaToVa(img->FileHeader, - img->MappedAddress, - chained_func->UnwindInfoAddress, - &img->LastRvaSection)); - } else { - unwind_info = NULL; - } - } while (unwind_info); - fprintf(output_, "STACK CFI INIT %lx %lx .cfa: $rsp .ra: .cfa %lu - ^\n", - funcs[i].BeginAddress, - funcs[i].EndAddress - funcs[i].BeginAddress, rip_offset); - fprintf(output_, "STACK CFI %lx .cfa: $rsp %lu +\n", - funcs[i].BeginAddress, stack_size); - } - - return true; + return PrintPEFrameData(code_file_, output_); } bool PDBSourceLineWriter::PrintFrameData() { @@ -1236,8 +1050,8 @@ next_child: return param_size; } -bool PDBSourceLineWriter::WriteMap(FILE *map_file) { - output_ = map_file; +bool PDBSourceLineWriter::WriteSymbols(FILE *symbol_file) { + output_ = symbol_file; // Load the OMAP information, and disable auto-translation of addresses in // preference of doing it ourselves. @@ -1259,7 +1073,9 @@ bool PDBSourceLineWriter::WriteMap(FILE *map_file) { } void PDBSourceLineWriter::Close() { - session_.Release(); + if (session_ != nullptr) { + session_.Release(); + } } bool PDBSourceLineWriter::GetModuleInfo(PDBModuleInfo *info) { @@ -1284,17 +1100,7 @@ bool PDBSourceLineWriter::GetModuleInfo(PDBModuleInfo *info) { // Instead, it returns one of the IMAGE_FILE_MACHINE values as // defined here: // http://msdn.microsoft.com/en-us/library/ms680313%28VS.85%29.aspx - switch (machine_type) { - case IMAGE_FILE_MACHINE_I386: - info->cpu = L"x86"; - break; - case IMAGE_FILE_MACHINE_AMD64: - info->cpu = L"x86_64"; - break; - default: - info->cpu = L"unknown"; - break; - } + info->cpu = FileHeaderMachineToCpuString(static_cast(machine_type)); } else { // Unexpected, but handle gracefully. info->cpu = L"unknown"; @@ -1317,35 +1123,14 @@ bool PDBSourceLineWriter::GetModuleInfo(PDBModuleInfo *info) { return false; } - // Use the same format that the MS symbol server uses in filesystem - // hierarchies. - wchar_t age_string[9]; - swprintf(age_string, sizeof(age_string) / sizeof(age_string[0]), - L"%x", age); - - // remove when VC++7.1 is no longer supported - age_string[sizeof(age_string) / sizeof(age_string[0]) - 1] = L'\0'; - - info->debug_identifier = GUIDString::GUIDToSymbolServerWString(&guid); - info->debug_identifier.append(age_string); + info->debug_identifier = GenerateDebugIdentifier(age, guid); } else { DWORD signature; if (FAILED(global->get_signature(&signature))) { return false; } - // Use the same format that the MS symbol server uses in filesystem - // hierarchies. - wchar_t identifier_string[17]; - swprintf(identifier_string, - sizeof(identifier_string) / sizeof(identifier_string[0]), - L"%08X%x", signature, age); - - // remove when VC++7.1 is no longer supported - identifier_string[sizeof(identifier_string) / - sizeof(identifier_string[0]) - 1] = L'\0'; - - info->debug_identifier = identifier_string; + info->debug_identifier = GenerateDebugIdentifier(age, signature); } CComBSTR debug_file_string; @@ -1368,41 +1153,7 @@ bool PDBSourceLineWriter::GetPEInfo(PEModuleInfo *info) { return false; } - // Convert wchar to native charset because ImageLoad only takes - // a PSTR as input. - string code_file; - if (!WindowsStringUtils::safe_wcstombs(code_file_, &code_file)) { - return false; - } - - AutoImage img(ImageLoad((PSTR)code_file.c_str(), NULL)); - if (!img) { - fprintf(stderr, "Failed to open PE file: %s\n", code_file.c_str()); - return false; - } - - info->code_file = WindowsStringUtils::GetBaseName(code_file_); - - // The date and time that the file was created by the linker. - DWORD TimeDateStamp = img->FileHeader->FileHeader.TimeDateStamp; - // The size of the file in bytes, including all headers. - DWORD SizeOfImage = 0; - PIMAGE_OPTIONAL_HEADER64 opt = - &((PIMAGE_NT_HEADERS64)img->FileHeader)->OptionalHeader; - if (opt->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { - // 64-bit PE file. - SizeOfImage = opt->SizeOfImage; - } else { - // 32-bit PE file. - SizeOfImage = img->FileHeader->OptionalHeader.SizeOfImage; - } - wchar_t code_identifier[32]; - swprintf(code_identifier, - sizeof(code_identifier) / sizeof(code_identifier[0]), - L"%08X%X", TimeDateStamp, SizeOfImage); - info->code_identifier = code_identifier; - - return true; + return ReadPEInfo(code_file_, info); } bool PDBSourceLineWriter::UsesGUID(bool *uses_guid) { diff --git a/src/common/windows/pdb_source_line_writer.h b/src/common/windows/pdb_source_line_writer.h index 3ed07361..c0adf29f 100644 --- a/src/common/windows/pdb_source_line_writer.h +++ b/src/common/windows/pdb_source_line_writer.h @@ -35,9 +35,10 @@ #include -#include #include +#include +#include "common/windows/module_info.h" #include "common/windows/omap.h" struct IDiaEnumLineNumbers; @@ -49,41 +50,6 @@ namespace google_breakpad { using std::wstring; using std::unordered_map; -// A structure that carries information that identifies a pdb file. -struct PDBModuleInfo { - public: - // The basename of the pdb file from which information was loaded. - wstring debug_file; - - // The pdb's identifier. For recent pdb files, the identifier consists - // of the pdb's guid, in uppercase hexadecimal form without any dashes - // or separators, followed immediately by the pdb's age, also in - // uppercase hexadecimal form. For older pdb files which have no guid, - // the identifier is the pdb's 32-bit signature value, in zero-padded - // hexadecimal form, followed immediately by the pdb's age, in lowercase - // hexadecimal form. - wstring debug_identifier; - - // A string identifying the cpu that the pdb is associated with. - // Currently, this may be "x86" or "unknown". - wstring cpu; -}; - -// A structure that carries information that identifies a PE file, -// either an EXE or a DLL. -struct PEModuleInfo { - // The basename of the PE file. - wstring code_file; - - // The PE file's code identifier, which consists of its timestamp - // and file size concatenated together into a single hex string. - // (The fields IMAGE_OPTIONAL_HEADER::SizeOfImage and - // IMAGE_FILE_HEADER::TimeDateStamp, as defined in the ImageHlp - // documentation.) This is not well documented, if it's documented - // at all, but it's what symstore does and what DbgHelp supports. - wstring code_identifier; -}; - class PDBSourceLineWriter { public: enum FileFormat { @@ -101,6 +67,9 @@ class PDBSourceLineWriter { // Returns true on success. bool Open(const wstring &file, FileFormat format); + // Closes the current pdb file and its associated resources. + void Close(); + // Sets the code file full path. This is optional for 32-bit modules. It is // also optional for 64-bit modules when there is an executable file stored // in the same directory as the PDB file. It is only required for 64-bit @@ -110,12 +79,9 @@ class PDBSourceLineWriter { // SetCodeFile() with a different file path and it will return false. bool SetCodeFile(const wstring &exe_file); - // Writes a map file from the current pdb file to the given file stream. + // Writes a Breakpad symbol file from the current pdb file to |symbol_file|. // Returns true on success. - bool WriteMap(FILE *map_file); - - // Closes the current pdb file and its associated resources. - void Close(); + bool WriteSymbols(FILE *symbol_file); // Retrieves information about the module's debugging file. Returns // true on success and false on failure. diff --git a/src/common/windows/pe_source_line_writer.cpp b/src/common/windows/pe_source_line_writer.cpp new file mode 100644 index 00000000..cb6cc713 --- /dev/null +++ b/src/common/windows/pe_source_line_writer.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "common/windows/pe_source_line_writer.h" + +#include "common/windows/pe_util.h" + +namespace google_breakpad { +PESourceLineWriter::PESourceLineWriter(const wstring& pe_file) : + pe_file_(pe_file) { +} + +PESourceLineWriter::~PESourceLineWriter() { +} + +bool PESourceLineWriter::WriteSymbols(FILE* symbol_file) { + PDBModuleInfo module_info; + if (!GetModuleInfo(&module_info)) { + return false; + } + // Hard-code "windows" for the OS because that's the only thing that makes + // sense for PDB files. (This might not be strictly correct for Windows CE + // support, but we don't care about that at the moment.) + fprintf(symbol_file, "MODULE windows %ws %ws %ws\n", + module_info.cpu.c_str(), module_info.debug_identifier.c_str(), + module_info.debug_file.c_str()); + + PEModuleInfo pe_info; + if (!GetPEInfo(&pe_info)) { + return false; + } + fprintf(symbol_file, "INFO CODE_ID %ws %ws\n", + pe_info.code_identifier.c_str(), + pe_info.code_file.c_str()); + + if (!PrintPEFrameData(pe_file_, symbol_file)) { + return false; + } + + return true; +} + +bool PESourceLineWriter::GetModuleInfo(PDBModuleInfo* info) { + return ReadModuleInfo(pe_file_, info); +} + +bool PESourceLineWriter::GetPEInfo(PEModuleInfo* info) { + return ReadPEInfo(pe_file_, info); +} + +} // namespace google_breakpad diff --git a/src/common/windows/pe_source_line_writer.h b/src/common/windows/pe_source_line_writer.h new file mode 100644 index 00000000..2bf1d4fd --- /dev/null +++ b/src/common/windows/pe_source_line_writer.h @@ -0,0 +1,69 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef COMMON_WINDOWS_PE_SOURCE_LINE_WRITER_H_ +#define COMMON_WINDOWS_PE_SOURCE_LINE_WRITER_H_ + +#include + +#include "common/basictypes.h" +#include "common/windows/module_info.h" + +namespace google_breakpad { + +using std::wstring; + +// PESourceLineWriter uses a pe file produced by Visual C++ to output +// a line/address map for use with BasicSourceLineResolver. +// NOTE: Only supports PE32+ format, ie. a 64bit PE file. +class PESourceLineWriter { +public: + explicit PESourceLineWriter(const wstring& pe_file); + ~PESourceLineWriter(); + + // Writes Breakpad symbols from the pe file to |symbol_file|. + // Returns true on success. + bool WriteSymbols(FILE* symbol_file); + + // Retrieves information about the module. Returns true on success. + bool GetModuleInfo(PDBModuleInfo* info); + + // Retrieves information about the module's PE file. Returns + // true on success. + bool GetPEInfo(PEModuleInfo* info); + +private: + const wstring pe_file_; + + DISALLOW_COPY_AND_ASSIGN(PESourceLineWriter); +}; + +} // namespace google_breakpad + +#endif // COMMON_WINDOWS_PE_SOURCE_LINE_WRITER_H_ diff --git a/src/common/windows/pe_util.cpp b/src/common/windows/pe_util.cpp new file mode 100644 index 00000000..11181954 --- /dev/null +++ b/src/common/windows/pe_util.cpp @@ -0,0 +1,418 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "pe_util.h" + +#include +#include +#include +#include + +#include + +#include "common/windows/string_utils-inl.h" +#include "common/windows/guid_string.h" + +namespace { + +/* + * Not defined in WinNT.h for some reason. Definitions taken from: + * http://uninformed.org/index.cgi?v=4&a=1&p=13 + * + */ +typedef unsigned char UBYTE; + +#if !defined(_WIN64) +#define UNW_FLAG_EHANDLER 0x01 +#define UNW_FLAG_UHANDLER 0x02 +#define UNW_FLAG_CHAININFO 0x04 +#endif + +union UnwindCode { + struct { + UBYTE offset_in_prolog; + UBYTE unwind_operation_code : 4; + UBYTE operation_info : 4; + }; + USHORT frame_offset; +}; + +enum UnwindOperationCodes { + UWOP_PUSH_NONVOL = 0, /* info == register number */ + UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */ + UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */ + UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */ + UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */ + UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */ + // XXX: these are missing from MSDN! + // See: http://www.osronline.com/ddkx/kmarch/64bitamd_4rs7.htm + UWOP_SAVE_XMM, + UWOP_SAVE_XMM_FAR, + UWOP_SAVE_XMM128, /* info == XMM reg number, offset in next slot */ + UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */ + UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */ +}; + +// See: http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx +// Note: some fields removed as we don't use them. +struct UnwindInfo { + UBYTE version : 3; + UBYTE flags : 5; + UBYTE size_of_prolog; + UBYTE count_of_codes; + UBYTE frame_register : 4; + UBYTE frame_offset : 4; + UnwindCode unwind_code[1]; +}; + +struct CV_INFO_PDB70 { + ULONG cv_signature; + GUID signature; + ULONG age; + UCHAR pdb_filename[ANYSIZE_ARRAY]; +}; + +#define CV_SIGNATURE_RSDS 'SDSR' + +// A helper class to scope a PLOADED_IMAGE. +class AutoImage { +public: + explicit AutoImage(PLOADED_IMAGE img) : img_(img) {} + ~AutoImage() { + if (img_) + ImageUnload(img_); + } + + operator PLOADED_IMAGE() { return img_; } + PLOADED_IMAGE operator->() { return img_; } + +private: + PLOADED_IMAGE img_; +}; +} // namespace + +namespace google_breakpad { + +using std::unique_ptr; +using google_breakpad::GUIDString; + +bool ReadModuleInfo(const wstring & pe_file, PDBModuleInfo * info) { + info->debug_file = WindowsStringUtils::GetBaseName(pe_file); + + // Convert wchar to native charset because ImageLoad only takes + // a PSTR as input. + string img_file; + if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { + fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", + pe_file.c_str()); + return false; + } + + AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); + if (!img) { + fprintf(stderr, "Failed to load %s\n", img_file.c_str()); + return false; + } + + info->cpu = FileHeaderMachineToCpuString( + img->FileHeader->FileHeader.Machine); + + PIMAGE_OPTIONAL_HEADER64 optional_header = + &(reinterpret_cast(img->FileHeader))->OptionalHeader; + if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + fprintf(stderr, "Not a PE32+ image\n"); + return false; + } + + // Search debug directories for a guid signature & age + DWORD debug_rva = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; + DWORD debug_size = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + PIMAGE_DEBUG_DIRECTORY debug_directories = + static_cast( + ImageRvaToVa(img->FileHeader, + img->MappedAddress, + debug_rva, + &img->LastRvaSection)); + + for (DWORD i = 0; i < debug_size / sizeof(*debug_directories); i++) { + if (debug_directories[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW || + debug_directories[i].SizeOfData < sizeof(CV_INFO_PDB70)) { + continue; + } + + struct CV_INFO_PDB70* cv_info = static_cast(ImageRvaToVa( + img->FileHeader, + img->MappedAddress, + debug_directories[i].AddressOfRawData, + &img->LastRvaSection)); + if (cv_info->cv_signature != CV_SIGNATURE_RSDS) { + continue; + } + + info->debug_identifier = GenerateDebugIdentifier(cv_info->age, + cv_info->signature); + return true; + } + + fprintf(stderr, "Image is missing debug information.\n"); + return false; +} + +bool ReadPEInfo(const wstring & pe_file, PEModuleInfo * info) { + // Convert wchar to native charset because ImageLoad only takes + // a PSTR as input. + string img_file; + if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { + fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", + pe_file.c_str()); + return false; + } + + AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); + if (!img) { + fprintf(stderr, "Failed to open PE file: %S\n", pe_file.c_str()); + return false; + } + + info->code_file = WindowsStringUtils::GetBaseName(pe_file); + + // The date and time that the file was created by the linker. + DWORD TimeDateStamp = img->FileHeader->FileHeader.TimeDateStamp; + // The size of the file in bytes, including all headers. + DWORD SizeOfImage = 0; + PIMAGE_OPTIONAL_HEADER64 opt = + &((PIMAGE_NT_HEADERS64)img->FileHeader)->OptionalHeader; + if (opt->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + // 64-bit PE file. + SizeOfImage = opt->SizeOfImage; + } + else { + // 32-bit PE file. + SizeOfImage = img->FileHeader->OptionalHeader.SizeOfImage; + } + wchar_t code_identifier[32]; + swprintf(code_identifier, + sizeof(code_identifier) / sizeof(code_identifier[0]), + L"%08X%X", TimeDateStamp, SizeOfImage); + info->code_identifier = code_identifier; + + return true; +} + +bool PrintPEFrameData(const wstring & pe_file, FILE * out_file) +{ + // Convert wchar to native charset because ImageLoad only takes + // a PSTR as input. + string img_file; + if (!WindowsStringUtils::safe_wcstombs(pe_file, &img_file)) { + fprintf(stderr, "Image path '%S' contains unrecognized characters.\n", + pe_file.c_str()); + return false; + } + + AutoImage img(ImageLoad((PSTR)img_file.c_str(), NULL)); + if (!img) { + fprintf(stderr, "Failed to load %s\n", img_file.c_str()); + return false; + } + PIMAGE_OPTIONAL_HEADER64 optional_header = + &(reinterpret_cast(img->FileHeader))->OptionalHeader; + if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + fprintf(stderr, "Not a PE32+ image\n"); + return false; + } + + // Read Exception Directory + DWORD exception_rva = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; + DWORD exception_size = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; + PIMAGE_RUNTIME_FUNCTION_ENTRY funcs = + static_cast( + ImageRvaToVa(img->FileHeader, + img->MappedAddress, + exception_rva, + &img->LastRvaSection)); + for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) { + DWORD unwind_rva = funcs[i].UnwindInfoAddress; + // handle chaining + while (unwind_rva & 0x1) { + unwind_rva ^= 0x1; + PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = + static_cast( + ImageRvaToVa(img->FileHeader, + img->MappedAddress, + unwind_rva, + &img->LastRvaSection)); + unwind_rva = chained_func->UnwindInfoAddress; + } + + UnwindInfo *unwind_info = static_cast( + ImageRvaToVa(img->FileHeader, + img->MappedAddress, + unwind_rva, + &img->LastRvaSection)); + + DWORD stack_size = 8; // minimal stack size is 8 for RIP + DWORD rip_offset = 8; + do { + for (UBYTE c = 0; c < unwind_info->count_of_codes; c++) { + UnwindCode *unwind_code = &unwind_info->unwind_code[c]; + switch (unwind_code->unwind_operation_code) { + case UWOP_PUSH_NONVOL: { + stack_size += 8; + break; + } + case UWOP_ALLOC_LARGE: { + if (unwind_code->operation_info == 0) { + c++; + if (c < unwind_info->count_of_codes) + stack_size += (unwind_code + 1)->frame_offset * 8; + } + else { + c += 2; + if (c < unwind_info->count_of_codes) + stack_size += (unwind_code + 1)->frame_offset | + ((unwind_code + 2)->frame_offset << 16); + } + break; + } + case UWOP_ALLOC_SMALL: { + stack_size += unwind_code->operation_info * 8 + 8; + break; + } + case UWOP_SET_FPREG: + case UWOP_SAVE_XMM: + case UWOP_SAVE_XMM_FAR: + break; + case UWOP_SAVE_NONVOL: + case UWOP_SAVE_XMM128: { + c++; // skip slot with offset + break; + } + case UWOP_SAVE_NONVOL_FAR: + case UWOP_SAVE_XMM128_FAR: { + c += 2; // skip 2 slots with offset + break; + } + case UWOP_PUSH_MACHFRAME: { + if (unwind_code->operation_info) { + stack_size += 88; + } + else { + stack_size += 80; + } + rip_offset += 80; + break; + } + } + } + if (unwind_info->flags & UNW_FLAG_CHAININFO) { + PIMAGE_RUNTIME_FUNCTION_ENTRY chained_func = + reinterpret_cast( + (unwind_info->unwind_code + + ((unwind_info->count_of_codes + 1) & ~1))); + + unwind_info = static_cast( + ImageRvaToVa(img->FileHeader, + img->MappedAddress, + chained_func->UnwindInfoAddress, + &img->LastRvaSection)); + } + else { + unwind_info = NULL; + } + } while (unwind_info); + fprintf(out_file, "STACK CFI INIT %lx %lx .cfa: $rsp .ra: .cfa %lu - ^\n", + funcs[i].BeginAddress, + funcs[i].EndAddress - funcs[i].BeginAddress, rip_offset); + fprintf(out_file, "STACK CFI %lx .cfa: $rsp %lu +\n", + funcs[i].BeginAddress, stack_size); + } + + return true; +} + +wstring GenerateDebugIdentifier(DWORD age, GUID signature) +{ + // Use the same format that the MS symbol server uses in filesystem + // hierarchies. + wchar_t age_string[9]; + swprintf(age_string, sizeof(age_string) / sizeof(age_string[0]), + L"%x", age); + + // remove when VC++7.1 is no longer supported + age_string[sizeof(age_string) / sizeof(age_string[0]) - 1] = L'\0'; + + wstring debug_identifier = GUIDString::GUIDToSymbolServerWString(&signature); + debug_identifier.append(age_string); + + return debug_identifier; +} + +wstring GenerateDebugIdentifier(DWORD age, DWORD signature) +{ + // Use the same format that the MS symbol server uses in filesystem + // hierarchies. + wchar_t identifier_string[17]; + swprintf(identifier_string, + sizeof(identifier_string) / sizeof(identifier_string[0]), + L"%08X%x", signature, age); + + // remove when VC++7.1 is no longer supported + identifier_string[sizeof(identifier_string) / + sizeof(identifier_string[0]) - 1] = L'\0'; + + return wstring(identifier_string); +} + +constexpr const wchar_t* FileHeaderMachineToCpuString(WORD machine) +{ + { + switch (machine) { + case IMAGE_FILE_MACHINE_I386: + { + return L"x86"; + } + case IMAGE_FILE_MACHINE_IA64: + case IMAGE_FILE_MACHINE_AMD64: + { + return L"x86_64"; + } + default: + { + return L"unknown"; + } + } + } +} + +} // namespace google_breakpad diff --git a/src/common/windows/pe_util.h b/src/common/windows/pe_util.h new file mode 100644 index 00000000..cea40a2e --- /dev/null +++ b/src/common/windows/pe_util.h @@ -0,0 +1,67 @@ +// Copyright (c) 2019, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef COMMON_WINDOWS_PE_UTIL_H_ +#define COMMON_WINDOWS_PE_UTIL_H_ + +#include + +#include "common/windows/module_info.h" + +namespace google_breakpad { + +using std::wstring; + +// Reads |pe_file| and populates |info|. Returns true on success. +// Only supports PE32+ format, ie. a 64bit PE file. +// Will fail if |pe_file| does not contain a valid CodeView record. +bool ReadModuleInfo(const wstring& pe_file, PDBModuleInfo* info); + +// Reads |pe_file| and populates |info|. Returns true on success. +bool ReadPEInfo(const wstring& pe_file, PEModuleInfo* info); + +// Reads |pe_file| and prints frame data (aka. unwind info) to |out_file|. +// Only supports PE32+ format, ie. a 64bit PE file. +bool PrintPEFrameData(const wstring& pe_file, FILE* out_file); + +// Combines a GUID |signature| and DWORD |age| to create a Breakpad debug +// identifier. +wstring GenerateDebugIdentifier(DWORD age, GUID signature); + +// Combines a DWORD |signature| and DWORD |age| to create a Breakpad debug +// identifier. +wstring GenerateDebugIdentifier(DWORD age, DWORD signature); + +// Converts |machine| enum value to the corresponding string used by Breakpad. +// The enum is IMAGE_FILE_MACHINE_*, contained in winnt.h. +constexpr const wchar_t* FileHeaderMachineToCpuString(WORD machine); + +} // namespace google_breakpad + +#endif // COMMON_WINDOWS_PE_UTIL_H_ diff --git a/src/tools/windows/dump_syms/dump_syms.cc b/src/tools/windows/dump_syms/dump_syms.cc index 8ea777a5..a61434be 100644 --- a/src/tools/windows/dump_syms/dump_syms.cc +++ b/src/tools/windows/dump_syms/dump_syms.cc @@ -36,27 +36,38 @@ #include #include "common/windows/pdb_source_line_writer.h" +#include "common/windows/pe_source_line_writer.h" -using std::wstring; +using std::wstring;; using google_breakpad::PDBSourceLineWriter; +using google_breakpad::PESourceLineWriter; +using std::unique_ptr; int wmain(int argc, wchar_t **argv) { - if (argc < 2) { - fprintf(stderr, "Usage: %ws \n", argv[0]); + bool success; + if (argc == 2) { + PDBSourceLineWriter pdb_writer; + if (!pdb_writer.Open(wstring(argv[1]), PDBSourceLineWriter::ANY_FILE)) { + fprintf(stderr, "Open failed.\n"); + return 1; + } + success = pdb_writer.WriteSymbols(stdout); + } else if (argc == 3 && wcscmp(argv[1], L"--pe") == 0) { + PESourceLineWriter pe_writer(argv[2]); + success = pe_writer.WriteSymbols(stdout); + } else { + fprintf(stderr, "Usage: %ws [--pe] \n", argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, "--pe:\tRead debugging information from PE file and do " + "not attempt to locate matching PDB file.\n" + "\tThis is only supported for PE32+ (64 bit) PE files.\n"); return 1; } - PDBSourceLineWriter writer; - if (!writer.Open(wstring(argv[1]), PDBSourceLineWriter::ANY_FILE)) { - fprintf(stderr, "Open failed\n"); + if (!success) { + fprintf(stderr, "WriteSymbols failed.\n"); return 1; } - if (!writer.WriteMap(stdout)) { - fprintf(stderr, "WriteMap failed\n"); - return 1; - } - - writer.Close(); return 0; } diff --git a/src/tools/windows/dump_syms/dump_syms_unittest.cc b/src/tools/windows/dump_syms/dump_syms_unittest.cc index 5ed512e6..766e5c09 100644 --- a/src/tools/windows/dump_syms/dump_syms_unittest.cc +++ b/src/tools/windows/dump_syms/dump_syms_unittest.cc @@ -55,11 +55,15 @@ const wchar_t* kRootNames[] = { // without source data. L"omap_stretched", // A PDB file with OMAP data for an image that has been basic block reordered. - L"omap_reorder_bbs", + L"omap_reorder_bbs", // A 64bit PDB file with no OMAP data. L"dump_syms_regtest64", }; +const wchar_t* kPEOnlyRootNames[] = { + L"pe_only_symbol_test", +}; + void TrimLastComponent(const std::wstring& path, std::wstring* trimmed, std::wstring* component) { @@ -177,29 +181,64 @@ class DumpSymsRegressionTest : public testing::TestWithParam { std::wstring testdata_dir; }; +class DumpSymsPEOnlyRegressionTest : public testing::TestWithParam { +public: + virtual void SetUp() { + std::wstring self_dir; + ASSERT_TRUE(GetSelfDirectory(&self_dir)); + dump_syms_exe = self_dir + L"\\dump_syms.exe"; + + TrimLastComponent(self_dir, &testdata_dir, NULL); + testdata_dir += L"\\testdata"; + } + + std::wstring dump_syms_exe; + std::wstring testdata_dir; +}; + } //namespace TEST_P(DumpSymsRegressionTest, EnsureDumpedSymbolsMatch) { - const wchar_t* root_name = GetParam(); - std::wstring root_path = testdata_dir + L"\\" + root_name; + const wchar_t* root_name = GetParam(); + std::wstring root_path = testdata_dir + L"\\" + root_name; - std::wstring sym_path = root_path + L".sym"; - std::string expected_symbols; - ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols)); + std::wstring sym_path = root_path + L".sym"; + std::string expected_symbols; + ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols)); - std::wstring pdb_path = root_path + L".pdb"; - std::wstring command_line = L"\"" + dump_syms_exe + L"\" \"" + - pdb_path + L"\""; - std::string symbols; - ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols)); + std::wstring pdb_path = root_path + L".pdb"; + std::wstring command_line = L"\"" + dump_syms_exe + L"\" \"" + + pdb_path + L"\""; + std::string symbols; + ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols)); - EXPECT_EQ(expected_symbols, symbols); + EXPECT_EQ(expected_symbols, symbols); } INSTANTIATE_TEST_CASE_P(DumpSyms, DumpSymsRegressionTest, - testing::ValuesIn(kRootNames)); + testing::ValuesIn(kRootNames)); + +TEST_P(DumpSymsPEOnlyRegressionTest, EnsurePEOnlyDumpedSymbolsMatch) { + const wchar_t* root_name = GetParam(); + std::wstring root_path = testdata_dir + L"\\" + root_name; + + std::wstring sym_path = root_path + L".sym"; + std::string expected_symbols; + ASSERT_NO_FATAL_FAILURE(GetFileContents(sym_path, &expected_symbols)); + + std::wstring dll_path = root_path + L".dll"; + std::wstring command_line = L"\"" + dump_syms_exe + L"\" --pe \"" + + dll_path + L"\""; + std::string symbols; + ASSERT_NO_FATAL_FAILURE(RunCommand(command_line, &symbols)); + + EXPECT_EQ(expected_symbols, symbols); +} + +INSTANTIATE_TEST_CASE_P(PEOnlyDumpSyms, DumpSymsPEOnlyRegressionTest, + testing::ValuesIn(kPEOnlyRootNames)); } // namespace dump_syms } // namespace windows -} // namespace tools +} // namespace tools diff --git a/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.dll b/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.dll new file mode 100644 index 0000000000000000000000000000000000000000..879af40b14c980d56332ba525d2a806edbe8851d GIT binary patch literal 51200 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