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 <ivanpe@chromium.org>
This commit is contained in:
Nelson Billing 2019-06-12 14:19:57 -07:00
parent 548ca6e382
commit 87bc402210
12 changed files with 1020 additions and 328 deletions

View File

@ -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',

View File

@ -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_

View File

@ -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() {
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) {

View File

@ -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.

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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;
}

View File

@ -60,6 +60,10 @@ const wchar_t* kRootNames[] = {
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,6 +181,21 @@ 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) {
@ -199,6 +218,26 @@ TEST_P(DumpSymsRegressionTest, EnsureDumpedSymbolsMatch) {
INSTANTIATE_TEST_CASE_P(DumpSyms, DumpSymsRegressionTest,
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

Binary file not shown.

View File

@ -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 +