mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
Bug 1523276 - Implement PHC, a probabilistic heap checker. r=glandium,gsvelto
Differential Revision: https://phabricator.services.mozilla.com/D25021 --HG-- extra : rebase_source : 86e94499f746b18a596130341692c6a9992d4867
This commit is contained in:
parent
7c88ba1685
commit
38dbbfcffc
@ -45,3 +45,33 @@ def replace_malloc_static(build_project):
|
||||
|
||||
|
||||
set_config('MOZ_REPLACE_MALLOC_STATIC', replace_malloc_static)
|
||||
|
||||
# PHC (Probabilistic Heap Checker)
|
||||
# ==============================================================
|
||||
|
||||
# In general, it only makes sense for PHC to run on the platforms that have a
|
||||
# crash reporter. Currently it only runs on Linux, but not on 32-bit Linux
|
||||
# because stack tracing frequently crashes (for unclear reasons). In the
|
||||
# future, we want it to run on Win64 and Mac as well.
|
||||
@depends(milestone, target, replace_malloc_default, '--enable-replace-malloc',
|
||||
when='--enable-jemalloc')
|
||||
def phc_default(milestone, target, replace_malloc_default, replace_malloc):
|
||||
if not replace_malloc_default or \
|
||||
(replace_malloc.origin != 'default' and not replace_malloc):
|
||||
return False
|
||||
if not milestone.is_nightly:
|
||||
return False
|
||||
return (target.os == 'GNU' and target.kernel == 'Linux' and
|
||||
target.bitness == 64)
|
||||
|
||||
|
||||
option('--enable-phc', env='MOZ_PHC', default=phc_default,
|
||||
when='--enable-jemalloc',
|
||||
help='{Enable|Disable} PHC (Probabilistic Memory Checker). '
|
||||
'Also enables replace-malloc and frame pointers')
|
||||
imply_option('--enable-replace-malloc', True, when='--enable-phc')
|
||||
imply_option('--enable-frame-pointers', True, when='--enable-phc')
|
||||
|
||||
|
||||
set_config('MOZ_PHC', True, when='--enable-phc')
|
||||
|
||||
|
@ -20,6 +20,9 @@ if CONFIG['MOZ_REPLACE_MALLOC']:
|
||||
'replace_malloc_bridge.h',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DEFINES['MOZ_PHC'] = True
|
||||
|
||||
if CONFIG['MOZ_MEMORY']:
|
||||
UNIFIED_SOURCES += [
|
||||
'mozjemalloc.cpp',
|
||||
|
@ -4511,6 +4511,8 @@ static void replace_malloc_init_funcs(malloc_table_t*);
|
||||
extern "C" void logalloc_init(malloc_table_t*, ReplaceMallocBridge**);
|
||||
|
||||
extern "C" void dmd_init(malloc_table_t*, ReplaceMallocBridge**);
|
||||
|
||||
extern "C" void phc_init(malloc_table_t*, ReplaceMallocBridge**);
|
||||
# endif
|
||||
|
||||
bool Equals(const malloc_table_t& aTable1, const malloc_table_t& aTable2) {
|
||||
@ -4549,6 +4551,11 @@ static void init() {
|
||||
dmd_init(&tempTable, &gReplaceMallocBridge);
|
||||
}
|
||||
# endif
|
||||
# ifdef MOZ_PHC
|
||||
if (Equals(tempTable, gDefaultMallocTable)) {
|
||||
phc_init(&tempTable, &gReplaceMallocBridge);
|
||||
}
|
||||
# endif
|
||||
# endif
|
||||
if (!Equals(tempTable, gDefaultMallocTable)) {
|
||||
replace_malloc_init_funcs(&tempTable);
|
||||
|
@ -115,6 +115,10 @@ namespace dmd {
|
||||
struct DMDFuncs;
|
||||
} // namespace dmd
|
||||
|
||||
namespace phc {
|
||||
class AddrInfo;
|
||||
} // namespace phc
|
||||
|
||||
// Callbacks to register debug file handles for Poison IO interpose.
|
||||
// See Mozilla(|Un)RegisterDebugHandle in xpcom/build/PoisonIOInterposer.h
|
||||
struct DebugFdRegistry {
|
||||
@ -126,7 +130,7 @@ struct DebugFdRegistry {
|
||||
} // namespace mozilla
|
||||
|
||||
struct ReplaceMallocBridge {
|
||||
ReplaceMallocBridge() : mVersion(3) {}
|
||||
ReplaceMallocBridge() : mVersion(4) {}
|
||||
|
||||
// This method was added in version 1 of the bridge.
|
||||
virtual mozilla::dmd::DMDFuncs* GetDMDFuncs() { return nullptr; }
|
||||
@ -156,6 +160,28 @@ struct ReplaceMallocBridge {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If this is a PHC-handled address, return true, and if an AddrInfo is
|
||||
// provided, fill in all of its fields. Otherwise, return false and leave
|
||||
// AddrInfo unchanged.
|
||||
// This method was added in version 4 of the bridge.
|
||||
virtual bool IsPHCAllocation(const void*, mozilla::phc::AddrInfo*) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable PHC allocations on the current thread. Only useful for tests. Note
|
||||
// that PHC deallocations will still occur as needed.
|
||||
// This method was added in version 4 of the bridge.
|
||||
virtual void DisablePHCOnCurrentThread() {}
|
||||
|
||||
// Re-enable PHC allocations on the current thread. Only useful for tests.
|
||||
// This method was added in version 4 of the bridge.
|
||||
virtual void ReenablePHCOnCurrentThread() {}
|
||||
|
||||
// Test whether PHC allocations are enabled on the current thread. Only
|
||||
// useful for tests.
|
||||
// This method was added in version 4 of the bridge.
|
||||
virtual bool IsPHCEnabledOnCurrentThread() { return false; }
|
||||
|
||||
# ifndef REPLACE_MALLOC_IMPL
|
||||
// Returns the replace-malloc bridge if its version is at least the
|
||||
// requested one.
|
||||
@ -199,6 +225,30 @@ struct ReplaceMalloc {
|
||||
return singleton ? singleton->RegisterHook(aName, aTable, aHookTable)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
static bool IsPHCAllocation(const void* aPtr, mozilla::phc::AddrInfo* aOut) {
|
||||
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
|
||||
return singleton ? singleton->IsPHCAllocation(aPtr, aOut) : false;
|
||||
}
|
||||
|
||||
static void DisablePHCOnCurrentThread() {
|
||||
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
|
||||
if (singleton) {
|
||||
singleton->DisablePHCOnCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
static void ReenablePHCOnCurrentThread() {
|
||||
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
|
||||
if (singleton) {
|
||||
singleton->ReenablePHCOnCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsPHCEnabledOnCurrentThread() {
|
||||
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
|
||||
return singleton ? singleton->IsPHCEnabledOnCurrentThread() : false;
|
||||
}
|
||||
};
|
||||
# endif
|
||||
|
||||
|
@ -16,6 +16,10 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
# include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG) && !defined(XP_WIN) && !defined(ANDROID)
|
||||
# define HAS_GDB_SLEEP_DURATION 1
|
||||
extern unsigned int _gdb_sleep_duration;
|
||||
@ -47,6 +51,21 @@ static void DisableCrashReporter() {
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class AutoDisablePHCOnCurrentThread {
|
||||
public:
|
||||
AutoDisablePHCOnCurrentThread() {
|
||||
#ifdef MOZ_PHC
|
||||
ReplaceMalloc::DisablePHCOnCurrentThread();
|
||||
#endif
|
||||
}
|
||||
|
||||
~AutoDisablePHCOnCurrentThread() {
|
||||
#ifdef MOZ_PHC
|
||||
ReplaceMalloc::ReenablePHCOnCurrentThread();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
static inline void TestOne(size_t size) {
|
||||
size_t req = size;
|
||||
size_t adv = malloc_good_size(req);
|
||||
@ -378,6 +397,12 @@ static bool IsSameRoundedHugeClass(size_t aSize1, size_t aSize2,
|
||||
|
||||
static bool CanReallocInPlace(size_t aFromSize, size_t aToSize,
|
||||
jemalloc_stats_t& aStats) {
|
||||
// PHC allocations must be disabled because PHC reallocs differently to
|
||||
// mozjemalloc.
|
||||
#ifdef MOZ_PHC
|
||||
MOZ_RELEASE_ASSERT(!ReplaceMalloc::IsPHCEnabledOnCurrentThread());
|
||||
#endif
|
||||
|
||||
if (aFromSize == malloc_good_size(aToSize)) {
|
||||
// Same size class: in-place.
|
||||
return true;
|
||||
@ -397,6 +422,10 @@ static bool CanReallocInPlace(size_t aFromSize, size_t aToSize,
|
||||
|
||||
TEST(Jemalloc, InPlace)
|
||||
{
|
||||
// Disable PHC allocations for this test, because CanReallocInPlace() isn't
|
||||
// valid for PHC allocations.
|
||||
AutoDisablePHCOnCurrentThread disable;
|
||||
|
||||
jemalloc_stats_t stats;
|
||||
jemalloc_stats(&stats);
|
||||
|
||||
@ -430,6 +459,10 @@ TEST(Jemalloc, InPlace)
|
||||
#if !defined(XP_WIN) || !defined(MOZ_CODE_COVERAGE)
|
||||
TEST(Jemalloc, JunkPoison)
|
||||
{
|
||||
// Disable PHC allocations for this test, because CanReallocInPlace() isn't
|
||||
// valid for PHC allocations, and the testing UAFs aren't valid.
|
||||
AutoDisablePHCOnCurrentThread disable;
|
||||
|
||||
jemalloc_stats_t stats;
|
||||
jemalloc_stats(&stats);
|
||||
|
||||
@ -631,6 +664,10 @@ TEST(Jemalloc, JunkPoison)
|
||||
|
||||
TEST(Jemalloc, GuardRegion)
|
||||
{
|
||||
// Disable PHC allocations for this test, because even a single PHC
|
||||
// allocation occurring can throw it off.
|
||||
AutoDisablePHCOnCurrentThread disable;
|
||||
|
||||
jemalloc_stats_t stats;
|
||||
jemalloc_stats(&stats);
|
||||
|
||||
|
@ -10,6 +10,9 @@ if CONFIG['OS_TARGET'] != 'Android' and not(CONFIG['OS_TARGET'] == 'WINNT' and C
|
||||
'TestJemalloc.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DEFINES['MOZ_PHC'] = True
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
@ -15,12 +15,17 @@ SOURCES += [
|
||||
'Replay.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_REPLACE_MALLOC_STATIC'] and CONFIG['MOZ_DMD']:
|
||||
if CONFIG['MOZ_REPLACE_MALLOC_STATIC'] and \
|
||||
(CONFIG['MOZ_DMD'] or CONFIG['MOZ_PHC']):
|
||||
UNIFIED_SOURCES += [
|
||||
'/mfbt/HashFunctions.cpp',
|
||||
'/mfbt/JSONWriter.cpp',
|
||||
'/mozglue/misc/StackWalk.cpp',
|
||||
]
|
||||
if CONFIG['MOZ_BUILD_APP'] == 'memory':
|
||||
EXPORTS.mozilla += [
|
||||
'/mozglue/misc/StackWalk.h',
|
||||
]
|
||||
|
||||
if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
|
||||
SOURCES += [
|
||||
|
@ -18,3 +18,6 @@ DIRS += [
|
||||
|
||||
if CONFIG['MOZ_DMD']:
|
||||
DIRS += ['dmd']
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DIRS += ['phc']
|
||||
|
1352
memory/replace/phc/PHC.cpp
Normal file
1352
memory/replace/phc/PHC.cpp
Normal file
File diff suppressed because it is too large
Load Diff
88
memory/replace/phc/PHC.h
Normal file
88
memory/replace/phc/PHC.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 PHC_h
|
||||
#define PHC_h
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace phc {
|
||||
|
||||
// Note: a more compact stack trace representation could be achieved with
|
||||
// some effort.
|
||||
struct StackTrace {
|
||||
public:
|
||||
static const size_t kMaxFrames = 16;
|
||||
|
||||
// The number of PCs in the stack trace.
|
||||
size_t mLength;
|
||||
|
||||
// The PCs in the stack trace. Only the first mLength are initialized.
|
||||
const void* mPcs[kMaxFrames];
|
||||
|
||||
public:
|
||||
StackTrace() : mLength(0) {}
|
||||
};
|
||||
|
||||
// Info from PHC about an address in memory.
|
||||
class AddrInfo {
|
||||
public:
|
||||
enum class Kind {
|
||||
// The address is not in PHC-managed memory.
|
||||
Unknown = 0,
|
||||
|
||||
// The address is within a PHC page that has never been allocated. A crash
|
||||
// involving such an address is unlikely in practice, because it would
|
||||
// require the crash to happen quite early.
|
||||
NeverAllocatedPage = 1,
|
||||
|
||||
// The address is within a PHC page that is in use.
|
||||
InUsePage = 2,
|
||||
|
||||
// The address is within a PHC page that has been allocated and then freed.
|
||||
// A crash involving such an address most likely indicates a
|
||||
// use-after-free. (A sufficiently wild write -- e.g. a large buffer
|
||||
// overflow -- could also trigger it, but this is less likely.)
|
||||
FreedPage = 3,
|
||||
|
||||
// The address is within a PHC guard page. A crash involving such an
|
||||
// address most likely indicates a buffer overflow. (Again, a sufficiently
|
||||
// wild write could unluckily trigger it, but this is less likely.)
|
||||
//
|
||||
// NOTE: guard pages are not yet implemented. This value is present so they
|
||||
// can be added easily in the future.
|
||||
GuardPage = 4,
|
||||
};
|
||||
|
||||
Kind mKind;
|
||||
|
||||
// The base address of the containing PHC allocation, if there is one.
|
||||
const void* mBaseAddr;
|
||||
|
||||
// The usable size of the containing PHC allocation, if there is one.
|
||||
size_t mUsableSize;
|
||||
|
||||
// The allocation and free stack traces of the containing PHC allocation, if
|
||||
// there is one.
|
||||
StackTrace mAllocStack;
|
||||
StackTrace mFreeStack;
|
||||
|
||||
// Default to no PHC info.
|
||||
AddrInfo()
|
||||
: mKind(Kind::Unknown),
|
||||
mBaseAddr(nullptr),
|
||||
mUsableSize(0),
|
||||
mAllocStack(),
|
||||
mFreeStack() {}
|
||||
};
|
||||
|
||||
} // namespace phc
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* PHC_h */
|
32
memory/replace/phc/moz.build
Normal file
32
memory/replace/phc/moz.build
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
ReplaceMalloc('phc')
|
||||
|
||||
DEFINES['MOZ_NO_MOZALLOC'] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'../logalloc',
|
||||
'/memory/build',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'PHC.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'PHC.cpp',
|
||||
]
|
||||
|
||||
if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
|
||||
SOURCES += [
|
||||
'../logalloc/FdPrintf.cpp',
|
||||
'/mozglue/misc/StackWalk.cpp',
|
||||
]
|
||||
|
||||
TEST_DIRS += ['test']
|
||||
|
||||
DisableStlWrapping()
|
155
memory/replace/phc/test/gtest/TestPHC.cpp
Normal file
155
memory/replace/phc/test/gtest/TestPHC.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
/* 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/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozmemory.h"
|
||||
#include "replace_malloc_bridge.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "../../PHC.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
bool PHCInfoEq(phc::AddrInfo& aInfo, phc::AddrInfo::Kind aKind, void* aBaseAddr,
|
||||
size_t aUsableSize, bool aHasAllocStack, bool aHasFreeStack) {
|
||||
return aInfo.mKind == aKind && aInfo.mBaseAddr == aBaseAddr &&
|
||||
aInfo.mUsableSize == aUsableSize &&
|
||||
// Proper stack traces will have at least 3 elements.
|
||||
(aHasAllocStack ? (aInfo.mAllocStack.mLength > 2)
|
||||
: (aInfo.mAllocStack.mLength == 0)) &&
|
||||
(aHasFreeStack ? (aInfo.mFreeStack.mLength > 2)
|
||||
: (aInfo.mFreeStack.mLength == 0));
|
||||
}
|
||||
|
||||
bool JeInfoEq(jemalloc_ptr_info_t& aInfo, PtrInfoTag aTag, void* aAddr,
|
||||
size_t aSize, arena_id_t arenaId) {
|
||||
return aInfo.tag == aTag && aInfo.addr == aAddr && aInfo.size == aSize
|
||||
#ifdef MOZ_DEBUG
|
||||
&& aInfo.arenaId == arenaId
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
char* GetPHCAllocation(size_t aSize) {
|
||||
// A crude but effective way to get a PHC allocation.
|
||||
for (int i = 0; i < 2000000; i++) {
|
||||
char* p = (char*)malloc(aSize);
|
||||
if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
|
||||
return p;
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST(PHC, TestPHCBasics)
|
||||
{
|
||||
int stackVar;
|
||||
phc::AddrInfo phcInfo;
|
||||
jemalloc_ptr_info_t jeInfo;
|
||||
|
||||
// Test a default AddrInfo.
|
||||
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0ul,
|
||||
false, false));
|
||||
|
||||
// Test some non-PHC allocation addresses.
|
||||
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(nullptr, &phcInfo));
|
||||
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
|
||||
false, false));
|
||||
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(&stackVar, &phcInfo));
|
||||
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
|
||||
false, false));
|
||||
|
||||
char* p = GetPHCAllocation(32);
|
||||
if (!p) {
|
||||
MOZ_CRASH("failed to get a PHC allocation");
|
||||
}
|
||||
|
||||
// Test an in-use PHC allocation, via its base address.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
|
||||
ASSERT_EQ(malloc_usable_size(p), 32ul);
|
||||
jemalloc_ptr_info(p, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
|
||||
|
||||
// Test an in-use PHC allocation, via an address in its middle.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
|
||||
ASSERT_EQ(malloc_usable_size(p), 32ul);
|
||||
jemalloc_ptr_info(p + 10, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
|
||||
|
||||
// Test an in-use PHC allocation, via an address past its end. The results
|
||||
// for phcInfo should be the same, but be different for jeInfo.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 64, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
|
||||
jemalloc_ptr_info(p + 64, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
|
||||
|
||||
free(p);
|
||||
|
||||
// Test a freed PHC allocation, via its base address.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
|
||||
jemalloc_ptr_info(p, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
|
||||
|
||||
// Test a freed PHC allocation, via an address in its middle.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
|
||||
jemalloc_ptr_info(p + 10, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
|
||||
|
||||
// Test a freed PHC allocation, via an address past its end.
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
|
||||
ASSERT_TRUE(
|
||||
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
|
||||
jemalloc_ptr_info(p + 64, &jeInfo);
|
||||
ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
|
||||
|
||||
// There are no tests for `mKind == NeverAllocatedPage` because it's not
|
||||
// possible to reliably get ahold of such a page.
|
||||
|
||||
// There are not tests for `mKind == GuardPage` because it's currently not
|
||||
// implemented.
|
||||
}
|
||||
|
||||
TEST(PHC, TestPHCDisabling)
|
||||
{
|
||||
char* p = GetPHCAllocation(32);
|
||||
char* q = GetPHCAllocation(32);
|
||||
if (!p || !q) {
|
||||
MOZ_CRASH("failed to get a PHC allocation");
|
||||
}
|
||||
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
|
||||
ReplaceMalloc::DisablePHCOnCurrentThread();
|
||||
ASSERT_FALSE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
|
||||
|
||||
// Test realloc() on a PHC allocation while PHC is disabled on the thread.
|
||||
char* p2 = (char*)realloc(p, 128);
|
||||
// The small realloc is in-place.
|
||||
ASSERT_TRUE(p2 == p);
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p2, nullptr));
|
||||
char* p3 = (char*)realloc(p2, 8192);
|
||||
// The big realloc is not in-place, and the result is not a PHC allocation.
|
||||
ASSERT_TRUE(p3 != p2);
|
||||
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(p3, nullptr));
|
||||
free(p3);
|
||||
|
||||
// Test free() on a PHC allocation while PHC is disabled on the thread.
|
||||
free(q);
|
||||
|
||||
// These must not be PHC allocations.
|
||||
char* r = GetPHCAllocation(32); // This will fail.
|
||||
ASSERT_FALSE(!!r);
|
||||
|
||||
ReplaceMalloc::ReenablePHCOnCurrentThread();
|
||||
ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
|
||||
}
|
15
memory/replace/phc/test/gtest/moz.build
Normal file
15
memory/replace/phc/test/gtest/moz.build
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestPHC.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'../../',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
9
memory/replace/phc/test/moz.build
Normal file
9
memory/replace/phc/test/moz.build
Normal file
@ -0,0 +1,9 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
# The gtests won't work in a SpiderMonkey-only build.
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT']:
|
||||
TEST_DIRS += ['gtest']
|
@ -350,6 +350,40 @@ GraphicsStartupTest:
|
||||
Set to 1 by the graphics driver crash guard when it's activated.
|
||||
type: boolean
|
||||
|
||||
PHCKind:
|
||||
description: >
|
||||
The allocation kind, if the crash involved a bad access of a special PHC
|
||||
allocation.
|
||||
type: string
|
||||
|
||||
PHCBaseAddress:
|
||||
description: >
|
||||
The allocation's base address, if the crash involved a bad access of a
|
||||
special PHC allocation. Encoded as a decimal address.
|
||||
type: string
|
||||
|
||||
PHCUsableSize:
|
||||
description: >
|
||||
The allocation's usable size, if the crash involved a bad access of a
|
||||
special PHC allocation.
|
||||
# A 32-bit integer is enough because the maximum usable size of a special PHC
|
||||
# allocation is far less than 2 GiB.
|
||||
type: integer
|
||||
|
||||
PHCAllocStack:
|
||||
description: >
|
||||
The allocation's allocation stack trace, if the crash involved a bad access
|
||||
of a special PHC allocation. Encoded as a comma-separated list of decimal
|
||||
addresses.
|
||||
type: string
|
||||
|
||||
PHCFreeStack:
|
||||
description: >
|
||||
The allocation's free stack trace, if the crash involved a bad access
|
||||
of a special PHC allocation. Encoded as a comma-separated list of decimal
|
||||
addresses.
|
||||
type: string
|
||||
|
||||
HangMonitorDescription:
|
||||
description: >
|
||||
Name of the hang monitor that generated the crash.
|
||||
|
@ -98,6 +98,10 @@
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
#include "prenv.h"
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
#include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "linux/sched.h"
|
||||
#endif
|
||||
@ -446,10 +450,24 @@ int ExceptionHandler::ThreadEntry(void *arg) {
|
||||
thread_arg->context_size) == false;
|
||||
}
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
void GetPHCAddrInfo(siginfo_t* siginfo, mozilla::phc::AddrInfo* addr_info) {
|
||||
// Is this a crash involving a PHC allocation?
|
||||
if (siginfo->si_signo == SIGSEGV || siginfo->si_signo == SIGBUS) {
|
||||
ReplaceMalloc::IsPHCAllocation(siginfo->si_addr, addr_info);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// This function runs in a compromised context: see the top of the file.
|
||||
// Runs on the crashing thread.
|
||||
bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
mozilla::phc::AddrInfo addr_info;
|
||||
#ifdef MOZ_PHC
|
||||
GetPHCAddrInfo(info, &addr_info);
|
||||
#endif
|
||||
|
||||
if (filter_ && !filter_(callback_context_, &addr_info))
|
||||
return false;
|
||||
|
||||
// Allow ourselves to be dumped if the signal is trusted.
|
||||
@ -489,7 +507,8 @@ bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return GenerateDump(&g_crash_context_);
|
||||
|
||||
return GenerateDump(&g_crash_context_, &addr_info);
|
||||
}
|
||||
|
||||
// This is a public interface to HandleSignal that allows the client to
|
||||
@ -506,7 +525,8 @@ bool ExceptionHandler::SimulateSignalDelivery(int sig) {
|
||||
}
|
||||
|
||||
// This function may run in a compromised context: see the top of the file.
|
||||
bool ExceptionHandler::GenerateDump(CrashContext *context) {
|
||||
bool ExceptionHandler::GenerateDump(
|
||||
CrashContext *context, const mozilla::phc::AddrInfo* addr_info) {
|
||||
if (IsOutOfProcess())
|
||||
return crash_generation_client_->RequestDump(context, sizeof(*context));
|
||||
|
||||
@ -591,7 +611,8 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
|
||||
|
||||
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
if (callback_)
|
||||
success = callback_(minidump_descriptor_, callback_context_, success);
|
||||
success =
|
||||
callback_(minidump_descriptor_, callback_context_, addr_info, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -765,7 +786,8 @@ bool ExceptionHandler::WriteMinidump() {
|
||||
#error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
return GenerateDump(&context);
|
||||
// nullptr here for phc::AddrInfo* is ok because this is not a crash.
|
||||
return GenerateDump(&context, nullptr);
|
||||
}
|
||||
|
||||
void ExceptionHandler::AddMappingInfo(const string& name,
|
||||
@ -822,7 +844,9 @@ bool ExceptionHandler::WriteMinidumpForChild(pid_t child,
|
||||
child_blamed_thread))
|
||||
return false;
|
||||
|
||||
return callback ? callback(descriptor, callback_context, true) : true;
|
||||
// nullptr here for phc::AddrInfo* is ok because this is not a crash.
|
||||
return callback ? callback(descriptor, callback_context, nullptr, true)
|
||||
: true;
|
||||
}
|
||||
|
||||
void SetFirstChanceExceptionHandler(FirstChanceHandler callback) {
|
||||
|
@ -44,6 +44,12 @@
|
||||
#include "common/using_std_string.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
#include "PHC.h"
|
||||
#else
|
||||
namespace mozilla { namespace phc { class AddrInfo {}; } }
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// ExceptionHandler
|
||||
@ -82,7 +88,8 @@ class ExceptionHandler {
|
||||
// attempting to write a minidump. If a FilterCallback returns false,
|
||||
// Breakpad will immediately report the exception as unhandled without
|
||||
// writing a minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void *context);
|
||||
typedef bool (*FilterCallback)(void *context,
|
||||
const mozilla::phc::AddrInfo* addr_info);
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// |descriptor| contains the file descriptor or file path containing the
|
||||
@ -102,6 +109,7 @@ class ExceptionHandler {
|
||||
// return true directly (unless |succeeded| is true).
|
||||
typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor,
|
||||
void* context,
|
||||
const mozilla::phc::AddrInfo* addr_info,
|
||||
bool succeeded);
|
||||
|
||||
// In certain cases, a user may wish to handle the generation of the minidump
|
||||
@ -234,7 +242,8 @@ class ExceptionHandler {
|
||||
static void RestoreHandlersLocked();
|
||||
|
||||
void PreresolveSymbols();
|
||||
bool GenerateDump(CrashContext *context);
|
||||
bool GenerateDump(CrashContext *context,
|
||||
const mozilla::phc::AddrInfo* addr_info);
|
||||
void SendContinueSignalToChild();
|
||||
void WaitForContinueSignal();
|
||||
|
||||
|
@ -33,3 +33,6 @@ if CONFIG['OS_TARGET'] == 'Android':
|
||||
FINAL_LIBRARY = 'breakpad_client'
|
||||
|
||||
include('/toolkit/crashreporter/crashreporter.mozbuild')
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DEFINES['MOZ_PHC'] = True
|
||||
|
@ -343,7 +343,7 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
|
||||
|
||||
if (callback) {
|
||||
return callback(dump_path.c_str(), dump_id.c_str(),
|
||||
callback_context, result);
|
||||
callback_context, nullptr, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -377,7 +377,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||
if (exception_type && exception_code) {
|
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
if (filter_ && !filter_(callback_context_, nullptr))
|
||||
return false;
|
||||
result = crash_generation_client_->RequestDumpForException(
|
||||
exception_type,
|
||||
@ -402,7 +402,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||
if (exception_type && exception_code) {
|
||||
// If this is a real exception, give the filter (if any) a chance to
|
||||
// decide if this should be sent.
|
||||
if (filter_ && !filter_(callback_context_))
|
||||
if (filter_ && !filter_(callback_context_, nullptr))
|
||||
return false;
|
||||
|
||||
md.SetExceptionInformation(exception_type, exception_code,
|
||||
@ -418,7 +418,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||
// (rather than just writing out the file), then we should exit without
|
||||
// forwarding the exception to the next handler.
|
||||
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
|
||||
result)) {
|
||||
nullptr, result)) {
|
||||
if (exit_after_write)
|
||||
_exit(exception_type);
|
||||
}
|
||||
|
@ -48,6 +48,12 @@
|
||||
#include "mac/crash_generation/crash_generation_client.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
#include "PHC.h"
|
||||
#else
|
||||
namespace mozilla { namespace phc { class AddrInfo {}; } }
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::string;
|
||||
@ -75,7 +81,8 @@ class ExceptionHandler {
|
||||
// attempting to write a minidump. If a FilterCallback returns false, Breakpad
|
||||
// will immediately report the exception as unhandled without writing a
|
||||
// minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void *context);
|
||||
typedef bool (*FilterCallback)(void *context,
|
||||
const mozilla::phc::AddrInfo* addr_info);
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
// |minidump_id| is a unique id for the dump, so the minidump
|
||||
@ -87,7 +94,9 @@ class ExceptionHandler {
|
||||
// exception.
|
||||
typedef bool (*MinidumpCallback)(const char *dump_dir,
|
||||
const char *minidump_id,
|
||||
void *context, bool succeeded);
|
||||
void *context,
|
||||
const mozilla::phc::AddrInfo* addr_info,
|
||||
bool succeeded);
|
||||
|
||||
// A callback function which will be called directly if an exception occurs.
|
||||
// This bypasses the minidump file writing and simply gives the client
|
||||
|
@ -824,7 +824,7 @@ bool ExceptionHandler::WriteMinidumpForChild(HANDLE child,
|
||||
|
||||
if (callback) {
|
||||
success = callback(handler.dump_path_c_, handler.next_minidump_id_c_,
|
||||
callback_context, NULL, NULL, success);
|
||||
callback_context, NULL, NULL, nullptr, success);
|
||||
}
|
||||
|
||||
return success;
|
||||
@ -840,7 +840,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||
// HandleException to call any previous handler or return
|
||||
// EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear
|
||||
// as though this handler were not present at all.
|
||||
if (filter_ && !filter_(callback_context_, exinfo, assertion)) {
|
||||
if (filter_ && !filter_(callback_context_, exinfo, nullptr, assertion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -861,7 +861,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
|
||||
// scenario, the server process ends up creating the dump path and dump
|
||||
// id so they are not known to the client.
|
||||
success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
|
||||
exinfo, assertion, success);
|
||||
exinfo, assertion, nullptr, success);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
@ -75,6 +75,12 @@
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
#include "PHC.h"
|
||||
#else
|
||||
namespace mozilla { namespace phc { class AddrInfo {}; } }
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
using std::vector;
|
||||
@ -94,6 +100,7 @@ class ExceptionHandler {
|
||||
// Breakpad will immediately report the exception as unhandled without
|
||||
// writing a minidump, allowing another handler the opportunity to handle it.
|
||||
typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo,
|
||||
const mozilla::phc::AddrInfo* addr_info,
|
||||
MDRawAssertionInfo* assertion);
|
||||
|
||||
// A callback function to run after the minidump has been written.
|
||||
@ -125,6 +132,7 @@ class ExceptionHandler {
|
||||
void* context,
|
||||
EXCEPTION_POINTERS* exinfo,
|
||||
MDRawAssertionInfo* assertion,
|
||||
const mozilla::phc::AddrInfo* addr_info,
|
||||
bool succeeded);
|
||||
|
||||
// HandlerType specifies which types of handlers should be installed, if
|
||||
|
@ -115,6 +115,9 @@ if CONFIG['MOZ_CRASHREPORTER']:
|
||||
DEFINES['UNICODE'] = True
|
||||
DEFINES['_UNICODE'] = True
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DEFINES['MOZ_PHC'] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'google-breakpad/src',
|
||||
]
|
||||
|
@ -668,6 +668,72 @@ class BinaryAnnotationWriter : public AnnotationWriter {
|
||||
PlatformWriter& mPlatformWriter;
|
||||
};
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
// The stack traces are encoded as a comma-separated list of decimal
|
||||
// (not hexadecimal!) addresses, e.g. "12345678,12345679,12345680".
|
||||
static void WritePHCStackTrace(AnnotationWriter& aWriter,
|
||||
const Annotation aName,
|
||||
const phc::StackTrace* aStack) {
|
||||
// 21 is the max length of a 64-bit decimal address entry, including the
|
||||
// trailing comma or '\0'. And then we add another 32 just to be safe.
|
||||
char addrsString[mozilla::phc::StackTrace::kMaxFrames * 21 + 32];
|
||||
char addrString[32];
|
||||
char* p = addrsString;
|
||||
*p = 0;
|
||||
for (size_t i = 0; i < aStack->mLength; i++) {
|
||||
if (i != 0) {
|
||||
strcat(addrsString, ",");
|
||||
p++;
|
||||
}
|
||||
XP_STOA(uintptr_t(aStack->mPcs[i]), addrString);
|
||||
strcat(addrsString, addrString);
|
||||
}
|
||||
aWriter.Write(aName, addrsString);
|
||||
}
|
||||
|
||||
static void WritePHCAddrInfo(AnnotationWriter& writer,
|
||||
const phc::AddrInfo* aAddrInfo) {
|
||||
// Is this a PHC allocation needing special treatment?
|
||||
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
|
||||
const char* kindString;
|
||||
switch (aAddrInfo->mKind) {
|
||||
case phc::AddrInfo::Kind::Unknown:
|
||||
kindString = "Unknown(?!)";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::NeverAllocatedPage:
|
||||
kindString = "NeverAllocatedPage";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::InUsePage:
|
||||
kindString = "InUsePage(?!)";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::FreedPage:
|
||||
kindString = "FreedPage";
|
||||
break;
|
||||
case phc::AddrInfo::Kind::GuardPage:
|
||||
kindString = "GuardPage";
|
||||
break;
|
||||
default:
|
||||
kindString = "Unmatched(?!)";
|
||||
break;
|
||||
}
|
||||
writer.Write(Annotation::PHCKind, kindString);
|
||||
|
||||
char baseAddrString[32];
|
||||
XP_STOA(uintptr_t(aAddrInfo->mBaseAddr), baseAddrString);
|
||||
writer.Write(Annotation::PHCBaseAddress, baseAddrString);
|
||||
|
||||
char usableSizeString[32];
|
||||
XP_TTOA(aAddrInfo->mUsableSize, usableSizeString);
|
||||
writer.Write(Annotation::PHCUsableSize, usableSizeString);
|
||||
|
||||
WritePHCStackTrace(writer, Annotation::PHCAllocStack,
|
||||
&aAddrInfo->mAllocStack);
|
||||
WritePHCStackTrace(writer, Annotation::PHCFreeStack,
|
||||
&aAddrInfo->mFreeStack);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* If minidump_id is null, we assume that dump_path contains the full
|
||||
* dump file path.
|
||||
@ -885,6 +951,7 @@ static void WriteMozCrashReason(AnnotationWriter& aWriter) {
|
||||
}
|
||||
|
||||
static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
|
||||
const phc::AddrInfo* addrInfo,
|
||||
time_t crashTime) {
|
||||
INIAnnotationWriter writer(pw);
|
||||
for (auto key : MakeEnumeratedRange(Annotation::Count)) {
|
||||
@ -961,6 +1028,10 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
|
||||
writer.Write(Annotation::ContainsMemoryReport, "1");
|
||||
}
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
WritePHCAddrInfo(writer, addrInfo);
|
||||
#endif
|
||||
|
||||
std::function<void(const char*)> getThreadAnnotationCB =
|
||||
[&](const char* aValue) -> void {
|
||||
if (aValue) {
|
||||
@ -971,6 +1042,7 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
|
||||
}
|
||||
|
||||
static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
|
||||
const phc::AddrInfo* addrInfo,
|
||||
#ifdef XP_LINUX
|
||||
const MinidumpDescriptor& descriptor
|
||||
#else
|
||||
@ -1013,7 +1085,7 @@ static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
|
||||
WriteLiteral(eventFile, "\n");
|
||||
WriteString(eventFile, id_ascii);
|
||||
WriteLiteral(eventFile, "\n");
|
||||
WriteAnnotationsForMainProcessCrash(eventFile, crashTime);
|
||||
WriteAnnotationsForMainProcessCrash(eventFile, addrInfo, crashTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1033,7 +1105,7 @@ bool MinidumpCallback(
|
||||
#ifdef XP_WIN
|
||||
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
|
||||
#endif
|
||||
bool succeeded) {
|
||||
const phc::AddrInfo* addrInfo, bool succeeded) {
|
||||
bool returnValue = showOSCrashReporter ? false : succeeded;
|
||||
|
||||
static XP_CHAR minidumpPath[XP_PATH_MAX];
|
||||
@ -1086,7 +1158,7 @@ bool MinidumpCallback(
|
||||
WriteString(lastCrashFile, crashTimeString);
|
||||
}
|
||||
|
||||
WriteCrashEventFile(crashTime, crashTimeString,
|
||||
WriteCrashEventFile(crashTime, crashTimeString, addrInfo,
|
||||
#ifdef XP_LINUX
|
||||
descriptor
|
||||
#else
|
||||
@ -1100,7 +1172,7 @@ bool MinidumpCallback(
|
||||
#else
|
||||
OpenAPIData(apiData, dump_path, minidump_id);
|
||||
#endif
|
||||
WriteAnnotationsForMainProcessCrash(apiData, crashTime);
|
||||
WriteAnnotationsForMainProcessCrash(apiData, addrInfo, crashTime);
|
||||
|
||||
if (!doReport) {
|
||||
#ifdef XP_WIN
|
||||
@ -1220,7 +1292,8 @@ static bool BuildTempPath(PathStringT& aResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PrepareChildExceptionTimeAnnotations(void* context) {
|
||||
static void PrepareChildExceptionTimeAnnotations(
|
||||
void* context, const phc::AddrInfo* addrInfo) {
|
||||
MOZ_ASSERT(!XRE_IsParentProcess());
|
||||
|
||||
FileHandle f;
|
||||
@ -1245,6 +1318,10 @@ static void PrepareChildExceptionTimeAnnotations(void* context) {
|
||||
|
||||
WriteMozCrashReason(writer);
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
WritePHCAddrInfo(writer, addrInfo);
|
||||
#endif
|
||||
|
||||
std::function<void(const char*)> getThreadAnnotationCB =
|
||||
[&](const char* aValue) -> void {
|
||||
if (aValue) {
|
||||
@ -1275,6 +1352,7 @@ static void FreeBreakpadVM() {
|
||||
* Also calls FreeBreakpadVM if appropriate.
|
||||
*/
|
||||
static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
|
||||
const phc::AddrInfo* addr_info,
|
||||
MDRawAssertionInfo* assertion) {
|
||||
if (!exinfo) {
|
||||
mozilla::IOInterposer::Disable();
|
||||
@ -1307,10 +1385,11 @@ static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
|
||||
}
|
||||
|
||||
static bool ChildFPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
|
||||
const phc::AddrInfo* addrInfo,
|
||||
MDRawAssertionInfo* assertion) {
|
||||
bool result = FPEFilter(context, exinfo, assertion);
|
||||
bool result = FPEFilter(context, exinfo, addrInfo, assertion);
|
||||
if (result) {
|
||||
PrepareChildExceptionTimeAnnotations(context);
|
||||
PrepareChildExceptionTimeAnnotations(context, addrInfo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -1364,14 +1443,14 @@ static bool ShouldReport() {
|
||||
|
||||
#if !defined(XP_WIN)
|
||||
|
||||
static bool Filter(void* context) {
|
||||
static bool Filter(void* context, const phc::AddrInfo* addrInfo) {
|
||||
mozilla::IOInterposer::Disable();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ChildFilter(void* context) {
|
||||
static bool ChildFilter(void* context, const phc::AddrInfo* addrInfo) {
|
||||
mozilla::IOInterposer::Disable();
|
||||
PrepareChildExceptionTimeAnnotations(context);
|
||||
PrepareChildExceptionTimeAnnotations(context, addrInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3310,7 +3389,7 @@ static bool PairedDumpCallback(
|
||||
#ifdef XP_WIN
|
||||
EXCEPTION_POINTERS* /*unused*/, MDRawAssertionInfo* /*unused*/,
|
||||
#endif
|
||||
bool succeeded) {
|
||||
const phc::AddrInfo* addrInfo, bool succeeded) {
|
||||
nsCOMPtr<nsIFile>& minidump = *static_cast<nsCOMPtr<nsIFile>*>(context);
|
||||
|
||||
xpstring path;
|
||||
|
@ -31,6 +31,8 @@ var CrashTestUtils = {
|
||||
CRASH_X64CFI_SAVE_XMM128_FAR: 18,
|
||||
CRASH_X64CFI_EPILOG: 19,
|
||||
CRASH_X64CFI_EOF: 20,
|
||||
CRASH_PHC_USE_AFTER_FREE: 21,
|
||||
CRASH_PHC_DOUBLE_FREE: 22,
|
||||
|
||||
// Constants for dumpHasStream()
|
||||
// From google_breakpad/common/minidump_format.h
|
||||
|
@ -5,7 +5,16 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
FINAL_TARGET = '_tests/xpcshell/toolkit/crashreporter/test'
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini', 'unit_ipc/xpcshell.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'unit/xpcshell.ini',
|
||||
'unit_ipc/xpcshell.ini'
|
||||
]
|
||||
if CONFIG['MOZ_PHC']:
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
'unit/xpcshell-phc.ini',
|
||||
'unit_ipc/xpcshell-phc.ini'
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
@ -41,6 +50,9 @@ else:
|
||||
'-fexceptions',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_PHC']:
|
||||
DEFINES['MOZ_PHC'] = True
|
||||
|
||||
GeckoSharedLibrary('testcrasher')
|
||||
|
||||
DEFINES['SHARED_LIBRARY'] = '%s%s%s' % (
|
||||
|
@ -12,6 +12,10 @@
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
# include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This pure virtual call example is from MSDN
|
||||
*/
|
||||
@ -77,6 +81,8 @@ const int16_t CRASH_X64CFI_SAVE_XMM128 = 17;
|
||||
const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
|
||||
const int16_t CRASH_X64CFI_EPILOG = 19;
|
||||
const int16_t CRASH_X64CFI_EOF = 20;
|
||||
const int16_t CRASH_PHC_USE_AFTER_FREE = 21;
|
||||
const int16_t CRASH_PHC_DOUBLE_FREE = 22;
|
||||
|
||||
#if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64) && !defined(__MINGW32__)
|
||||
|
||||
@ -118,6 +124,21 @@ void MOZ_NEVER_INLINE ReserveStack() {
|
||||
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD
|
||||
|
||||
#ifdef MOZ_PHC
|
||||
char* GetPHCAllocation(size_t aSize) {
|
||||
// A crude but effective way to get a PHC allocation.
|
||||
for (int i = 0; i < 2000000; i++) {
|
||||
char* p = (char*)malloc(aSize);
|
||||
if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
|
||||
return p;
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
// This failure doesn't seem to occur in practice...
|
||||
MOZ_CRASH("failed to get a PHC allocation");
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" NS_EXPORT void Crash(int16_t how) {
|
||||
switch (how) {
|
||||
case CRASH_INVALID_POINTER_DEREF: {
|
||||
@ -170,6 +191,22 @@ extern "C" NS_EXPORT void Crash(int16_t how) {
|
||||
break;
|
||||
}
|
||||
#endif // XP_WIN && HAVE_64BIT_BUILD && !defined(__MINGW32__)
|
||||
#ifdef MOZ_PHC
|
||||
case CRASH_PHC_USE_AFTER_FREE: {
|
||||
// Do a UAF, triggering a crash.
|
||||
char* p = GetPHCAllocation(32);
|
||||
free(p);
|
||||
*p = 0;
|
||||
// not reached
|
||||
}
|
||||
case CRASH_PHC_DOUBLE_FREE: {
|
||||
// Do a double free, triggering a crash.
|
||||
char* p = GetPHCAllocation(64);
|
||||
free(p);
|
||||
free(p);
|
||||
// not reached
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
43
toolkit/crashreporter/test/unit/test_crash_phc.js
Normal file
43
toolkit/crashreporter/test/unit/test_crash_phc.js
Normal file
@ -0,0 +1,43 @@
|
||||
function check(extra, size) {
|
||||
Assert.equal(extra.PHCKind, "FreedPage");
|
||||
|
||||
// This is a string holding a decimal address.
|
||||
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
|
||||
|
||||
Assert.equal(extra.PHCUsableSize, size);
|
||||
|
||||
// These are strings holding comma-separated lists of decimal addresses.
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
|
||||
dump(
|
||||
"INFO | test_crash_phc.js | Can't test crashreporter in a non-libxul build.\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
do_crash(
|
||||
function() {
|
||||
crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
|
||||
},
|
||||
function(mdump, extra) {
|
||||
// CRASH_PHC_USE_AFTER_FREE uses 32 for the size.
|
||||
check(extra, 32);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
do_crash(
|
||||
function() {
|
||||
crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
|
||||
},
|
||||
function(mdump, extra) {
|
||||
// CRASH_PHC_DOUBLE_FREE uses 64 for the size.
|
||||
check(extra, 64);
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
9
toolkit/crashreporter/test/unit/xpcshell-phc.ini
Normal file
9
toolkit/crashreporter/test/unit/xpcshell-phc.ini
Normal file
@ -0,0 +1,9 @@
|
||||
[DEFAULT]
|
||||
head = head_crashreporter.js
|
||||
skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # 1536217
|
||||
support-files =
|
||||
crasher_subprocess_head.js
|
||||
crasher_subprocess_tail.js
|
||||
|
||||
[test_crash_phc.js]
|
||||
|
30
toolkit/crashreporter/test/unit_ipc/test_content_phc.js
Normal file
30
toolkit/crashreporter/test/unit_ipc/test_content_phc.js
Normal file
@ -0,0 +1,30 @@
|
||||
/* import-globals-from ../unit/head_crashreporter.js */
|
||||
load("../unit/head_crashreporter.js");
|
||||
|
||||
function run_test() {
|
||||
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
|
||||
dump(
|
||||
"INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
do_content_crash(
|
||||
function() {
|
||||
crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
|
||||
},
|
||||
function(mdump, extra) {
|
||||
Assert.equal(extra.PHCKind, "FreedPage");
|
||||
|
||||
// This is a string holding a decimal address.
|
||||
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
|
||||
|
||||
// CRASH_PHC_USE_AFTER_FREE uses 32 for the size.
|
||||
Assert.equal(extra.PHCUsableSize, 32);
|
||||
|
||||
// These are strings holding comma-separated lists of decimal addresses.
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
|
||||
}
|
||||
);
|
||||
}
|
33
toolkit/crashreporter/test/unit_ipc/test_content_phc2.js
Normal file
33
toolkit/crashreporter/test/unit_ipc/test_content_phc2.js
Normal file
@ -0,0 +1,33 @@
|
||||
/* import-globals-from ../unit/head_crashreporter.js */
|
||||
load("../unit/head_crashreporter.js");
|
||||
|
||||
function run_test() {
|
||||
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
|
||||
dump(
|
||||
"INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For some unknown reason, having two do_content_crash() calls in a single
|
||||
// test doesn't work. That explains why this test exists separately from
|
||||
// test_content_phc.js.
|
||||
do_content_crash(
|
||||
function() {
|
||||
crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
|
||||
},
|
||||
function(mdump, extra) {
|
||||
Assert.equal(extra.PHCKind, "FreedPage");
|
||||
|
||||
// This is a string holding a decimal address.
|
||||
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
|
||||
|
||||
// CRASH_PHC_DOUBLE_FREE uses 64 for the size.
|
||||
Assert.equal(extra.PHCUsableSize, 64);
|
||||
|
||||
// These are strings holding comma-separated lists of decimal addresses.
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
|
||||
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
|
||||
}
|
||||
);
|
||||
}
|
10
toolkit/crashreporter/test/unit_ipc/xpcshell-phc.ini
Normal file
10
toolkit/crashreporter/test/unit_ipc/xpcshell-phc.ini
Normal file
@ -0,0 +1,10 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # 1536217
|
||||
support-files =
|
||||
!/toolkit/crashreporter/test/unit/crasher_subprocess_head.js
|
||||
!/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js
|
||||
!/toolkit/crashreporter/test/unit/head_crashreporter.js
|
||||
|
||||
[test_content_phc.js]
|
||||
[test_content_phc2.js]
|
Loading…
Reference in New Issue
Block a user