Bug 1236108 - Add support for exception-context annotations for content processes to the crash reporter. r=bsmedberg, a=lizzard

This patch redefines XP_PATH_MAX on Windows to be MAX_PATH + 1. I did this
because the longer definition would actually not work with most Windows APIs.
Some APIs can work with longer lengths if the path is prefixed with "\\?\", but
that is not guaranteed in general.

See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath

MozReview-Commit-ID: LhT5oM5hlkl
This commit is contained in:
Aaron Klotz 2016-03-07 12:48:58 -08:00
parent 567de8d622
commit f1bc95aa31
4 changed files with 442 additions and 85 deletions

View File

@ -5,6 +5,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsExceptionHandler.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDataHashtable.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/CrashReporterChild.h"
@ -121,8 +124,7 @@ typedef std::wstring xpstring;
#define CRASH_REPORTER_FILENAME "crashreporter.exe"
#define PATH_SEPARATOR "\\"
#define XP_PATH_SEPARATOR L"\\"
// sort of arbitrary, but MAX_PATH is kinda small
#define XP_PATH_MAX 4096
#define XP_PATH_MAX (MAX_PATH + 1)
// "<reporter path>" "<minidump path>"
#define CMDLINE_SIZE ((XP_PATH_MAX * 2) + 6)
#ifdef _USE_32BIT_TIME_T
@ -157,10 +159,17 @@ typedef std::string xpstring;
#endif
#endif // XP_WIN32
#if defined(__GNUC__)
#define MAYBE_UNUSED __attribute__((unused))
#else
#define MAYBE_UNUSED
#endif // defined(__GNUC__)
#ifndef XP_LINUX
static const XP_CHAR dumpFileExtension[] = XP_TEXT(".dmp");
#endif
static const XP_CHAR childCrashAnnotationBaseName[] = XP_TEXT("GeckoChildCrash");
static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra");
static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz");
@ -255,6 +264,11 @@ static bool isSafeToDump = false;
// OOP crash reporting
static CrashGenerationServer* crashServer; // chrome process has this
#if (defined(XP_MACOSX) || defined(XP_WIN)) && defined(MOZ_CONTENT_SANDBOX)
// This field is only valid in the chrome process, not content.
static xpstring* contentProcessTmpDir = nullptr;
#endif
# if defined(XP_WIN) || defined(XP_MACOSX)
// If crash reporting is disabled, we hand out this "null" pipe to the
// child process and don't attempt to connect to a parent server.
@ -486,12 +500,34 @@ CreateFileFromPath(const wchar_t* path, nsIFile** file)
{
CreateFileFromPath(std::wstring(path), file);
}
static xpstring*
CreatePathFromFile(nsIFile* file)
{
nsAutoString path;
nsresult rv = file->GetPath(path);
if (NS_FAILED(rv)) {
return nullptr;
}
return new xpstring(path.get(), path.Length());
}
#else
static void
CreateFileFromPath(const xpstring& path, nsIFile** file)
{
NS_NewNativeLocalFile(nsDependentCString(path.c_str()), false, file);
}
MAYBE_UNUSED static xpstring*
CreatePathFromFile(nsIFile* file)
{
nsAutoCString path;
nsresult rv = file->GetNativePath(path);
if (NS_FAILED(rv)) {
return nullptr;
}
return new xpstring(path.get(), path.Length());
}
#endif
static XP_CHAR*
@ -580,6 +616,7 @@ bool copy_file(const char* from, const char* to)
#endif
#ifdef XP_WIN
class PlatformWriter
{
public:
@ -698,6 +735,34 @@ WriteAnnotation(PlatformWriter& pw, const char (&name)[N],
WriteLiteral(pw, "\n");
};
/**
* If minidump_id is null, we assume that dump_path contains the full
* dump file path.
*/
static void
OpenAPIData(PlatformWriter& aWriter,
const XP_CHAR* dump_path, const XP_CHAR* minidump_id = nullptr
)
{
static XP_CHAR extraDataPath[XP_PATH_MAX];
int size = XP_PATH_MAX;
XP_CHAR* p;
if (minidump_id) {
p = Concat(extraDataPath, dump_path, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
p = Concat(p, minidump_id, &size);
} else {
p = Concat(extraDataPath, dump_path, &size);
// Skip back past the .dmp extension, if any.
if (*(p - 4) == XP_TEXT('.')) {
p -= 4;
size += 4;
}
}
Concat(p, extraFileExtension, &size);
aWriter.Open(extraDataPath);
}
bool MinidumpCallback(
#ifdef XP_LINUX
const MinidumpDescriptor& descriptor,
@ -726,19 +791,6 @@ bool MinidumpCallback(
Concat(minidumpPath, descriptor.path(), &size);
#endif
static XP_CHAR extraDataPath[XP_PATH_MAX];
size = XP_PATH_MAX;
#ifndef XP_LINUX
p = Concat(extraDataPath, dump_path, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
p = Concat(p, minidump_id, &size);
#else
p = Concat(extraDataPath, descriptor.path(), &size);
// Skip back past the .dmp extension.
p -= 4;
#endif
Concat(p, extraFileExtension, &size);
static XP_CHAR memoryReportLocalPath[XP_PATH_MAX];
size = XP_PATH_MAX;
#ifndef XP_LINUX
@ -860,7 +912,11 @@ bool MinidumpCallback(
if (!crashReporterAPIData->IsEmpty()) {
// write out API data
apiData.Open(extraDataPath);
#ifdef XP_LINUX
OpenAPIData(apiData, descriptor.path());
#else
OpenAPIData(apiData, dump_path, minidump_id);
#endif
apiData.WriteBuffer(crashReporterAPIData->get(), crashReporterAPIData->Length());
}
WriteAnnotation(apiData, "CrashTime", crashTimeString);
@ -1049,6 +1105,154 @@ bool MinidumpCallback(
return returnValue;
}
#if defined(XP_MACOSX) || defined(__ANDROID__)
static size_t
EnsureTrailingSlash(char* aBuf, size_t aBufLen)
{
size_t len = XP_STRLEN(aBuf);
if ((len + 2) < aBufLen && aBuf[len - 1] != '/') {
aBuf[len] = '/';
++len;
aBuf[len] = 0;
}
return len;
}
#endif
#if defined(XP_WIN32)
static size_t
BuildTempPath(wchar_t* aBuf, size_t aBufLen)
{
// first figure out buffer size
DWORD pathLen = GetTempPath(0, nullptr);
if (pathLen == 0 || pathLen >= aBufLen) {
return 0;
}
return GetTempPath(pathLen, aBuf);
}
#elif defined(XP_MACOSX)
static size_t
BuildTempPath(char* aBuf, size_t aBufLen)
{
if (aBufLen < PATH_MAX) {
return 0;
}
FSRef fsRef;
OSErr err = FSFindFolder(kUserDomain, kTemporaryFolderType,
kCreateFolder, &fsRef);
if (err != noErr) {
return 0;
}
OSStatus status = FSRefMakePath(&fsRef, (UInt8*)aBuf, PATH_MAX);
if (status != noErr) {
return 0;
}
return EnsureTrailingSlash(aBuf, aBufLen);
}
#elif defined(__ANDROID__)
static size_t
BuildTempPath(char* aBuf, size_t aBufLen)
{
// GeckoAppShell or Gonk's init.rc sets this in the environment
const char *tempenv = PR_GetEnv("TMPDIR");
if (!tempenv) {
return false;
}
int size = (int)aBufLen;
Concat(aBuf, tempenv, &size);
return EnsureTrailingSlash(aBuf, aBufLen);
}
#elif defined(XP_UNIX)
static size_t
BuildTempPath(char* aBuf, size_t aBufLen)
{
// we assume it's always /tmp on unix systems
NS_NAMED_LITERAL_CSTRING(tmpPath, "/tmp/");
int size = (int)aBufLen;
Concat(aBuf, tmpPath.get(), &size);
return tmpPath.Length();
}
#else
#error "Implement this for your platform"
#endif
template <typename CharT, size_t N>
static size_t
BuildTempPath(CharT (&aBuf)[N])
{
static_assert(N >= XP_PATH_MAX, "char array length is too small");
return BuildTempPath(&aBuf[0], N);
}
template <typename PathStringT>
static bool
BuildTempPath(PathStringT& aResult)
{
aResult.SetLength(XP_PATH_MAX);
size_t actualLen = BuildTempPath(aResult.BeginWriting(), XP_PATH_MAX);
if (!actualLen) {
return false;
}
aResult.SetLength(actualLen);
return true;
}
static void
PrepareChildExceptionTimeAnnotations()
{
MOZ_ASSERT(!XRE_IsParentProcess());
static XP_CHAR tempPath[XP_PATH_MAX] = {0};
// Get the temp path
size_t tempPathLen = BuildTempPath(tempPath);
if (!tempPathLen) {
return;
}
// Generate and append the file name
int size = XP_PATH_MAX - tempPathLen;
XP_CHAR* p = tempPath + tempPathLen;
p = Concat(p, childCrashAnnotationBaseName, &size);
XP_CHAR pidBuffer[32] = XP_TEXT("");
#if defined(XP_WIN32)
_ui64tow(GetCurrentProcessId(), pidBuffer, 10);
#else
XP_STOA(getpid(), pidBuffer, 10);
#endif
p = Concat(p, pidBuffer, &size);
// Now open the file...
PlatformWriter apiData;
OpenAPIData(apiData, tempPath);
// ...and write out any annotations. These should be escaped if necessary
// (but don't call EscapeAnnotation here, because it touches the heap).
char oomAllocationSizeBuffer[32] = "";
if (gOOMAllocationSize) {
XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer, 10);
}
if (oomAllocationSizeBuffer[0]) {
WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer);
}
if (gMozCrashReason) {
WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason);
}
}
#ifdef XP_WIN
static void
ReserveBreakpadVM()
@ -1099,6 +1303,18 @@ static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
FreeBreakpadVM();
return true;
}
static bool
ChildFPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion)
{
bool result = FPEFilter(context, exinfo, assertion);
if (result) {
PrepareChildExceptionTimeAnnotations();
}
return result;
}
#endif // XP_WIN
static bool ShouldReport()
@ -1118,13 +1334,22 @@ static bool ShouldReport()
return true;
}
namespace {
bool Filter(void* context) {
mozilla::IOInterposer::Disable();
return true;
}
static bool
Filter(void* context)
{
mozilla::IOInterposer::Disable();
return true;
}
static bool
ChildFilter(void* context)
{
bool result = Filter(context);
if (result) {
PrepareChildExceptionTimeAnnotations();
}
return result;
}
nsresult SetExceptionHandler(nsIFile* aXREDirectory,
bool force/*=false*/)
@ -1219,41 +1444,12 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
// get temp path to use for minidump path
#if defined(XP_WIN32)
nsString tempPath;
// first figure out buffer size
int pathLen = GetTempPath(0, nullptr);
if (pathLen == 0)
return NS_ERROR_FAILURE;
tempPath.SetLength(pathLen);
GetTempPath(pathLen, (LPWSTR)tempPath.BeginWriting());
#elif defined(XP_MACOSX)
nsCString tempPath;
FSRef fsRef;
OSErr err = FSFindFolder(kUserDomain, kTemporaryFolderType,
kCreateFolder, &fsRef);
if (err != noErr)
return NS_ERROR_FAILURE;
char path[PATH_MAX];
OSStatus status = FSRefMakePath(&fsRef, (UInt8*)path, PATH_MAX);
if (status != noErr)
return NS_ERROR_FAILURE;
tempPath = path;
#elif defined(__ANDROID__)
// GeckoAppShell or Gonk's init.rc sets this in the environment
const char *tempenv = PR_GetEnv("TMPDIR");
if (!tempenv)
return NS_ERROR_FAILURE;
nsCString tempPath(tempenv);
#elif defined(XP_UNIX)
// we assume it's always /tmp on unix systems
nsCString tempPath = NS_LITERAL_CSTRING("/tmp/");
#else
#error "Implement this for your platform"
nsCString tempPath;
#endif
if (!BuildTempPath(tempPath)) {
return NS_ERROR_FAILURE;
}
#ifdef XP_MACOSX
// Initialize spawn attributes, since this calls malloc.
@ -2677,24 +2873,164 @@ AppendExtraData(nsIFile* extraFile, const AnnotationTable& data)
return WriteExtraData(extraFile, data, Blacklist());
}
static bool
GetExtraFileForChildPid(nsIFile* aMinidump, uint32_t aPid, nsIFile** aExtraFile)
{
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIFile> extraFile;
nsresult rv;
#if defined(XP_WIN) || defined(XP_MACOSX)
# if defined(MOZ_CONTENT_SANDBOX)
if (!contentProcessTmpDir) {
return false;
}
CreateFileFromPath(*contentProcessTmpDir, getter_AddRefs(extraFile));
if (!extraFile) {
return false;
}
# else
rv = aMinidump->Clone(getter_AddRefs(extraFile));
if (NS_FAILED(rv)) {
return false;
}
# endif // defined(MOZ_CONTENT_SANDBOX)
#elif defined(XP_UNIX)
rv = NS_NewLocalFile(NS_LITERAL_STRING("/tmp"), false,
getter_AddRefs(extraFile));
if (NS_FAILED(rv)) {
return false;
}
#else
#error "Implement this for your platform"
#endif
nsAutoString leafName;
#if defined(XP_WIN)
leafName.AppendPrintf("%S%u%S", childCrashAnnotationBaseName, aPid,
extraFileExtension);
#else
leafName.AppendPrintf("%s%u%s", childCrashAnnotationBaseName, aPid,
extraFileExtension);
#endif
#if defined(XP_WIN) || defined(XP_MACOSX)
# if defined(MOZ_CONTENT_SANDBOX)
rv = extraFile->Append(leafName);
if (NS_FAILED(rv)) {
return false;
}
# else
rv = extraFile->SetLeafName(leafName);
if (NS_FAILED(rv)) {
return false;
}
# endif // defined(MOZ_CONTENT_SANDBOX)
#elif defined(XP_UNIX)
rv = extraFile->Append(leafName);
if (NS_FAILED(rv)) {
return false;
}
#else
#error "Implement this for your platform"
#endif
extraFile.forget(aExtraFile);
return true;
}
static bool
IsDataEscaped(char* aData)
{
if (strchr(aData, '\n')) {
// There should not be any newlines
return false;
}
char* pos = aData;
while ((pos = strchr(pos, '\\'))) {
if (*(pos + 1) != '\\') {
return false;
}
// Add 2 to account for the second pos
pos += 2;
}
return true;
}
static void
ReadAndValidateExceptionTimeAnnotations(FILE*& aFd,
AnnotationTable& aAnnotations)
{
char line[0x1000];
while (fgets(line, sizeof(line), aFd)) {
char* data = strchr(line, '=');
if (!data) {
// bad data? Abort!
break;
}
// Move past the '='
*data = 0;
++data;
size_t dataLen = strlen(data);
// Chop off any trailing newline
if (dataLen > 0 && data[dataLen - 1] == '\n') {
data[dataLen - 1] = 0;
--dataLen;
}
// There should not be any newlines in the key
if (strchr(line, '\n')) {
break;
}
// Data should have been escaped by the child
if (!IsDataEscaped(data)) {
break;
}
// Looks good, save the (line,data) pair
aAnnotations.Put(nsDependentCString(line),
nsDependentCString(data, dataLen));
}
}
/**
* NOTE: One side effect of this function is that it deletes the
* GeckoChildCrash<pid>.extra file if it exists, once processed.
*/
static bool
WriteExtraForMinidump(nsIFile* minidump,
uint32_t pid,
const Blacklist& blacklist,
nsIFile** extraFile)
{
nsCOMPtr<nsIFile> extra;
if (!GetExtraFileForMinidump(minidump, getter_AddRefs(extra)))
if (!GetExtraFileForMinidump(minidump, getter_AddRefs(extra))) {
return false;
}
if (!WriteExtraData(extra, *crashReporterAPIData_Hash,
blacklist,
true /*write crash time*/,
true /*truncate*/))
true /*truncate*/)) {
return false;
}
*extraFile = nullptr;
extra.swap(*extraFile);
nsCOMPtr<nsIFile> exceptionTimeExtra;
FILE* fd;
if (pid && GetExtraFileForChildPid(minidump, pid,
getter_AddRefs(exceptionTimeExtra)) &&
NS_SUCCEEDED(exceptionTimeExtra->OpenANSIFileDesc("r", &fd))) {
AnnotationTable exceptionTimeAnnotations;
ReadAndValidateExceptionTimeAnnotations(fd, exceptionTimeAnnotations);
fclose(fd);
if (!AppendExtraData(extra, exceptionTimeAnnotations)) {
return false;
}
}
if (exceptionTimeExtra) {
exceptionTimeExtra->Remove(false);
}
extra.forget(extraFile);
return true;
}
@ -2748,7 +3084,14 @@ OnChildProcessDumpRequested(void* aContext,
#endif
getter_AddRefs(minidump));
if (!WriteExtraForMinidump(minidump,
uint32_t pid =
#ifdef XP_MACOSX
aClientInfo.pid();
#else
aClientInfo->pid();
#endif
if (!WriteExtraForMinidump(minidump, pid,
Blacklist(kSubprocessBlacklist,
ArrayLength(kSubprocessBlacklist)),
getter_AddRefs(extraFile)))
@ -2758,12 +3101,6 @@ OnChildProcessDumpRequested(void* aContext,
MoveToPending(minidump, extraFile);
{
uint32_t pid =
#ifdef XP_MACOSX
aClientInfo.pid();
#else
aClientInfo->pid();
#endif
#ifdef MOZ_CRASHREPORTER_INJECTOR
bool runCallback;
@ -2791,13 +3128,6 @@ OOPInitialized()
return pidToMinidump != nullptr;
}
#ifdef XP_MACOSX
static bool ChildFilter(void *context) {
mozilla::IOInterposer::Disable();
return true;
}
#endif
void
OOPInit()
{
@ -2824,6 +3154,14 @@ OOPInit()
MOZ_ASSERT(gExceptionHandler != nullptr,
"attempt to initialize OOP crash reporter before in-process crashreporter!");
#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
nsCOMPtr<nsIFile> tmpDir;
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir));
if (NS_SUCCEEDED(rv)) {
contentProcessTmpDir = CreatePathFromFile(tmpDir);
}
#endif
#if defined(XP_WIN)
childCrashNotifyPipe =
PR_smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i",
@ -2862,7 +3200,7 @@ OOPInit()
crashServer = new CrashGenerationServer(
childCrashNotifyPipe,
ChildFilter,
nullptr,
nullptr,
OnChildProcessDumpRequested, nullptr,
nullptr, nullptr,
@ -3070,16 +3408,16 @@ SetRemoteExceptionHandler(const nsACString& crashPipe)
gExceptionHandler = new google_breakpad::
ExceptionHandler(L"",
FPEFilter,
ChildFPEFilter,
nullptr, // no minidump callback
nullptr, // no callback context
google_breakpad::ExceptionHandler::HANDLER_ALL,
MiniDumpNormal,
NS_ConvertASCIItoUTF16(crashPipe).get(),
nullptr);
#ifdef XP_WIN
gExceptionHandler->set_handle_debug_exceptions(true);
#endif
mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize);
// we either do remote or nothing, no fallback to regular crash reporting
return gExceptionHandler->IsOutOfProcess();
@ -3112,15 +3450,12 @@ SetRemoteExceptionHandler()
{
MOZ_ASSERT(!gExceptionHandler, "crash client already init'd");
#ifndef XP_LINUX
xpstring path = "";
#else
// MinidumpDescriptor requires a non-empty path.
google_breakpad::MinidumpDescriptor path(".");
#endif
gExceptionHandler = new google_breakpad::
ExceptionHandler(path,
nullptr, // no filter callback
ChildFilter,
nullptr, // no minidump callback
nullptr, // no callback context
true, // install signal handlers
@ -3133,6 +3468,8 @@ SetRemoteExceptionHandler()
delete gDelayedAnnotations;
}
mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize);
// we either do remote or nothing, no fallback to regular crash reporting
return gExceptionHandler->IsOutOfProcess();
}
@ -3151,12 +3488,14 @@ SetRemoteExceptionHandler(const nsACString& crashPipe)
gExceptionHandler = new google_breakpad::
ExceptionHandler("",
Filter,
ChildFilter,
nullptr, // no minidump callback
nullptr, // no callback context
true, // install signal handlers
crashPipe.BeginReading());
mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize);
// we either do remote or nothing, no fallback to regular crash reporting
return gExceptionHandler->IsOutOfProcess();
}
@ -3270,7 +3609,7 @@ PairedDumpCallbackExtra(
nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context);
nsCOMPtr<nsIFile> extra;
return WriteExtraForMinidump(minidump, Blacklist(), getter_AddRefs(extra));
return WriteExtraForMinidump(minidump, 0, Blacklist(), getter_AddRefs(extra));
}
ThreadId

View File

@ -7,12 +7,12 @@ function run_test()
return;
}
// Try crashing with a pure virtual call
// Try crashing with a runtime abort
do_content_crash(function() {
crashType = CrashTestUtils.CRASH_RUNTIMEABORT;
crashReporter.annotateCrashReport("TestKey", "TestValue");
crashReporter.appendAppNotesToCrashReport("!!!foo!!!");
},
},
function(mdump, extra) {
do_check_eq(extra.TestKey, "TestValue");
do_check_true('StartupTime' in extra);

View File

@ -0,0 +1,17 @@
load("../unit/head_crashreporter.js");
function run_test()
{
if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
dump("INFO | test_content_annotation.js | Can't test crashreporter in a non-libxul build.\n");
return;
}
// Try crashing with an OOM
do_content_crash(function() {
crashType = CrashTestUtils.CRASH_OOM;
},
function(mdump, extra) {
do_check_true('OOMAllocationSize' in extra);
});
}

View File

@ -4,3 +4,4 @@ tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_content_annotation.js]
[test_content_exception_time_annotation.js]