Files
UnrealEngine/Engine/Source/ThirdParty/PLCrashReporter/PLCrashReporter-1.12.0/Tests/PLCrashMachExceptionServerTests.m
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

283 lines
12 KiB
Objective-C

/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include "PLCrashFeatureConfig.h"
#if PLCRASH_FEATURE_MACH_EXCEPTIONS
#import "SenTestCompat.h"
#import "PLCrashMachExceptionServer.h"
#import "PLCrashMachExceptionPort.h"
#import "PLCrashHostInfo.h"
#import "PLCrashAsync.h"
#include <sys/mman.h> // mprotect()
@interface PLCrashMachExceptionServerTests : SenTestCase {
plcrash_mach_exception_port_set_t _task_ports;
plcrash_mach_exception_port_set_t _thread_ports;
}
@end
@implementation PLCrashMachExceptionServerTests
/* Page-sized, page aligned, and allocated/deallocated with vm_allocate()/vm_deallocate via -setUp/-tearDown.
* We use this page to test exception handler behavior by adjusting its page protections and triggering crashes. */
static uint8_t *crash_page;
- (void) setUp {
/*
* Reset the current exception ports. Our tests interfere with any observing
* debuggers, so we remove any task and thread exception ports here, and then
* restore them in -tearDown.
*/
kern_return_t kr;
kr = task_swap_exception_ports(mach_task_self(),
EXC_MASK_BAD_ACCESS,
MACH_PORT_NULL,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE,
_task_ports.masks,
&_task_ports.count,
_task_ports.ports,
_task_ports.behaviors,
_task_ports.flavors);
STAssertEquals(kr, KERN_SUCCESS, @"Failed to reset task ports");
kr = thread_swap_exception_ports(pl_mach_thread_self(),
EXC_MASK_BAD_ACCESS,
MACH_PORT_NULL,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE,
_thread_ports.masks,
&_thread_ports.count,
_thread_ports.ports,
_thread_ports.behaviors,
_thread_ports.flavors);
STAssertEquals(kr, KERN_SUCCESS, @"Failed to reset thread ports");
/* Allocate a test page that can be used to test exception handler behavior by adjusting its page protections
* and triggering crashes via read/write (or execution) into the page. */
vm_address_t crash_page_addr;
kr = vm_allocate(mach_task_self(), &crash_page_addr, PAGE_SIZE, VM_PROT_READ|VM_PROT_WRITE);
STAssertEquals(KERN_SUCCESS, kr, @"Failed to allocate test page: %d", kr);
crash_page = (uint8_t *) crash_page_addr;
}
- (void) tearDown {
kern_return_t kr;
/* Deallocate our test page */
kr = vm_deallocate(mach_task_self(), (vm_address_t) crash_page, PAGE_SIZE);
STAssertEquals(KERN_SUCCESS, kr, @"Failed to deallocate test page: %d", kr);
/* Restore the original exception ports */
for (mach_msg_type_number_t i = 0; i < _task_ports.count; i++) {
if (!MACH_PORT_VALID(_task_ports.ports[i]))
continue;
kr = task_set_exception_ports(mach_task_self(), _task_ports.masks[i], _task_ports.ports[i], _task_ports.behaviors[i], _task_ports.flavors[i]);
STAssertEquals(kr, KERN_SUCCESS, @"Failed to set task ports");
}
for (mach_msg_type_number_t i = 0; i < _thread_ports.count; i++) {
if (!MACH_PORT_VALID(_thread_ports.ports[i]))
continue;
kr = thread_set_exception_ports(pl_mach_thread_self(), _thread_ports.masks[i], _thread_ports.ports[i], _thread_ports.behaviors[i], _thread_ports.flavors[i]);
STAssertEquals(kr, KERN_SUCCESS, @"Failed to set thread ports");
}
}
static kern_return_t exception_callback (task_t task,
thread_t thread,
exception_type_t exception_type,
mach_exception_data_t code,
mach_msg_type_number_t code_count,
void *context)
{
mprotect(crash_page, sizeof(crash_page), PROT_READ|PROT_WRITE);
if (code_count != 2) {
crash_page[1] = 0xFA;
} else if (code[1] != (uintptr_t) crash_page) {
crash_page[1] = 0xFB;
} else {
// Success
crash_page[1] = 0xFE;
}
BOOL *didRun = (BOOL *) context;
if (didRun != NULL)
*didRun = YES;
return KERN_SUCCESS;
}
/**
* Test inserting/removing the task mach exception server from the handler chain.
*/
- (void) testTaskServerInsertion {
NSError *error;
PLCrashMachExceptionServer *server = [[PLCrashMachExceptionServer alloc] initWithCallBack: exception_callback
context: NULL
error: &error];
STAssertNotNil(server, @"Failed to initialize server");
PLCrashMachExceptionPort *port = [server exceptionPortWithMask: EXC_MASK_BAD_ACCESS error: &error];
STAssertNotNil(port, @"Failed to fetch server port: %@", error);
STAssertTrue([port registerForTask: mach_task_self()
previousPortSet: NULL
error: &error], @"Failed to configure handler: %@", error);
mprotect(crash_page, sizeof(crash_page), 0);
/* If the test doesn't lock up here, it's working */
crash_page[0] = 0xCA;
STAssertEquals(crash_page[0], (uint8_t)0xCA, @"Page should have been set to test value");
STAssertEquals(crash_page[1], (uint8_t)0xFE, @"Crash callback did not run");
}
/**
* Test inserting/removing the mach exception server from the handler chain.
*/
- (void) testThreadServerInsertion {
NSError *error;
BOOL taskRan = false;
BOOL threadRan = false;
PLCrashMachExceptionServer *task = [[PLCrashMachExceptionServer alloc] initWithCallBack: exception_callback
context: &taskRan
error: &error];
STAssertNotNil(task, @"Failed to initialize server");
PLCrashMachExceptionServer *thr = [[PLCrashMachExceptionServer alloc] initWithCallBack: exception_callback
context: &threadRan
error: &error];
STAssertNotNil(thr, @"Failed to initialize server");
PLCrashMachExceptionPort *taskPort = [task exceptionPortWithMask: EXC_MASK_BAD_ACCESS error: &error];
STAssertNotNil(taskPort, @"Failed to fetch server port: %@", error);
PLCrashMachExceptionPort *thrPort = [thr exceptionPortWithMask: EXC_MASK_BAD_ACCESS error: &error];
STAssertNotNil(thrPort, @"Failed to fetch server port: %@", error);
STAssertTrue([taskPort registerForTask: mach_task_self()
previousPortSet: NULL
error: &error], @"Failed to configure handler: %@", error);
STAssertTrue([thrPort registerForThread: pl_mach_thread_self()
previousPortSet: NULL
error: &error], @"Failed to configure handler: %@", error);
mprotect(crash_page, sizeof(crash_page), 0);
/* If the test doesn't lock up here, it's working */
crash_page[0] = 0xCA;
STAssertEquals(crash_page[0], (uint8_t)0xCA, @"Page should have been set to test value");
STAssertEquals(crash_page[1], (uint8_t)0xFE, @"Crash callback did not run");
STAssertFalse(taskRan, @"Task handler ran");
STAssertTrue(threadRan, @"Thread-specific handler did not run");
}
/**
* Test forwarding implementation
*/
- (void) testForwardException {
NSError *error;
/* Set up a test server */
BOOL didRun = false;
PLCrashMachExceptionServer *server = [[PLCrashMachExceptionServer alloc] initWithCallBack: exception_callback
context: &didRun
error: &error];
STAssertNotNil(server, @"Failed to initialize server");
PLCrashMachExceptionPort *port = [server exceptionPortWithMask: EXC_MASK_BAD_ACCESS error: &error];
STAssertNotNil(port, @"Failed to fetch server port: %@", error);
STAssertTrue([port registerForTask: mach_task_self()
previousPortSet: NULL
error: &error], @"Failed to configure handler: %@", error);
/* Fetch the server's port set */
PLCrashMachExceptionPortSet *portSet = [PLCrashMachExceptionPort exceptionPortsForTask: mach_task_self() mask: EXC_MASK_BAD_ACCESS error: &error];
/* Attempt to forward the exception */
mach_exception_data_type_t codes[2];
codes[0] = 0x1;
codes[1] = 0x2;
plcrash_mach_exception_port_set_t port_set = portSet.asyncSafeRepresentation;
kern_return_t kt = PLCrashMachExceptionForward(mach_task_self(),
pl_mach_thread_self(),
EXC_BAD_ACCESS,
codes,
2,
&port_set);
/* Forwarding is documented as broken in the case where 64-bit codes are used, but
* the mach_exc_* 64-bit APIs are not available. */
#if (PL_MACH64_EXC_API || !PL_MACH64_EXC_CODES)
STAssertEquals(KERN_SUCCESS, kt, @"Callback did not return KERN_SUCCESS");
STAssertTrue(didRun, @"Calback was not executed");
#else
STAssertNotEquals(KERN_SUCCESS, kt, @"Callback returned KERN_SUCCESS despite missing mach_exc* APIs");
#endif
}
/**
* Test basic copying of the send right.
*/
- (void) testCopySendRight {
NSError *error;
PLCrashMachExceptionServer *server = [[PLCrashMachExceptionServer alloc] initWithCallBack: exception_callback
context: NULL
error: &error];
STAssertNotNil(server, @"Failed to initialize server");
mach_port_t sendRight = [server copySendRightForServerAndReturningError: &error];
STAssertTrue(MACH_PORT_VALID(sendRight), @"Failed to copy send right: %@", error);
STAssertEquals(KERN_SUCCESS, mach_port_deallocate(mach_task_self(), sendRight), @"Failed to deallocate send right");
}
@end
#endif /* PLCRASH_FEATURE_MACH_EXCEPTIONS */