aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/windows/common_windows.gyp5
-rw-r--r--src/common/windows/module_info.h75
-rw-r--r--src/common/windows/pdb_source_line_writer.cc273
-rw-r--r--src/common/windows/pdb_source_line_writer.h48
-rw-r--r--src/common/windows/pe_source_line_writer.cpp77
-rw-r--r--src/common/windows/pe_source_line_writer.h69
-rw-r--r--src/common/windows/pe_util.cpp418
-rw-r--r--src/common/windows/pe_util.h67
-rw-r--r--src/tools/windows/dump_syms/dump_syms.cc35
-rw-r--r--src/tools/windows/dump_syms/dump_syms_unittest.cc67
-rw-r--r--src/tools/windows/dump_syms/testdata/pe_only_symbol_test.dllbin0 -> 51200 bytes
-rw-r--r--src/tools/windows/dump_syms/testdata/pe_only_symbol_test.sym214
12 files changed, 1020 insertions, 328 deletions
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 <string>
+
+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<PIMAGE_NT_HEADERS64>(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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
- 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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
- ImageRvaToVa(img->FileHeader,
- img->MappedAddress,
- unwind_rva,
- &img->LastRvaSection));
- unwind_rva = chained_func->UnwindInfoAddress;
- }
-
- UnwindInfo *unwind_info = static_cast<UnwindInfo *>(
- 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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
- (unwind_info->unwind_code +
- ((unwind_info->count_of_codes + 1) & ~1)));
-
- unwind_info = static_cast<UnwindInfo *>(
- 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<WORD>(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 <atlcomcli.h>
-#include <unordered_map>
#include <string>
+#include <unordered_map>
+#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 <string>
+
+#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 <windows.h>
+#include <winnt.h>
+#include <atlbase.h>
+#include <ImageHlp.h>
+
+#include <functional>
+
+#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<PIMAGE_NT_HEADERS64>(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<PIMAGE_DEBUG_DIRECTORY>(
+ 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<CV_INFO_PDB70*>(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<PIMAGE_NT_HEADERS64>(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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
+ 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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
+ ImageRvaToVa(img->FileHeader,
+ img->MappedAddress,
+ unwind_rva,
+ &img->LastRvaSection));
+ unwind_rva = chained_func->UnwindInfoAddress;
+ }
+
+ UnwindInfo *unwind_info = static_cast<UnwindInfo *>(
+ 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<PIMAGE_RUNTIME_FUNCTION_ENTRY>(
+ (unwind_info->unwind_code +
+ ((unwind_info->count_of_codes + 1) & ~1)));
+
+ unwind_info = static_cast<UnwindInfo *>(
+ 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 <windows.h>
+
+#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 <string>
#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 <file.[pdb|exe|dll]>\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] <file.[pdb|exe|dll]>\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<const wchar_t *> {
std::wstring testdata_dir;
};
+class DumpSymsPEOnlyRegressionTest : public testing::TestWithParam<const wchar_t *> {
+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 00000000..879af40b
--- /dev/null
+++ b/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.dll
Binary files differ
diff --git a/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.sym b/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.sym
new file mode 100644
index 00000000..24a3ddf4
--- /dev/null
+++ b/src/tools/windows/dump_syms/testdata/pe_only_symbol_test.sym
@@ -0,0 +1,214 @@
+MODULE windows x86_64 2A5EAB481FAB4A17A9761CDC14FE531A1 pe_only_symbol_test.dll
+INFO CODE_ID 5C8AD05F12000 pe_only_symbol_test.dll
+STACK CFI INIT 1440 39 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1440 .cfa: $rsp 32 +
+STACK CFI INIT 1490 7f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1490 .cfa: $rsp 128 +
+STACK CFI INIT 1520 41 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1520 .cfa: $rsp 48 +
+STACK CFI INIT 1570 35 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1570 .cfa: $rsp 48 +
+STACK CFI INIT 15b0 3a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 15b0 .cfa: $rsp 48 +
+STACK CFI INIT 1640 8 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1640 .cfa: $rsp 16 +
+STACK CFI INIT 1650 d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1650 .cfa: $rsp 16 +
+STACK CFI INIT 1660 b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1660 .cfa: $rsp 16 +
+STACK CFI INIT 1670 5a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1670 .cfa: $rsp 64 +
+STACK CFI INIT 16e0 97 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 16e0 .cfa: $rsp 112 +
+STACK CFI INIT 17c0 3f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 17c0 .cfa: $rsp 64 +
+STACK CFI INIT 1810 23 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1810 .cfa: $rsp 64 +
+STACK CFI INIT 1840 16 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1840 .cfa: $rsp 16 +
+STACK CFI INIT 1856 20 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1856 .cfa: $rsp 16 +
+STACK CFI INIT 1876 5 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1876 .cfa: $rsp 16 +
+STACK CFI INIT 1890 1b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1890 .cfa: $rsp 48 +
+STACK CFI INIT 18ab 56 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 18ab .cfa: $rsp 48 +
+STACK CFI INIT 1901 10 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1901 .cfa: $rsp 48 +
+STACK CFI INIT 1940 7 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1940 .cfa: $rsp 64 +
+STACK CFI INIT 1947 1a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1947 .cfa: $rsp 64 +
+STACK CFI INIT 1961 b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1961 .cfa: $rsp 64 +
+STACK CFI INIT 196c 4c .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 196c .cfa: $rsp 64 +
+STACK CFI INIT 19b8 5 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 19b8 .cfa: $rsp 64 +
+STACK CFI INIT 19bd 13 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 19bd .cfa: $rsp 64 +
+STACK CFI INIT 19d0 73 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 19d0 .cfa: $rsp 64 +
+STACK CFI INIT 1a90 3a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1a90 .cfa: $rsp 48 +
+STACK CFI INIT 1ae0 f8 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1ae0 .cfa: $rsp 96 +
+STACK CFI INIT 1c30 21 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1c30 .cfa: $rsp 8 +
+STACK CFI INIT 1c60 87 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1c60 .cfa: $rsp 64 +
+STACK CFI INIT 1d10 13a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1d10 .cfa: $rsp 80 +
+STACK CFI INIT 1ea0 88 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1ea0 .cfa: $rsp 64 +
+STACK CFI INIT 1f50 135 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 1f50 .cfa: $rsp 80 +
+STACK CFI INIT 20e0 4d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 20e0 .cfa: $rsp 64 +
+STACK CFI INIT 2140 2a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2140 .cfa: $rsp 48 +
+STACK CFI INIT 2180 36 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2180 .cfa: $rsp 48 +
+STACK CFI INIT 2290 36 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2290 .cfa: $rsp 96 +
+STACK CFI INIT 22e0 44 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 22e0 .cfa: $rsp 96 +
+STACK CFI INIT 2340 5f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2340 .cfa: $rsp 544 +
+STACK CFI INIT 239f d9 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 239f .cfa: $rsp 544 +
+STACK CFI INIT 2478 1d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2478 .cfa: $rsp 544 +
+STACK CFI INIT 2560 cf .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2560 .cfa: $rsp 1088 +
+STACK CFI INIT 2670 2d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2670 .cfa: $rsp 80 +
+STACK CFI INIT 269d 6b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 269d .cfa: $rsp 80 +
+STACK CFI INIT 2708 1a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2708 .cfa: $rsp 80 +
+STACK CFI INIT 2770 260 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2770 .cfa: $rsp 3824 +
+STACK CFI INIT 2a70 1f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2a70 .cfa: $rsp 48 +
+STACK CFI INIT 2aa0 c5 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2aa0 .cfa: $rsp 1088 +
+STACK CFI INIT 2ba0 64 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2ba0 .cfa: $rsp 64 +
+STACK CFI INIT 2c20 25 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2c20 .cfa: $rsp 64 +
+STACK CFI INIT 2c50 35 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2c50 .cfa: $rsp 48 +
+STACK CFI INIT 2ca0 d1 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2ca0 .cfa: $rsp 64 +
+STACK CFI INIT 2db0 13 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2db0 .cfa: $rsp 48 +
+STACK CFI INIT 2dd0 9b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2dd0 .cfa: $rsp 48 +
+STACK CFI INIT 2ea0 10e .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 2ea0 .cfa: $rsp 64 +
+STACK CFI INIT 3000 91 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3000 .cfa: $rsp 128 +
+STACK CFI INIT 30c0 b2 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 30c0 .cfa: $rsp 128 +
+STACK CFI INIT 31a0 be .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 31a0 .cfa: $rsp 80 +
+STACK CFI INIT 3290 74 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3290 .cfa: $rsp 64 +
+STACK CFI INIT 3330 16 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3330 .cfa: $rsp 48 +
+STACK CFI INIT 3350 15 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3350 .cfa: $rsp 48 +
+STACK CFI INIT 3380 45 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3380 .cfa: $rsp 64 +
+STACK CFI INIT 33e0 3b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 33e0 .cfa: $rsp 48 +
+STACK CFI INIT 3430 40 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3430 .cfa: $rsp 48 +
+STACK CFI INIT 34a0 15 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 34a0 .cfa: $rsp 48 +
+STACK CFI INIT 34c0 c6 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 34c0 .cfa: $rsp 64 +
+STACK CFI INIT 35c0 e .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 35c0 .cfa: $rsp 48 +
+STACK CFI INIT 35e0 8a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 35e0 .cfa: $rsp 48 +
+STACK CFI INIT 36a0 62 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 36a0 .cfa: $rsp 80 +
+STACK CFI INIT 3720 2d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3720 .cfa: $rsp 48 +
+STACK CFI INIT 3760 1d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3760 .cfa: $rsp 48 +
+STACK CFI INIT 3790 30 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3790 .cfa: $rsp 48 +
+STACK CFI INIT 37d0 15 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 37d0 .cfa: $rsp 48 +
+STACK CFI INIT 37f0 5b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 37f0 .cfa: $rsp 64 +
+STACK CFI INIT 3870 2e .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3870 .cfa: $rsp 48 +
+STACK CFI INIT 38b0 15 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 38b0 .cfa: $rsp 48 +
+STACK CFI INIT 38d0 49 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 38d0 .cfa: $rsp 48 +
+STACK CFI INIT 3930 10c .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3930 .cfa: $rsp 128 +
+STACK CFI INIT 3a80 8b .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3a80 .cfa: $rsp 96 +
+STACK CFI INIT 3b30 2f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3b30 .cfa: $rsp 48 +
+STACK CFI INIT 3b70 3f .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3b70 .cfa: $rsp 48 +
+STACK CFI INIT 3bc0 82 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3bc0 .cfa: $rsp 80 +
+STACK CFI INIT 3c70 50 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3c70 .cfa: $rsp 64 +
+STACK CFI INIT 3ce0 33 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3ce0 .cfa: $rsp 64 +
+STACK CFI INIT 3d50 191 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3d50 .cfa: $rsp 1536 +
+STACK CFI INIT 3f50 51 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3f50 .cfa: $rsp 176 +
+STACK CFI INIT 3fc0 e .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3fc0 .cfa: $rsp 48 +
+STACK CFI INIT 3ff0 a6 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 3ff0 .cfa: $rsp 64 +
+STACK CFI INIT 40c0 16 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 40c0 .cfa: $rsp 48 +
+STACK CFI INIT 40f0 72 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 40f0 .cfa: $rsp 64 +
+STACK CFI INIT 4180 42 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4180 .cfa: $rsp 48 +
+STACK CFI INIT 41e0 42 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 41e0 .cfa: $rsp 48 +
+STACK CFI INIT 4250 1e .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4250 .cfa: $rsp 32 +
+STACK CFI INIT 4280 18 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4280 .cfa: $rsp 48 +
+STACK CFI INIT 42a0 37 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 42a0 .cfa: $rsp 64 +
+STACK CFI INIT 4300 145 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4300 .cfa: $rsp 1120 +
+STACK CFI INIT 44a0 2ae .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 44a0 .cfa: $rsp 640 +
+STACK CFI INIT 4800 103 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4800 .cfa: $rsp 1664 +
+STACK CFI INIT 4950 3c5 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4950 .cfa: $rsp 240 +
+STACK CFI INIT 4e10 36d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 4e10 .cfa: $rsp 96 +
+STACK CFI INIT 5270 25 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 5270 .cfa: $rsp 32 +
+STACK CFI INIT 66c0 2 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 66c0 .cfa: $rsp 8 +
+STACK CFI INIT 76d0 1a .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 76d0 .cfa: $rsp 48 +
+STACK CFI INIT 76f0 20 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 76f0 .cfa: $rsp 48 +
+STACK CFI INIT 7720 48 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 7720 .cfa: $rsp 64 +
+STACK CFI INIT 7780 20 .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 7780 .cfa: $rsp 48 +
+STACK CFI INIT 77b0 3d .cfa: $rsp .ra: .cfa 8 - ^
+STACK CFI 77b0 .cfa: $rsp 48 +