mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 732173 - poison write during shutdown in a debug build. r=ted.
With this patch we now patch the write functions during shutdown in a debug build and abort if a non white listed write is found.
This commit is contained in:
parent
010708a004
commit
bc46f77176
@ -37,6 +37,8 @@ namespace google_breakpad {
|
||||
|
||||
CrashGenerationServer::CrashGenerationServer(
|
||||
const char *mach_port_name,
|
||||
FilterCallback filter,
|
||||
void *filter_context,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void *dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
@ -44,6 +46,8 @@ CrashGenerationServer::CrashGenerationServer(
|
||||
bool generate_dumps,
|
||||
const std::string &dump_path)
|
||||
: dump_callback_(dump_callback),
|
||||
filter_(filter),
|
||||
filter_context_(filter_context),
|
||||
dump_context_(dump_context),
|
||||
exit_callback_(exit_callback),
|
||||
exit_context_(exit_context),
|
||||
@ -110,7 +114,7 @@ bool CrashGenerationServer::WaitForOneMessage() {
|
||||
|
||||
bool result;
|
||||
std::string dump_path;
|
||||
if (generate_dumps_) {
|
||||
if (generate_dumps_ && (!filter_ || filter_(filter_context_))) {
|
||||
ScopedTaskSuspend suspend(remote_task);
|
||||
|
||||
MinidumpGenerator generator(remote_task, handler_thread);
|
||||
|
@ -65,10 +65,14 @@ class CrashGenerationServer {
|
||||
|
||||
typedef void (*OnClientExitingCallback)(void *context,
|
||||
const ClientInfo &client_info);
|
||||
typedef bool (*FilterCallback)(void *context);
|
||||
|
||||
// Create an instance with the given parameters.
|
||||
//
|
||||
// mach_port_name: Named server port to listen on.
|
||||
// filter: Callback for a client to know that we are about to write a dump
|
||||
// and mabybe prevent it.
|
||||
// filter_context: Context for the filte callback.
|
||||
// dump_callback: Callback for a client crash dump request.
|
||||
// dump_context: Context for client crash dump request callback.
|
||||
// exit_callback: Callback for client process exit.
|
||||
@ -80,6 +84,8 @@ class CrashGenerationServer {
|
||||
// dump_path: Path for generating dumps; required only if true is
|
||||
// passed for generateDumps parameter; NULL can be passed otherwise.
|
||||
CrashGenerationServer(const char *mach_port_name,
|
||||
FilterCallback filter,
|
||||
void *filter_context,
|
||||
OnClientDumpRequestCallback dump_callback,
|
||||
void *dump_context,
|
||||
OnClientExitingCallback exit_callback,
|
||||
@ -109,6 +115,9 @@ class CrashGenerationServer {
|
||||
// if a quit message was received or if an error occurred.
|
||||
bool WaitForOneMessage();
|
||||
|
||||
FilterCallback filter_;
|
||||
void *filter_context_;
|
||||
|
||||
OnClientDumpRequestCallback dump_callback_;
|
||||
void *dump_context_;
|
||||
|
||||
|
@ -79,6 +79,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "mozilla/mozalloc_oom.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
CFStringRef reporterClientAppID = CFSTR("org.mozilla.crashreporter");
|
||||
@ -634,6 +635,14 @@ static bool ShouldReport()
|
||||
return !(envvar && *envvar);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool Filter(void* context) {
|
||||
mozilla::DisableWritePoisoning();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsresult SetExceptionHandler(nsIFile* aXREDirectory,
|
||||
bool force/*=false*/)
|
||||
{
|
||||
@ -792,7 +801,7 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory,
|
||||
#ifdef XP_WIN
|
||||
FPEFilter,
|
||||
#else
|
||||
nsnull,
|
||||
Filter,
|
||||
#endif
|
||||
MinidumpCallback,
|
||||
nsnull,
|
||||
@ -1913,6 +1922,11 @@ OOPInitialized()
|
||||
return pidToMinidump != NULL;
|
||||
}
|
||||
|
||||
static bool ChildFilter(void *context) {
|
||||
mozilla::DisableWritePoisoning();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
OOPInit()
|
||||
{
|
||||
@ -1963,6 +1977,8 @@ OOPInit()
|
||||
|
||||
crashServer = new CrashGenerationServer(
|
||||
childCrashNotifyPipe,
|
||||
ChildFilter,
|
||||
NULL,
|
||||
OnChildProcessDumpRequested, NULL,
|
||||
NULL, NULL,
|
||||
true, // automatically generate dumps
|
||||
@ -2105,7 +2121,7 @@ SetRemoteExceptionHandler(const nsACString& crashPipe)
|
||||
|
||||
gExceptionHandler = new google_breakpad::
|
||||
ExceptionHandler("",
|
||||
NULL, // no filter callback
|
||||
Filter,
|
||||
NULL, // no minidump callback
|
||||
NULL, // no callback context
|
||||
true, // install signal handlers
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "nsStackWalk.h"
|
||||
#include "nsTraceMallocCallbacks.h"
|
||||
#include "nsTypeInfo.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
|
||||
@ -1329,6 +1330,9 @@ NS_TraceMallocStartup(int logfd)
|
||||
PR_ASSERT(logfp == &default_logfile);
|
||||
tracing_enabled = (logfd >= 0);
|
||||
|
||||
if (logfd >= 3)
|
||||
MozillaRegisterDebugFD(logfd);
|
||||
|
||||
/* stacks are disabled if this env var is set to a non-empty value */
|
||||
stack_disable_env = PR_GetEnv("NS_TRACE_MALLOC_DISABLE_STACKS");
|
||||
stacks_enabled = !stack_disable_env || !*stack_disable_env;
|
||||
@ -1527,6 +1531,7 @@ NS_TraceMallocShutdown(void)
|
||||
log_tmstats(fp);
|
||||
flush_logfile(fp);
|
||||
if (fp->fd >= 0) {
|
||||
MozillaUnRegisterDebugFD(fp->fd);
|
||||
close(fp->fd);
|
||||
fp->fd = -1;
|
||||
}
|
||||
@ -1663,6 +1668,7 @@ NS_TraceMallocCloseLogFD(int fd)
|
||||
}
|
||||
|
||||
TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t);
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@ -1757,6 +1763,7 @@ NS_TraceMallocDumpAllocations(const char *pathname)
|
||||
{
|
||||
FILE *ofp;
|
||||
int rv;
|
||||
int fd;
|
||||
|
||||
tm_thread *t = tm_get_thread();
|
||||
|
||||
@ -1764,12 +1771,15 @@ NS_TraceMallocDumpAllocations(const char *pathname)
|
||||
|
||||
ofp = fopen(pathname, WRITE_FLAGS);
|
||||
if (ofp) {
|
||||
MozillaRegisterDebugFD(fileno(ofp));
|
||||
if (allocations) {
|
||||
PL_HashTableEnumerateEntries(allocations, allocation_enumerator,
|
||||
ofp);
|
||||
}
|
||||
rv = ferror(ofp) ? -1 : 0;
|
||||
fclose(ofp);
|
||||
fd = fileno(ofp);
|
||||
fclose(ofp); /* May call write. */
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
} else {
|
||||
rv = -1;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#endif
|
||||
|
||||
#include "mozilla/BlockingResourceBase.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#ifdef HAVE_DLOPEN
|
||||
#include <dlfcn.h>
|
||||
@ -646,6 +647,7 @@ static bool InitLog(const char* envVar, const char* msg, FILE* *result)
|
||||
}
|
||||
stream = ::fopen(fname.get(), "w" FOPEN_NO_INHERIT);
|
||||
if (stream != NULL) {
|
||||
MozillaRegisterDebugFD(fileno(stream));
|
||||
*result = stream;
|
||||
fprintf(stdout, "### %s defined -- logging %s to %s\n",
|
||||
envVar, msg, fname.get());
|
||||
@ -1235,6 +1237,17 @@ nsTraceRefcntImpl::Startup()
|
||||
{
|
||||
}
|
||||
|
||||
static void maybeUnregisterAndCloseFile(FILE *&f) {
|
||||
if (!f)
|
||||
return;
|
||||
|
||||
int fd = fileno(f);
|
||||
fclose(f);
|
||||
if (fd != 1 && fd != 2)
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
f = nsnull;
|
||||
}
|
||||
|
||||
void
|
||||
nsTraceRefcntImpl::Shutdown()
|
||||
{
|
||||
@ -1256,26 +1269,11 @@ nsTraceRefcntImpl::Shutdown()
|
||||
PL_HashTableDestroy(gSerialNumbers);
|
||||
gSerialNumbers = nsnull;
|
||||
}
|
||||
if (gBloatLog) {
|
||||
fclose(gBloatLog);
|
||||
gBloatLog = nsnull;
|
||||
}
|
||||
if (gRefcntsLog) {
|
||||
fclose(gRefcntsLog);
|
||||
gRefcntsLog = nsnull;
|
||||
}
|
||||
if (gAllocLog) {
|
||||
fclose(gAllocLog);
|
||||
gAllocLog = nsnull;
|
||||
}
|
||||
if (gLeakyLog) {
|
||||
fclose(gLeakyLog);
|
||||
gLeakyLog = nsnull;
|
||||
}
|
||||
if (gCOMPtrLog) {
|
||||
fclose(gCOMPtrLog);
|
||||
gCOMPtrLog = nsnull;
|
||||
}
|
||||
maybeUnregisterAndCloseFile(gBloatLog);
|
||||
maybeUnregisterAndCloseFile(gRefcntsLog);
|
||||
maybeUnregisterAndCloseFile(gAllocLog);
|
||||
maybeUnregisterAndCloseFile(gLeakyLog);
|
||||
maybeUnregisterAndCloseFile(gCOMPtrLog);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,10 @@ ifeq ($(OS_ARCH),Linux)
|
||||
DEFINES += -DXP_LINUX
|
||||
endif
|
||||
|
||||
ifeq (Darwin, $(OS_ARCH))
|
||||
CSRCS = mach_override.c
|
||||
endif
|
||||
|
||||
CPPSRCS = \
|
||||
$(XPCOM_GLUE_SRC_LCPPSRCS) \
|
||||
$(XPCOM_GLUENS_SRC_LCPPSRCS) \
|
||||
@ -37,6 +41,12 @@ CPPSRCS = \
|
||||
FileLocation.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifeq (Darwin, $(OS_ARCH))
|
||||
CPPSRCS += mozPoisonWriteMac.cpp
|
||||
else
|
||||
CPPSRCS += mozPoisonWriteStub.cpp
|
||||
endif
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
CPPSRCS += perfprobe.cpp
|
||||
endif
|
||||
@ -95,6 +105,7 @@ EXPORTS_mozilla = \
|
||||
ServiceList.h \
|
||||
Omnijar.h \
|
||||
FileLocation.h \
|
||||
mozPoisonWrite.h \
|
||||
$(NULL)
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
|
768
xpcom/build/mach_override.c
Normal file
768
xpcom/build/mach_override.c
Normal file
@ -0,0 +1,768 @@
|
||||
/* copied from https://github.com/rentzsch/mach_star */
|
||||
|
||||
/*******************************************************************************
|
||||
mach_override.c
|
||||
Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
|
||||
Some rights reserved: <http://opensource.org/licenses/mit-license.php>
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "mach_override.h"
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/vm_map.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
/**************************
|
||||
*
|
||||
* Constants
|
||||
*
|
||||
**************************/
|
||||
#pragma mark -
|
||||
#pragma mark (Constants)
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
|
||||
long kIslandTemplate[] = {
|
||||
0x9001FFFC, // stw r0,-4(SP)
|
||||
0x3C00DEAD, // lis r0,0xDEAD
|
||||
0x6000BEEF, // ori r0,r0,0xBEEF
|
||||
0x7C0903A6, // mtctr r0
|
||||
0x8001FFFC, // lwz r0,-4(SP)
|
||||
0x60000000, // nop ; optionally replaced
|
||||
0x4E800420 // bctr
|
||||
};
|
||||
|
||||
#define kAddressHi 3
|
||||
#define kAddressLo 5
|
||||
#define kInstructionHi 10
|
||||
#define kInstructionLo 11
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
#define kOriginalInstructionsSize 16
|
||||
|
||||
char kIslandTemplate[] = {
|
||||
// kOriginalInstructionsSize nop instructions so that we
|
||||
// should have enough space to host original instructions
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
// Now the real jump instruction
|
||||
0xE9, 0xEF, 0xBE, 0xAD, 0xDE
|
||||
};
|
||||
|
||||
#define kInstructions 0
|
||||
#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1
|
||||
#elif defined(__x86_64__)
|
||||
|
||||
#define kOriginalInstructionsSize 32
|
||||
|
||||
#define kJumpAddress kOriginalInstructionsSize + 6
|
||||
|
||||
char kIslandTemplate[] = {
|
||||
// kOriginalInstructionsSize nop instructions so that we
|
||||
// should have enough space to host original instructions
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
|
||||
// Now the real jump instruction
|
||||
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#define kAllocateHigh 1
|
||||
#define kAllocateNormal 0
|
||||
|
||||
/**************************
|
||||
*
|
||||
* Data Types
|
||||
*
|
||||
**************************/
|
||||
#pragma mark -
|
||||
#pragma mark (Data Types)
|
||||
|
||||
typedef struct {
|
||||
char instructions[sizeof(kIslandTemplate)];
|
||||
int allocatedHigh;
|
||||
} BranchIsland;
|
||||
|
||||
/**************************
|
||||
*
|
||||
* Funky Protos
|
||||
*
|
||||
**************************/
|
||||
#pragma mark -
|
||||
#pragma mark (Funky Protos)
|
||||
|
||||
mach_error_t
|
||||
allocateBranchIsland(
|
||||
BranchIsland **island,
|
||||
int allocateHigh,
|
||||
void *originalFunctionAddress);
|
||||
|
||||
mach_error_t
|
||||
freeBranchIsland(
|
||||
BranchIsland *island );
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
mach_error_t
|
||||
setBranchIslandTarget(
|
||||
BranchIsland *island,
|
||||
const void *branchTo,
|
||||
long instruction );
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
mach_error_t
|
||||
setBranchIslandTarget_i386(
|
||||
BranchIsland *island,
|
||||
const void *branchTo,
|
||||
char* instructions );
|
||||
void
|
||||
atomic_mov64(
|
||||
uint64_t *targetAddress,
|
||||
uint64_t value );
|
||||
|
||||
static Boolean
|
||||
eatKnownInstructions(
|
||||
unsigned char *code,
|
||||
uint64_t *newInstruction,
|
||||
int *howManyEaten,
|
||||
char *originalInstructions,
|
||||
int *originalInstructionCount,
|
||||
uint8_t *originalInstructionSizes );
|
||||
|
||||
static void
|
||||
fixupInstructions(
|
||||
void *originalFunction,
|
||||
void *escapeIsland,
|
||||
void *instructionsToFix,
|
||||
int instructionCount,
|
||||
uint8_t *instructionSizes );
|
||||
#endif
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Interface
|
||||
*
|
||||
*******************************************************************************/
|
||||
#pragma mark -
|
||||
#pragma mark (Interface)
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
mach_error_t makeIslandExecutable(void *address) {
|
||||
mach_error_t err = err_none;
|
||||
vm_size_t pageSize;
|
||||
host_page_size( mach_host_self(), &pageSize );
|
||||
uintptr_t page = (uintptr_t)address & ~(uintptr_t)(pageSize-1);
|
||||
int e = err_none;
|
||||
e |= mprotect((void *)page, pageSize, PROT_EXEC | PROT_READ | PROT_WRITE);
|
||||
e |= msync((void *)page, pageSize, MS_INVALIDATE );
|
||||
if (e) {
|
||||
err = err_cannot_override;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
mach_error_t
|
||||
mach_override_ptr(
|
||||
void *originalFunctionAddress,
|
||||
const void *overrideFunctionAddress,
|
||||
void **originalFunctionReentryIsland )
|
||||
{
|
||||
assert( originalFunctionAddress );
|
||||
assert( overrideFunctionAddress );
|
||||
|
||||
// this addresses overriding such functions as AudioOutputUnitStart()
|
||||
// test with modified DefaultOutputUnit project
|
||||
#if defined(__x86_64__)
|
||||
for(;;){
|
||||
if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????]
|
||||
originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1));
|
||||
else break;
|
||||
}
|
||||
#elif defined(__i386__)
|
||||
for(;;){
|
||||
if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x????????
|
||||
originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1);
|
||||
else break;
|
||||
}
|
||||
#endif
|
||||
|
||||
long *originalFunctionPtr = (long*) originalFunctionAddress;
|
||||
mach_error_t err = err_none;
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
// Ensure first instruction isn't 'mfctr'.
|
||||
#define kMFCTRMask 0xfc1fffff
|
||||
#define kMFCTRInstruction 0x7c0903a6
|
||||
|
||||
long originalInstruction = *originalFunctionPtr;
|
||||
if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) )
|
||||
err = err_cannot_override;
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
int eatenCount = 0;
|
||||
int originalInstructionCount = 0;
|
||||
char originalInstructions[kOriginalInstructionsSize];
|
||||
uint8_t originalInstructionSizes[kOriginalInstructionsSize];
|
||||
uint64_t jumpRelativeInstruction = 0; // JMP
|
||||
|
||||
Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr,
|
||||
&jumpRelativeInstruction, &eatenCount,
|
||||
originalInstructions, &originalInstructionCount,
|
||||
originalInstructionSizes );
|
||||
if (eatenCount > kOriginalInstructionsSize) {
|
||||
//printf ("Too many instructions eaten\n");
|
||||
overridePossible = false;
|
||||
}
|
||||
if (!overridePossible) err = err_cannot_override;
|
||||
if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
|
||||
#endif
|
||||
|
||||
// Make the original function implementation writable.
|
||||
if( !err ) {
|
||||
err = vm_protect( mach_task_self(),
|
||||
(vm_address_t) originalFunctionPtr, 8, false,
|
||||
(VM_PROT_ALL | VM_PROT_COPY) );
|
||||
if( err )
|
||||
err = vm_protect( mach_task_self(),
|
||||
(vm_address_t) originalFunctionPtr, 8, false,
|
||||
(VM_PROT_DEFAULT | VM_PROT_COPY) );
|
||||
}
|
||||
if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
|
||||
|
||||
// Allocate and target the escape island to the overriding function.
|
||||
BranchIsland *escapeIsland = NULL;
|
||||
if( !err )
|
||||
err = allocateBranchIsland( &escapeIsland, kAllocateHigh, originalFunctionAddress );
|
||||
if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
|
||||
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
if( !err )
|
||||
err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 );
|
||||
|
||||
// Build the branch absolute instruction to the escape island.
|
||||
long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning.
|
||||
if( !err ) {
|
||||
long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF;
|
||||
branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress;
|
||||
}
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
|
||||
|
||||
if( !err )
|
||||
err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 );
|
||||
|
||||
if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
|
||||
// Build the jump relative instruction to the escape island
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
if (!err) {
|
||||
uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5);
|
||||
addressOffset = OSSwapInt32(addressOffset);
|
||||
|
||||
jumpRelativeInstruction |= 0xE900000000000000LL;
|
||||
jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24;
|
||||
jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Optionally allocate & return the reentry island. This may contain relocated
|
||||
// jmp instructions and so has all the same addressing reachability requirements
|
||||
// the escape island has to the original function, except the escape island is
|
||||
// technically our original function.
|
||||
BranchIsland *reentryIsland = NULL;
|
||||
if( !err && originalFunctionReentryIsland ) {
|
||||
err = allocateBranchIsland( &reentryIsland, kAllocateHigh, escapeIsland);
|
||||
if( !err )
|
||||
*originalFunctionReentryIsland = reentryIsland;
|
||||
}
|
||||
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
// Atomically:
|
||||
// o If the reentry island was allocated:
|
||||
// o Insert the original instruction into the reentry island.
|
||||
// o Target the reentry island at the 2nd instruction of the
|
||||
// original function.
|
||||
// o Replace the original instruction with the branch absolute.
|
||||
if( !err ) {
|
||||
int escapeIslandEngaged = false;
|
||||
do {
|
||||
if( reentryIsland )
|
||||
err = setBranchIslandTarget( reentryIsland,
|
||||
(void*) (originalFunctionPtr+1), originalInstruction );
|
||||
if( !err ) {
|
||||
escapeIslandEngaged = CompareAndSwap( originalInstruction,
|
||||
branchAbsoluteInstruction,
|
||||
(UInt32*)originalFunctionPtr );
|
||||
if( !escapeIslandEngaged ) {
|
||||
// Someone replaced the instruction out from under us,
|
||||
// re-read the instruction, make sure it's still not
|
||||
// 'mfctr' and try again.
|
||||
originalInstruction = *originalFunctionPtr;
|
||||
if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction)
|
||||
err = err_cannot_override;
|
||||
}
|
||||
}
|
||||
} while( !err && !escapeIslandEngaged );
|
||||
}
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
// Atomically:
|
||||
// o If the reentry island was allocated:
|
||||
// o Insert the original instructions into the reentry island.
|
||||
// o Target the reentry island at the first non-replaced
|
||||
// instruction of the original function.
|
||||
// o Replace the original first instructions with the jump relative.
|
||||
//
|
||||
// Note that on i386, we do not support someone else changing the code under our feet
|
||||
if ( !err ) {
|
||||
fixupInstructions(originalFunctionPtr, reentryIsland, originalInstructions,
|
||||
originalInstructionCount, originalInstructionSizes );
|
||||
|
||||
if( reentryIsland )
|
||||
err = setBranchIslandTarget_i386( reentryIsland,
|
||||
(void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions );
|
||||
// try making islands executable before planting the jmp
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if( !err )
|
||||
err = makeIslandExecutable(escapeIsland);
|
||||
if( !err && reentryIsland )
|
||||
err = makeIslandExecutable(reentryIsland);
|
||||
#endif
|
||||
if ( !err )
|
||||
atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Clean up on error.
|
||||
if( err ) {
|
||||
if( reentryIsland )
|
||||
freeBranchIsland( reentryIsland );
|
||||
if( escapeIsland )
|
||||
freeBranchIsland( escapeIsland );
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Implementation
|
||||
*
|
||||
*******************************************************************************/
|
||||
#pragma mark -
|
||||
#pragma mark (Implementation)
|
||||
|
||||
/***************************************************************************//**
|
||||
Implementation: Allocates memory for a branch island.
|
||||
|
||||
@param island <- The allocated island.
|
||||
@param allocateHigh -> Whether to allocate the island at the end of the
|
||||
address space (for use with the branch absolute
|
||||
instruction).
|
||||
@result <- mach_error_t
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
mach_error_t
|
||||
allocateBranchIsland(
|
||||
BranchIsland **island,
|
||||
int allocateHigh,
|
||||
void *originalFunctionAddress)
|
||||
{
|
||||
assert( island );
|
||||
|
||||
mach_error_t err = err_none;
|
||||
|
||||
if( allocateHigh ) {
|
||||
vm_size_t pageSize;
|
||||
err = host_page_size( mach_host_self(), &pageSize );
|
||||
if( !err ) {
|
||||
assert( sizeof( BranchIsland ) <= pageSize );
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
vm_address_t first = 0xfeffffff;
|
||||
vm_address_t last = 0xfe000000 + pageSize;
|
||||
#elif defined(__x86_64__)
|
||||
vm_address_t first = (uint64_t)originalFunctionAddress & ~(uint64_t)(((uint64_t)1 << 31) - 1) | ((uint64_t)1 << 31); // start in the middle of the page?
|
||||
vm_address_t last = 0x0;
|
||||
#else
|
||||
vm_address_t first = 0xffc00000;
|
||||
vm_address_t last = 0xfffe0000;
|
||||
#endif
|
||||
|
||||
vm_address_t page = first;
|
||||
int allocated = 0;
|
||||
vm_map_t task_self = mach_task_self();
|
||||
|
||||
while( !err && !allocated && page != last ) {
|
||||
|
||||
err = vm_allocate( task_self, &page, pageSize, 0 );
|
||||
if( err == err_none )
|
||||
allocated = 1;
|
||||
else if( err == KERN_NO_SPACE ) {
|
||||
#if defined(__x86_64__)
|
||||
page -= pageSize;
|
||||
#else
|
||||
page += pageSize;
|
||||
#endif
|
||||
err = err_none;
|
||||
}
|
||||
}
|
||||
if( allocated )
|
||||
*island = (BranchIsland*) page;
|
||||
else if( !allocated && !err )
|
||||
err = KERN_NO_SPACE;
|
||||
}
|
||||
} else {
|
||||
void *block = malloc( sizeof( BranchIsland ) );
|
||||
if( block )
|
||||
*island = block;
|
||||
else
|
||||
err = KERN_NO_SPACE;
|
||||
}
|
||||
if( !err )
|
||||
(**island).allocatedHigh = allocateHigh;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/***************************************************************************//**
|
||||
Implementation: Deallocates memory for a branch island.
|
||||
|
||||
@param island -> The island to deallocate.
|
||||
@result <- mach_error_t
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
mach_error_t
|
||||
freeBranchIsland(
|
||||
BranchIsland *island )
|
||||
{
|
||||
assert( island );
|
||||
assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] );
|
||||
assert( island->allocatedHigh );
|
||||
|
||||
mach_error_t err = err_none;
|
||||
|
||||
if( island->allocatedHigh ) {
|
||||
vm_size_t pageSize;
|
||||
err = host_page_size( mach_host_self(), &pageSize );
|
||||
if( !err ) {
|
||||
assert( sizeof( BranchIsland ) <= pageSize );
|
||||
err = vm_deallocate(
|
||||
mach_task_self(),
|
||||
(vm_address_t) island, pageSize );
|
||||
}
|
||||
} else {
|
||||
free( island );
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/***************************************************************************//**
|
||||
Implementation: Sets the branch island's target, with an optional
|
||||
instruction.
|
||||
|
||||
@param island -> The branch island to insert target into.
|
||||
@param branchTo -> The address of the target.
|
||||
@param instruction -> Optional instruction to execute prior to branch. Set
|
||||
to zero for nop.
|
||||
@result <- mach_error_t
|
||||
|
||||
***************************************************************************/
|
||||
#if defined(__ppc__) || defined(__POWERPC__)
|
||||
mach_error_t
|
||||
setBranchIslandTarget(
|
||||
BranchIsland *island,
|
||||
const void *branchTo,
|
||||
long instruction )
|
||||
{
|
||||
// Copy over the template code.
|
||||
bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
|
||||
|
||||
// Fill in the address.
|
||||
((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF;
|
||||
((short*)island->instructions)[kAddressHi]
|
||||
= (((long) branchTo) >> 16) & 0x0000FFFF;
|
||||
|
||||
// Fill in the (optional) instuction.
|
||||
if( instruction != 0 ) {
|
||||
((short*)island->instructions)[kInstructionLo]
|
||||
= instruction & 0x0000FFFF;
|
||||
((short*)island->instructions)[kInstructionHi]
|
||||
= (instruction >> 16) & 0x0000FFFF;
|
||||
}
|
||||
|
||||
//MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) );
|
||||
msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
|
||||
|
||||
return err_none;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__i386__)
|
||||
mach_error_t
|
||||
setBranchIslandTarget_i386(
|
||||
BranchIsland *island,
|
||||
const void *branchTo,
|
||||
char* instructions )
|
||||
{
|
||||
|
||||
// Copy over the template code.
|
||||
bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
|
||||
|
||||
// copy original instructions
|
||||
if (instructions) {
|
||||
bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize);
|
||||
}
|
||||
|
||||
// Fill in the address.
|
||||
int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4);
|
||||
*((int32_t *)(island->instructions + kJumpAddress)) = addressOffset;
|
||||
|
||||
msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
|
||||
return err_none;
|
||||
}
|
||||
|
||||
#elif defined(__x86_64__)
|
||||
mach_error_t
|
||||
setBranchIslandTarget_i386(
|
||||
BranchIsland *island,
|
||||
const void *branchTo,
|
||||
char* instructions )
|
||||
{
|
||||
// Copy over the template code.
|
||||
bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
|
||||
|
||||
// Copy original instructions.
|
||||
if (instructions) {
|
||||
bcopy (instructions, island->instructions, kOriginalInstructionsSize);
|
||||
}
|
||||
|
||||
// Fill in the address.
|
||||
*((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo;
|
||||
msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
|
||||
|
||||
return err_none;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
// simplistic instruction matching
|
||||
typedef struct {
|
||||
unsigned int length; // max 15
|
||||
unsigned char mask[15]; // sequence of bytes in memory order
|
||||
unsigned char constraint[15]; // sequence of bytes in memory order
|
||||
} AsmInstructionMatch;
|
||||
|
||||
#if defined(__i386__)
|
||||
static AsmInstructionMatch possibleInstructions[] = {
|
||||
{ 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x????????
|
||||
{ 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret
|
||||
{ 0x1, {0xFF}, {0x90} }, // nop
|
||||
{ 0x1, {0xFF}, {0x55} }, // push %esp
|
||||
{ 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp
|
||||
{ 0x1, {0xFF}, {0x53} }, // push %ebx
|
||||
{ 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp
|
||||
{ 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate
|
||||
{ 0x1, {0xFF}, {0x57} }, // push %edi
|
||||
{ 0x1, {0xFF}, {0x56} }, // push %esi
|
||||
{ 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax
|
||||
{ 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg
|
||||
{ 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg
|
||||
{ 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx
|
||||
{ 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax
|
||||
{ 0x0 }
|
||||
};
|
||||
#elif defined(__x86_64__)
|
||||
static AsmInstructionMatch possibleInstructions[] = {
|
||||
{ 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x????????
|
||||
{ 0x1, {0xFF}, {0x90} }, // nop
|
||||
{ 0x1, {0xF8}, {0x50} }, // push %rX
|
||||
{ 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp
|
||||
{ 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp
|
||||
{ 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp
|
||||
{ 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX
|
||||
{ 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX
|
||||
{ 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg
|
||||
{ 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi)
|
||||
{ 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax
|
||||
{ 0x0 }
|
||||
};
|
||||
#endif
|
||||
|
||||
static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction)
|
||||
{
|
||||
Boolean match = true;
|
||||
|
||||
size_t i;
|
||||
for (i=0; i<instruction->length; i++) {
|
||||
unsigned char mask = instruction->mask[i];
|
||||
unsigned char constraint = instruction->constraint[i];
|
||||
unsigned char codeValue = code[i];
|
||||
|
||||
match = ((codeValue & mask) == constraint);
|
||||
if (!match) break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
static Boolean
|
||||
eatKnownInstructions(
|
||||
unsigned char *code,
|
||||
uint64_t *newInstruction,
|
||||
int *howManyEaten,
|
||||
char *originalInstructions,
|
||||
int *originalInstructionCount,
|
||||
uint8_t *originalInstructionSizes )
|
||||
{
|
||||
Boolean allInstructionsKnown = true;
|
||||
int totalEaten = 0;
|
||||
unsigned char* ptr = code;
|
||||
int remainsToEat = 5; // a JMP instruction takes 5 bytes
|
||||
int instructionIndex = 0;
|
||||
|
||||
if (howManyEaten) *howManyEaten = 0;
|
||||
if (originalInstructionCount) *originalInstructionCount = 0;
|
||||
while (remainsToEat > 0) {
|
||||
Boolean curInstructionKnown = false;
|
||||
|
||||
// See if instruction matches one we know
|
||||
AsmInstructionMatch* curInstr = possibleInstructions;
|
||||
do {
|
||||
if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break;
|
||||
curInstr++;
|
||||
} while (curInstr->length > 0);
|
||||
|
||||
// if all instruction matches failed, we don't know current instruction then, stop here
|
||||
if (!curInstructionKnown) {
|
||||
allInstructionsKnown = false;
|
||||
fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// At this point, we've matched curInstr
|
||||
int eaten = curInstr->length;
|
||||
ptr += eaten;
|
||||
remainsToEat -= eaten;
|
||||
totalEaten += eaten;
|
||||
|
||||
if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten;
|
||||
instructionIndex += 1;
|
||||
if (originalInstructionCount) *originalInstructionCount = instructionIndex;
|
||||
}
|
||||
|
||||
|
||||
if (howManyEaten) *howManyEaten = totalEaten;
|
||||
|
||||
if (originalInstructions) {
|
||||
Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize);
|
||||
|
||||
if (enoughSpaceForOriginalInstructions) {
|
||||
memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP
|
||||
bcopy(code, originalInstructions, totalEaten);
|
||||
} else {
|
||||
// printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allInstructionsKnown) {
|
||||
// save last 3 bytes of first 64bits of codre we'll replace
|
||||
uint64_t currentFirst64BitsOfCode = *((uint64_t *)code);
|
||||
currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation
|
||||
currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL;
|
||||
|
||||
// keep only last 3 instructions bytes, first 5 will be replaced by JMP instr
|
||||
*newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes
|
||||
*newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes
|
||||
}
|
||||
|
||||
return allInstructionsKnown;
|
||||
}
|
||||
|
||||
static void
|
||||
fixupInstructions(
|
||||
void *originalFunction,
|
||||
void *escapeIsland,
|
||||
void *instructionsToFix,
|
||||
int instructionCount,
|
||||
uint8_t *instructionSizes )
|
||||
{
|
||||
int index;
|
||||
for (index = 0;index < instructionCount;index += 1)
|
||||
{
|
||||
if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative
|
||||
{
|
||||
uint32_t offset = (uintptr_t)originalFunction - (uintptr_t)escapeIsland;
|
||||
uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1);
|
||||
*jumpOffsetPtr += offset;
|
||||
}
|
||||
|
||||
originalFunction = (void*)((uintptr_t)originalFunction + instructionSizes[index]);
|
||||
escapeIsland = (void*)((uintptr_t)escapeIsland + instructionSizes[index]);
|
||||
instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__i386__)
|
||||
__asm(
|
||||
".text;"
|
||||
".align 2, 0x90;"
|
||||
"_atomic_mov64:;"
|
||||
" pushl %ebp;"
|
||||
" movl %esp, %ebp;"
|
||||
" pushl %esi;"
|
||||
" pushl %ebx;"
|
||||
" pushl %ecx;"
|
||||
" pushl %eax;"
|
||||
" pushl %edx;"
|
||||
|
||||
// atomic push of value to an address
|
||||
// we use cmpxchg8b, which compares content of an address with
|
||||
// edx:eax. If they are equal, it atomically puts 64bit value
|
||||
// ecx:ebx in address.
|
||||
// We thus put contents of address in edx:eax to force ecx:ebx
|
||||
// in address
|
||||
" mov 8(%ebp), %esi;" // esi contains target address
|
||||
" mov 12(%ebp), %ebx;"
|
||||
" mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address
|
||||
" mov (%esi), %eax;"
|
||||
" mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address
|
||||
" lock; cmpxchg8b (%esi);" // atomic move.
|
||||
|
||||
// restore registers
|
||||
" popl %edx;"
|
||||
" popl %eax;"
|
||||
" popl %ecx;"
|
||||
" popl %ebx;"
|
||||
" popl %esi;"
|
||||
" popl %ebp;"
|
||||
" ret"
|
||||
);
|
||||
#elif defined(__x86_64__)
|
||||
void atomic_mov64(
|
||||
uint64_t *targetAddress,
|
||||
uint64_t value )
|
||||
{
|
||||
*targetAddress = value;
|
||||
}
|
||||
#endif
|
||||
#endif
|
121
xpcom/build/mach_override.h
Normal file
121
xpcom/build/mach_override.h
Normal file
@ -0,0 +1,121 @@
|
||||
/*******************************************************************************
|
||||
mach_override.h
|
||||
Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
|
||||
Some rights reserved: <http://opensource.org/licenses/mit-license.php>
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************//**
|
||||
@mainpage mach_override
|
||||
@author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
|
||||
|
||||
This package, coded in C to the Mach API, allows you to override ("patch")
|
||||
program- and system-supplied functions at runtime. You can fully replace
|
||||
functions with your implementations, or merely head- or tail-patch the
|
||||
original implementations.
|
||||
|
||||
Use it by #include'ing mach_override.h from your .c, .m or .mm file(s).
|
||||
|
||||
@todo Discontinue use of Carbon's MakeDataExecutable() and
|
||||
CompareAndSwap() calls and start using the Mach equivalents, if they
|
||||
exist. If they don't, write them and roll them in. That way, this
|
||||
code will be pure Mach, which will make it easier to use everywhere.
|
||||
Update: MakeDataExecutable() has been replaced by
|
||||
msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but
|
||||
I'm currently unsure if I can link against it. May have to roll in
|
||||
my own version...
|
||||
@todo Stop using an entire 4K high-allocated VM page per 28-byte escape
|
||||
branch island. Done right, this will dramatically speed up escape
|
||||
island allocations when they number over 250. Then again, if you're
|
||||
overriding more than 250 functions, maybe speed isn't your main
|
||||
concern...
|
||||
@todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl
|
||||
first-instructions. Initially, we should refuse to override
|
||||
functions beginning with these instructions. Eventually, we should
|
||||
dynamically rewrite them to make them position-independent.
|
||||
@todo Write mach_unoverride(), which would remove an override placed on a
|
||||
function. Must be multiple-override aware, which means an almost
|
||||
complete rewrite under the covers, because the target address can't
|
||||
be spread across two load instructions like it is now since it will
|
||||
need to be atomically updatable.
|
||||
@todo Add non-rentry variants of overrides to test_mach_override.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef _mach_override_
|
||||
#define _mach_override_
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <mach/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
Returned if the function to be overrided begins with a 'mfctr' instruction.
|
||||
*/
|
||||
#define err_cannot_override (err_local|1)
|
||||
|
||||
/************************************************************************************//**
|
||||
Dynamically overrides the function implementation referenced by
|
||||
originalFunctionAddress with the implentation pointed to by overrideFunctionAddress.
|
||||
Optionally returns a pointer to a "reentry island" which, if jumped to, will resume
|
||||
the original implementation.
|
||||
|
||||
@param originalFunctionAddress -> Required address of the function to
|
||||
override (with overrideFunctionAddress).
|
||||
@param overrideFunctionAddress -> Required address to the overriding
|
||||
function.
|
||||
@param originalFunctionReentryIsland <- Optional pointer to pointer to the
|
||||
reentry island. Can be NULL.
|
||||
@result <- err_cannot_override if the original
|
||||
function's implementation begins with
|
||||
the 'mfctr' instruction.
|
||||
|
||||
************************************************************************************/
|
||||
|
||||
mach_error_t
|
||||
mach_override_ptr(
|
||||
void *originalFunctionAddress,
|
||||
const void *overrideFunctionAddress,
|
||||
void **originalFunctionReentryIsland );
|
||||
|
||||
/************************************************************************************//**
|
||||
|
||||
|
||||
************************************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \
|
||||
{ \
|
||||
static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \
|
||||
static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \
|
||||
class mach_override_class__##ORIGINAL_FUNCTION_NAME { \
|
||||
public: \
|
||||
static kern_return_t override(void *originalFunctionPtr) { \
|
||||
kern_return_t result = err_none; \
|
||||
if (!ORIGINAL_FUNCTION_NAME##_overriden) { \
|
||||
ORIGINAL_FUNCTION_NAME##_overriden = true; \
|
||||
result = mach_override_ptr( (void*)originalFunctionPtr, \
|
||||
(void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \
|
||||
(void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \
|
||||
} \
|
||||
return result; \
|
||||
} \
|
||||
static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS {
|
||||
|
||||
#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \
|
||||
} \
|
||||
}; \
|
||||
\
|
||||
err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // _mach_override_
|
19
xpcom/build/mozPoisonWrite.h
Normal file
19
xpcom/build/mozPoisonWrite.h
Normal file
@ -0,0 +1,19 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 "mozilla/Types.h"
|
||||
|
||||
MOZ_BEGIN_EXTERN_C
|
||||
void MozillaRegisterDebugFD(int fd);
|
||||
void MozillaUnRegisterDebugFD(int fd);
|
||||
MOZ_END_EXTERN_C
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace mozilla {
|
||||
void PoisonWrite();
|
||||
void DisableWritePoisoning();
|
||||
}
|
||||
#endif
|
257
xpcom/build/mozPoisonWriteMac.cpp
Normal file
257
xpcom/build/mozPoisonWriteMac.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 "mozilla/mozPoisonWrite.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "nsTraceRefcntImpl.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "mach_override.h"
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include <sys/uio.h>
|
||||
#include <aio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace {
|
||||
using namespace mozilla;
|
||||
|
||||
struct FuncData {
|
||||
const char *Name; // Name of the function for the ones we use dlsym
|
||||
const void *Wrapper; // The function that we will replace 'Function' with
|
||||
void *Function; // The function that will be replaced with 'Wrapper'
|
||||
void *Buffer; // Will point to the jump buffer that lets us call
|
||||
// 'Function' after it has been replaced.
|
||||
};
|
||||
|
||||
typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
|
||||
void AbortOnBadWrite(int fd, const void *wbuf, size_t count);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_write_temp(int fd, const void *buf, size_t count) {
|
||||
AbortOnBadWrite(fd, buf, count);
|
||||
write_t old_write = (write_t) foo.Buffer;
|
||||
return old_write(fd, buf, count);
|
||||
}
|
||||
|
||||
// FIXME: clang accepts usinging wrap_data_temp<X ## _data> in the struct
|
||||
// initialization. Is this a gcc 4.2 bug?
|
||||
|
||||
// Define a FuncData for a function that can be found without dlsym.
|
||||
#define DEFINE_F_DATA(X) \
|
||||
ssize_t wrap_ ## X (int fd, const void *buf, size_t count); \
|
||||
FuncData X ## _data = { 0, (void*) wrap_ ## X, (void*) X }; \
|
||||
ssize_t wrap_ ## X (int fd, const void *buf, size_t count) { \
|
||||
return wrap_write_temp<X ## _data>(fd, buf, count); \
|
||||
}
|
||||
|
||||
// Define a FuncData for a function that can only be found with dlsym.
|
||||
#define DEFINE_F_DATA_DYN(X, NAME) \
|
||||
ssize_t wrap_ ## X (int fd, const void *buf, size_t count); \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_ ## X }; \
|
||||
ssize_t wrap_ ## X (int fd, const void *buf, size_t count) { \
|
||||
return wrap_write_temp<X ## _data>(fd, buf, count); \
|
||||
}
|
||||
|
||||
// Define a simple FuncData that just aborts.
|
||||
#define DEFINE_F_DATA_ABORT(X) \
|
||||
void wrap_ ## X() { abort(); } \
|
||||
FuncData X ## _data = { 0, (void*) wrap_ ## X, (void*) X }
|
||||
|
||||
// Define a simple FuncData that just aborts for a function that needs dlsym.
|
||||
#define DEFINE_F_DATA_ABORT_DYN(X, NAME) \
|
||||
void wrap_ ## X() { abort(); } \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_ ## X }
|
||||
|
||||
DEFINE_F_DATA_ABORT(aio_write);
|
||||
DEFINE_F_DATA_ABORT(pwrite);
|
||||
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_F_DATA_ABORT_DYN(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
|
||||
DEFINE_F_DATA_ABORT_DYN(writev_UNIX2003, "writev$UNIX2003");
|
||||
DEFINE_F_DATA_ABORT_DYN(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
|
||||
DEFINE_F_DATA_ABORT_DYN(pwrite_UNIX2003, "pwrite$UNIX2003");
|
||||
|
||||
// These exist on 64 bit OS X
|
||||
DEFINE_F_DATA_ABORT_DYN(writev_NOCANCEL, "writev$NOCANCEL");
|
||||
DEFINE_F_DATA_ABORT_DYN(pwrite_NOCANCEL, "pwrite$NOCANCEL");
|
||||
|
||||
DEFINE_F_DATA(write);
|
||||
|
||||
typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt);
|
||||
ssize_t wrap_writev(int fd, const struct iovec *iov, int iovcnt);
|
||||
FuncData writev_data = { 0, (void*) wrap_writev, (void*) writev };
|
||||
ssize_t wrap_writev(int fd, const struct iovec *iov, int iovcnt) {
|
||||
AbortOnBadWrite(fd, 0, iovcnt);
|
||||
writev_t old_write = (writev_t) writev_data.Buffer;
|
||||
return old_write(fd, iov, iovcnt);
|
||||
}
|
||||
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_F_DATA_DYN(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
|
||||
DEFINE_F_DATA_DYN(write_UNIX2003, "write$UNIX2003");
|
||||
|
||||
// These exist on 64 bit OS X
|
||||
DEFINE_F_DATA_DYN(write_NOCANCEL, "write$NOCANCEL");
|
||||
|
||||
FuncData *Functions[] = { &aio_write_data,
|
||||
|
||||
&pwrite_data,
|
||||
&pwrite_NOCANCEL_UNIX2003_data,
|
||||
&pwrite_UNIX2003_data,
|
||||
&pwrite_NOCANCEL_data,
|
||||
|
||||
&write_data,
|
||||
&write_NOCANCEL_UNIX2003_data,
|
||||
&write_UNIX2003_data,
|
||||
&write_NOCANCEL_data,
|
||||
|
||||
&writev_data,
|
||||
&writev_NOCANCEL_UNIX2003_data,
|
||||
&writev_UNIX2003_data,
|
||||
&writev_NOCANCEL_data};
|
||||
|
||||
const int NumFunctions = ArrayLength(Functions);
|
||||
|
||||
std::vector<int>& getDebugFDs() {
|
||||
// We have to use new as some write happen during static destructors
|
||||
// so an static std::vector might be destroyed while we still need it.
|
||||
static std::vector<int> *DebugFDs = new std::vector<int>();
|
||||
return *DebugFDs;
|
||||
}
|
||||
|
||||
struct AutoLockTraits {
|
||||
typedef PRLock *type;
|
||||
const static type empty() {
|
||||
return NULL;
|
||||
}
|
||||
const static void release(type aL) {
|
||||
PR_Unlock(aL);
|
||||
}
|
||||
};
|
||||
|
||||
class MyAutoLock : public Scoped<AutoLockTraits> {
|
||||
public:
|
||||
static PRLock *getDebugFDsLock() {
|
||||
// We have to use something lower level than a mutex. If we don't, we
|
||||
// can get recursive in here when called from logging a call to free.
|
||||
static PRLock *Lock = PR_NewLock();
|
||||
return Lock;
|
||||
}
|
||||
|
||||
MyAutoLock() : Scoped<AutoLockTraits>(getDebugFDsLock()) {
|
||||
PR_Lock(get());
|
||||
}
|
||||
};
|
||||
|
||||
bool PoisoningDisabled = false;
|
||||
void AbortOnBadWrite(int fd, const void *wbuf, size_t count) {
|
||||
if (PoisoningDisabled)
|
||||
return;
|
||||
|
||||
// Ignore writes of zero bytes, firefox does some during shutdown.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// Stdout and Stderr are OK.
|
||||
if(fd == 1 || fd == 2)
|
||||
return;
|
||||
|
||||
struct stat buf;
|
||||
int rv = fstat(fd, &buf);
|
||||
MOZ_ASSERT(rv == 0);
|
||||
|
||||
// FIFOs are used for thread communication during shutdown.
|
||||
if ((buf.st_mode & S_IFMT) == S_IFIFO)
|
||||
return;
|
||||
|
||||
MyAutoLock lockedScope;
|
||||
|
||||
// Debugging FDs are OK
|
||||
std::vector<int> &Vec = getDebugFDs();
|
||||
if (std::find(Vec.begin(), Vec.end(), fd) != Vec.end())
|
||||
return;
|
||||
|
||||
// For writev we pass NULL in wbuf. We should only get here from
|
||||
// dbm, and it uses write, so assert that we have wbuf.
|
||||
MOZ_ASSERT(wbuf);
|
||||
|
||||
// As a really bad hack, accept writes that don't change the on disk
|
||||
// content. This is needed because dbm doesn't keep track of dirty bits
|
||||
// and can end up writing the same data to disk twice. Once when the
|
||||
// user (nss) asks it to sync and once when closing the database.
|
||||
void *wbuf2 = malloc(count);
|
||||
MOZ_ASSERT(wbuf2);
|
||||
off_t pos = lseek(fd, 0, SEEK_CUR);
|
||||
MOZ_ASSERT(pos != -1);
|
||||
ssize_t r = read(fd, wbuf2, count);
|
||||
MOZ_ASSERT(r == count);
|
||||
int cmp = memcmp(wbuf, wbuf2, count);
|
||||
MOZ_ASSERT(cmp == 0);
|
||||
free(wbuf2);
|
||||
off_t pos2 = lseek(fd, pos, SEEK_SET);
|
||||
MOZ_ASSERT(pos2 == pos);
|
||||
}
|
||||
|
||||
// We cannot use destructors to free the lock and the list of debug fds since
|
||||
// we don't control the order the destructors are called. Instead, we use
|
||||
// libc funcion __cleanup callback which runs after the destructors.
|
||||
void (*OldCleanup)();
|
||||
extern "C" void (*__cleanup)();
|
||||
void FinalCleanup() {
|
||||
if (OldCleanup)
|
||||
OldCleanup();
|
||||
delete &getDebugFDs();
|
||||
PR_DestroyLock(MyAutoLock::getDebugFDsLock());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
void MozillaRegisterDebugFD(int fd) {
|
||||
MyAutoLock lockedScope;
|
||||
std::vector<int> &Vec = getDebugFDs();
|
||||
MOZ_ASSERT(std::find(Vec.begin(), Vec.end(), fd) == Vec.end());
|
||||
Vec.push_back(fd);
|
||||
}
|
||||
void MozillaUnRegisterDebugFD(int fd) {
|
||||
MyAutoLock lockedScope;
|
||||
std::vector<int> &Vec = getDebugFDs();
|
||||
std::vector<int>::iterator i = std::find(Vec.begin(), Vec.end(), fd);
|
||||
MOZ_ASSERT(i != Vec.end());
|
||||
Vec.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
void PoisonWrite() {
|
||||
// For now only poison writes in debug builds.
|
||||
#ifndef DEBUG
|
||||
return;
|
||||
#endif
|
||||
|
||||
PoisoningDisabled = false;
|
||||
OldCleanup = __cleanup;
|
||||
__cleanup = FinalCleanup;
|
||||
|
||||
for (int i = 0; i < NumFunctions; ++i) {
|
||||
FuncData *d = Functions[i];
|
||||
if (!d->Function)
|
||||
d->Function = dlsym(RTLD_DEFAULT, d->Name);
|
||||
if (!d->Function)
|
||||
continue;
|
||||
mach_error_t t = mach_override_ptr(d->Function, d->Wrapper,
|
||||
&d->Buffer);
|
||||
MOZ_ASSERT(t == err_none);
|
||||
}
|
||||
}
|
||||
void DisableWritePoisoning() {
|
||||
PoisoningDisabled = true;
|
||||
}
|
||||
}
|
18
xpcom/build/mozPoisonWriteStub.cpp
Normal file
18
xpcom/build/mozPoisonWriteStub.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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/. */
|
||||
|
||||
namespace mozilla {
|
||||
void PoisonWrite() {
|
||||
}
|
||||
void DisableWritePoisoning() {
|
||||
}
|
||||
}
|
||||
extern "C" {
|
||||
void MozillaRegisterDebugFD(int fd) {
|
||||
}
|
||||
void MozillaUnRegisterDebugFD(int fd) {
|
||||
}
|
||||
}
|
@ -106,6 +106,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
|
||||
|
||||
#include "nsChromeRegistry.h"
|
||||
#include "nsChromeProtocolHandler.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#include "mozilla/scache/StartupCache.h"
|
||||
|
||||
@ -647,6 +648,8 @@ ShutdownXPCOM(nsIServiceManager* servMgr)
|
||||
|
||||
nsCycleCollector_shutdown();
|
||||
|
||||
mozilla::PoisonWrite();
|
||||
|
||||
if (moduleLoaders) {
|
||||
bool more;
|
||||
nsCOMPtr<nsISupports> el;
|
||||
|
Loading…
Reference in New Issue
Block a user