Bug 1333126: use win64 PE unwind metadata to improve client-side stack walking; r=gsvelto

MozReview-Commit-ID: GDARnPSemyu

--HG--
extra : rebase_source : 2b727cd57c5bddbc2959fa096dea102eeff7d41f
This commit is contained in:
Carl Corcoran 2017-08-06 08:45:58 +02:00
Родитель fb75aa4149
Коммит 03ac54b366
7 изменённых файлов: 635 добавлений и 58 удалений

Просмотреть файл

@ -0,0 +1,108 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MinidumpAnalyzerUtils_h
#define MinidumpAnalyzerUtils_h
#ifdef XP_WIN
#include <windows.h>
#endif
#include <vector>
#include <map>
#include <string>
#include <algorithm>
namespace CrashReporter {
struct MinidumpAnalyzerOptions {
std::string forceUseModule;
};
extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
#ifdef XP_WIN
inline std::wstring
UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr) {
wchar_t* buffer = nullptr;
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-1, nullptr, 0);
if (buffer_size == 0) {
if (aSuccess) {
*aSuccess = false;
}
return L"";
}
buffer = new wchar_t[buffer_size];
if (buffer == nullptr) {
if (aSuccess) {
*aSuccess = false;
}
return L"";
}
buffer[0] = 0;
MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-1, buffer, buffer_size);
std::wstring str = buffer;
delete [] buffer;
if (aSuccess) {
*aSuccess = true;
}
return str;
}
inline std::string
WideToMBCS(const std::wstring &inp) {
int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
nullptr, 0, NULL, NULL);
if (buffer_size == 0)
return "";
std::vector<char> buffer(buffer_size);
buffer[0] = 0;
WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
buffer.data(), buffer_size, NULL, NULL);
return buffer.data();
}
inline std::string UTF8toMBCS(const std::string &inp) {
std::wstring wide = UTF8ToWide(inp);
std::string ret = WideToMBCS(wide);
return ret;
}
#endif
// Check if a file exists at the specified path
inline bool
FileExists(const std::string& aPath)
{
#if defined(XP_WIN)
DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
return (attrs != INVALID_FILE_ATTRIBUTES);
#else // Non-Windows
struct stat sb;
int ret = stat(aPath.c_str(), &sb);
if (ret == -1 || !(sb.st_mode & S_IFREG)) {
return false;
}
return true;
#endif // XP_WIN
}
} // namespace
#endif // MinidumpAnalyzerUtils_h

Просмотреть файл

@ -0,0 +1,120 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if XP_WIN && HAVE_64BIT_BUILD
#include "MozStackFrameSymbolizer.h"
#include "MinidumpAnalyzerUtils.h"
#include "processor/cfi_frame_info.h"
#include <iostream>
#include <sstream>
#include <fstream>
namespace CrashReporter {
extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
using google_breakpad::CFIFrameInfo;
MozStackFrameSymbolizer::MozStackFrameSymbolizer() :
StackFrameSymbolizer(nullptr, nullptr)
{
}
MozStackFrameSymbolizer::SymbolizerResult
MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
const SystemInfo* system_info,
StackFrame* stack_frame)
{
SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
modules, system_info, stack_frame);
if (ret == kNoError && this->HasImplementation() &&
stack_frame->function_name.empty()) {
// Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
// address valid if it has associated symbols.
//
// This makes sense for complete & accurate symbols, but ours may be
// incomplete or wrong. Returning a function name tells Breakpad we
// recognize this address as code, so it's OK to use in stack scanning.
// This function is only called with addresses that land in this module.
//
// This allows us to fall back to stack scanning in the case where we were
// unable to provide CFI.
stack_frame->function_name = "<unknown code>";
}
return ret;
}
CFIFrameInfo*
MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame)
{
std::string modulePath;
// For unit testing, support loading a specified module instead of
// the real one.
bool moduleHasBeenReplaced = false;
if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
modulePath = gMinidumpAnalyzerOptions.forceUseModule;
moduleHasBeenReplaced = true;
} else {
if (!frame->module) {
return nullptr;
}
modulePath = frame->module->code_file();
}
// Get/create the unwind parser.
auto itMod = mModuleMap.find(modulePath);
std::shared_ptr<ModuleUnwindParser> unwindParser;
if (itMod != mModuleMap.end()) {
unwindParser = itMod->second;
} else {
unwindParser.reset(new ModuleUnwindParser(modulePath));
mModuleMap[modulePath] = unwindParser;
}
UnwindCFI cfi;
DWORD offsetAddr;
if (moduleHasBeenReplaced) {
// If we are replacing a module, addresses will never line up.
// So just act like the 1st entry is correct.
offsetAddr = unwindParser->GetAnyOffsetAddr();
} else {
offsetAddr = frame->instruction - frame->module->base_address();
}
if (!unwindParser->GetCFI(offsetAddr, cfi)) {
return nullptr;
}
std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
static const size_t exprSize = 50;
char expr[exprSize];
if (cfi.stackSize == 0) {
snprintf(expr, exprSize, "$rsp");
} else {
snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
}
rules->SetCFARule(expr);
if (cfi.ripOffset == 0) {
snprintf(expr, exprSize, ".cfa ^");
} else {
snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
}
rules->SetRARule(expr);
return rules.release();
}
} // namespace CrashReporter
#endif // XP_WIN && HAVE_64BIT_BUILD

Просмотреть файл

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MozStackFrameSymbolizer_h
#define MozStackFrameSymbolizer_h
#if XP_WIN && HAVE_64BIT_BUILD
#include "Win64ModuleUnwindMetadata.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "google_breakpad/processor/stack_frame.h"
#include <memory>
namespace CrashReporter {
using google_breakpad::CodeModule;
using google_breakpad::CodeModules;
using google_breakpad::SourceLineResolverInterface;
using google_breakpad::StackFrame;
using google_breakpad::StackFrameSymbolizer;
using google_breakpad::SymbolSupplier;
using google_breakpad::SystemInfo;
class MozStackFrameSymbolizer : public StackFrameSymbolizer {
using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
public:
MozStackFrameSymbolizer();
virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
const SystemInfo* system_info,
StackFrame* stack_frame);
virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
const StackFrame* frame);
};
} // namespace CrashReporter
#endif // XP_WIN && HAVE_64BIT_BUILD
#endif // MozStackFrameSymbolizer_h

Просмотреть файл

@ -0,0 +1,265 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if XP_WIN && HAVE_64BIT_BUILD
#include "Win64ModuleUnwindMetadata.h"
#include "MinidumpAnalyzerUtils.h"
#include <windows.h>
#include <winnt.h>
#include <ImageHlp.h>
#include <iostream>
#include <sstream>
#include <string>
namespace CrashReporter {
union UnwindCode {
struct {
uint8_t offset_in_prolog;
uint8_t unwind_operation_code : 4;
uint8_t operation_info : 4;
};
USHORT frame_offset;
};
enum UnwindOperationCodes {
UWOP_PUSH_NONVOL = 0, // info == register number
UWOP_ALLOC_LARGE = 1, // no info, alloc size in next 2 slots
UWOP_ALLOC_SMALL = 2, // info == size of allocation / 8 - 1
UWOP_SET_FPREG = 3, // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
UWOP_SAVE_NONVOL = 4, // info == register number, offset in next slot
UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
UWOP_SAVE_XMM = 6, // Version 1; undocumented
UWOP_EPILOG = 6, // Version 2; undocumented
UWOP_SAVE_XMM_FAR = 7, // Version 1; undocumented
UWOP_SPARE = 7, // Version 2; undocumented
UWOP_SAVE_XMM128 = 8, // info == XMM reg number, offset in next slot
UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
UWOP_PUSH_MACHFRAME = 10 // info == 0: no error-code, 1: error-code
};
struct UnwindInfo {
uint8_t version : 3;
uint8_t flags : 5;
uint8_t size_of_prolog;
uint8_t count_of_codes;
uint8_t frame_register : 4;
uint8_t frame_offset : 4;
UnwindCode unwind_code[1];
};
ModuleUnwindParser::~ModuleUnwindParser()
{
if (mImg) {
ImageUnload(mImg);
}
}
void*
ModuleUnwindParser::RvaToVa(ULONG aRva)
{
return ImageRvaToVa(
mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection);
}
ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
: mPath(aPath)
{
// Convert wchar to native charset because ImageLoad only takes
// a PSTR as input.
std::string code_file = UTF8toMBCS(aPath);
mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
if (!mImg || !mImg->FileHeader) {
return;
}
PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
return;
}
DWORD exception_rva = optional_header->
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
DWORD exception_size = optional_header->
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
if (!funcs) {
return;
}
for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
}
}
bool
ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
UnwindCFI& aRet)
{
DWORD unwind_rva = aFunc.UnwindInfoAddress;
// Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
// circular references.
std::set<DWORD> visited;
// Follow chained function entries
while (unwind_rva & 0x1) {
unwind_rva ^= 0x1;
if (visited.end() != visited.find(unwind_rva)) {
return false;
}
visited.insert(unwind_rva);
auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
if (!chained_func) {
return false;
}
unwind_rva = chained_func->UnwindInfoAddress;
}
visited.insert(unwind_rva);
auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
if (!unwind_info) {
return false;
}
DWORD stack_size = 8; // minimal stack size is 8 for RIP
DWORD rip_offset = 8;
do {
for (uint8_t 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:
// To correctly track RSP when it's been transferred to another
// register, we would need to emit CFI records for every unwind op.
// For simplicity, don't emit CFI records for this function as
// we know it will be incorrect after this point.
return false;
case UWOP_SAVE_NONVOL:
case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
case UWOP_SAVE_XMM128: {
c++; // skip slot with offset
break;
}
case UWOP_SAVE_NONVOL_FAR:
case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
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;
}
default: {
return false;
}
}
}
if (unwind_info->flags & UNW_FLAG_CHAININFO) {
auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(
(unwind_info->unwind_code +
((unwind_info->count_of_codes + 1) & ~1)));
if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
return false; // Circular reference
}
visited.insert(chained_func->UnwindInfoAddress);
unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
} else {
unwind_info = nullptr;
}
} while (unwind_info);
aRet.beginAddress = aFunc.BeginAddress;
aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
aRet.stackSize = stack_size;
aRet.ripOffset = rip_offset;
return true;
}
// For unit testing we sometimes need any address that's valid in this module.
// Just return the first address we know of.
DWORD
ModuleUnwindParser::GetAnyOffsetAddr() const {
if (mUnwindMap.size() < 1) {
return 0;
}
return mUnwindMap.begin()->first;
}
bool
ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet)
{
// Figure out the begin address of the requested address.
auto itUW = mUnwindMap.lower_bound(aAddress + 1);
if (itUW == mUnwindMap.begin()) {
return false; // address before this module.
}
--itUW;
// Ensure that the function entry is big enough to contain this address.
IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
if (aAddress > func.EndAddress) {
return false;
}
// Do we have CFI for this function already?
auto itCFI = mCFIMap.find(aAddress);
if (itCFI != mCFIMap.end()) {
aRet = itCFI->second;
return true;
}
// No, generate it.
if (!GenerateCFIForFunction(func, aRet)) {
return false;
}
mCFIMap[func.BeginAddress] = aRet;
return true;
}
} // namespace
#endif // XP_WIN && HAVE_64BIT_BUILD

Просмотреть файл

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef Win64ModuleUnwindMetadata_h
#define Win64ModuleUnwindMetadata_h
#if XP_WIN && HAVE_64BIT_BUILD
#include <functional>
#include <map>
#include <string>
#include <windows.h>
#include <winnt.h>
#include <ImageHlp.h>
namespace CrashReporter {
struct UnwindCFI
{
uint32_t beginAddress;
uint32_t size;
uint32_t stackSize;
uint32_t ripOffset;
};
// Does lazy-parsing of unwind info.
class ModuleUnwindParser {
PLOADED_IMAGE mImg;
std::string mPath;
// Maps begin address to exception record.
// Populated upon construction.
std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
// Maps begin address to CFI.
// Populated as needed.
std::map<DWORD, UnwindCFI> mCFIMap;
bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
UnwindCFI& aRet);
void* RvaToVa(ULONG aRva);
public:
explicit ModuleUnwindParser(const std::string& aPath);
~ModuleUnwindParser();
bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
DWORD GetAnyOffsetAddr() const;
};
} // namespace
#endif // XP_WIN && HAVE_64BIT_BUILD
#endif // Win64ModuleUnwindMetadata_h

Просмотреть файл

@ -6,6 +6,7 @@
#include <cstdio>
#include <fstream>
#include <string>
#include <cstring>
#include <sstream>
#include "json/json.h"
@ -31,6 +32,12 @@
#endif
#include "MinidumpAnalyzerUtils.h"
#if XP_WIN && HAVE_64BIT_BUILD
#include "MozStackFrameSymbolizer.h"
#endif
namespace CrashReporter {
using std::ios;
@ -54,44 +61,7 @@ using google_breakpad::ProcessResult;
using google_breakpad::ProcessState;
using google_breakpad::StackFrame;
#ifdef XP_WIN
static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
{
wchar_t* buffer = nullptr;
int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-1, nullptr, 0);
if (buffer_size == 0) {
if (aSuccess) {
*aSuccess = false;
}
return L"";
}
buffer = new wchar_t[buffer_size];
if (buffer == nullptr) {
if (aSuccess) {
*aSuccess = false;
}
return L"";
}
MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-1, buffer, buffer_size);
wstring str = buffer;
delete [] buffer;
if (aSuccess) {
*aSuccess = true;
}
return str;
}
#endif
MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
struct ModuleCompare {
bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
@ -189,6 +159,7 @@ ConvertStackToJSON(const ProcessState& aProcessState,
}
frameNode["trust"] = FrameTrust(frame->trust);
// The 'ip' field is equivalent to socorro's 'offset' field
frameNode["ip"] = ToHex(frame->instruction);
@ -319,9 +290,14 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
static bool
ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
#if XP_WIN && HAVE_64BIT_BUILD
MozStackFrameSymbolizer symbolizer;
MinidumpProcessor minidumpProcessor(&symbolizer, false);
#else
BasicSourceLineResolver resolver;
// We don't have a valid symbol resolver so we pass nullptr instead.
MinidumpProcessor minidumpProcessor(nullptr, &resolver);
#endif
// Process the minidump.
Minidump dump(aDumpFile);
@ -333,7 +309,6 @@ ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
ProcessState processState;
rv = minidumpProcessor.Process(&dump, &processState);
aRoot["status"] = ResultString(rv);
ConvertProcessStateToJSON(processState, aRoot);
return true;
@ -360,25 +335,6 @@ OpenAppend(const string& aFilename)
return file;
}
// Check if a file exists at the specified path
static bool
FileExists(const string& aPath)
{
#if defined(XP_WIN)
DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
return (attrs != INVALID_FILE_ATTRIBUTES);
#else // Non-Windows
struct stat sb;
int ret = stat(aPath.c_str(), &sb);
if (ret == -1 || !(sb.st_mode & S_IFREG)) {
return false;
}
return true;
#endif // XP_WIN
}
// Update the extra data file by adding the StackTraces field holding the
// JSON output of this program.
@ -427,6 +383,17 @@ int main(int argc, char** argv)
return 1;
}
// Process command-line arguments
for (int i = 2; i < argc; ++i) {
if (std::strcmp(argv[i], "--force-use-module") == 0) {
if ((++ i) < argc) {
gMinidumpAnalyzerOptions.forceUseModule = argv[i];
} else {
return 1; // The module name wasn't specified on the command line.
}
}
}
// Try processing the minidump
Json::Value root;
if (ProcessMinidump(root, dumpPath)) {

Просмотреть файл

@ -27,6 +27,18 @@ if CONFIG['OS_TARGET'] != 'Android':
if CONFIG['OS_TARGET'] == 'Darwin':
DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
UNIFIED_SOURCES += [
'MozStackFrameSymbolizer.cpp',
'Win64ModuleUnwindMetadata.cpp',
]
OS_LIBS += [
'Dbghelp',
'Imagehlp'
]
# Don't use the STL wrappers in the crashreporter clients; they don't
# link with -lmozalloc, and it really doesn't matter here anyway.
DISABLE_STL_WRAPPING = True