Files
plcrashreporter/Tests/PLCrashAsyncThreadTests.m
2020-04-27 12:49:57 +03:00

482 lines
22 KiB
Objective-C

/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2013 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.
*/
#import "SenTestCompat.h"
#import "PLCrashAsyncThread.h"
#import "PLCrashTestThread.h"
#import <pthread.h>
@interface PLCrashAsyncThreadTests : SenTestCase {
@private
plcrash_test_thread_t _thr_args;
}
@end
@implementation PLCrashAsyncThreadTests
- (void) setUp {
plcrash_test_thread_spawn(&_thr_args);
}
- (void) tearDown {
plcrash_test_thread_stop(&_thr_args);
}
- (void) testGetRegName {
plcrash_async_thread_state_t ts;
plcrash_async_thread_state_mach_thread_init(&ts, pthread_mach_thread_np(_thr_args.thread));
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&ts); i++) {
const char *name = plcrash_async_thread_state_get_reg_name(&ts, i);
STAssertNotNULL(name, @"Register name for %d is NULL", i);
STAssertNotEquals((size_t)0, strlen(name), @"Register name for %d is 0 length", i);
STAssertNotEquals((uint32_t)PLCRASH_REG_INVALID, (uint32_t)i, @"Register name is assigned to invalid pseudo-register");
}
}
- (void) testClearVolatileRegisters {
plcrash_async_thread_state_t ts;
plcrash_async_thread_state_mach_thread_init(&ts, pthread_mach_thread_np(_thr_args.thread));
/* Verify that clearing volatile registers clears some, but not all, registers */
size_t live_count = 0;
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&ts); i++) {
if (plcrash_async_thread_state_has_reg(&ts, i))
live_count++;
};
plcrash_async_thread_state_clear_volatile_regs(&ts);
size_t nv_count = 0;
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&ts); i++) {
if (plcrash_async_thread_state_has_reg(&ts, i))
nv_count++;
};
/* In theory, these tests could fail if ALL or NONE registers are callee-preserved. I can't think of an ABI
* on the planet where that is true, but in such a case, this test will fail and require updating */
STAssertLessThan(nv_count, live_count, @"Failed to clear any registers");
STAssertGreaterThan(nv_count, (size_t)0, @"Cleared all registers");
#define REQ_REG(_reg) STAssertTrue(plcrash_async_thread_state_has_reg(&ts, _reg), @"Missing required register");
#if defined(__arm64__)
REQ_REG(PLCRASH_ARM64_X19);
REQ_REG(PLCRASH_ARM64_X20);
REQ_REG(PLCRASH_ARM64_X21);
REQ_REG(PLCRASH_ARM64_X22);
REQ_REG(PLCRASH_ARM64_X23);
REQ_REG(PLCRASH_ARM64_X24);
REQ_REG(PLCRASH_ARM64_X25);
REQ_REG(PLCRASH_ARM64_X26);
REQ_REG(PLCRASH_ARM64_X27);
REQ_REG(PLCRASH_ARM64_X28);
#ifdef __APPLE__
REQ_REG(PLCRASH_ARM64_FP);
#else
#error Define OS frame pointer behavior as per AAPCS64 Section 5.2.3
#endif
STAssertEquals((size_t)11, nv_count, @"Incorrect number of registers preserved");
#elif defined(__arm__)
REQ_REG(PLCRASH_ARM_R4);
REQ_REG(PLCRASH_ARM_R5);
REQ_REG(PLCRASH_ARM_R6);
REQ_REG(PLCRASH_ARM_R7);
REQ_REG(PLCRASH_ARM_R8);
REQ_REG(PLCRASH_ARM_R10);
REQ_REG(PLCRASH_ARM_R11);
STAssertEquals((size_t)7, nv_count, @"Incorrect number of registers preserved");
#elif defined(__i386__)
REQ_REG(PLCRASH_X86_EBX);
REQ_REG(PLCRASH_X86_EBP);
REQ_REG(PLCRASH_X86_ESI);
REQ_REG(PLCRASH_X86_EDI);
REQ_REG(PLCRASH_X86_ESP);
STAssertEquals((size_t)5, nv_count, @"Incorrect number of registers preserved");
#elif defined(__x86_64__)
REQ_REG(PLCRASH_X86_64_RBX);
REQ_REG(PLCRASH_X86_64_RSP);
REQ_REG(PLCRASH_X86_64_RBP);
REQ_REG(PLCRASH_X86_64_R12);
REQ_REG(PLCRASH_X86_64_R13);
REQ_REG(PLCRASH_X86_64_R14);
REQ_REG(PLCRASH_X86_64_R15);
STAssertEquals((size_t)7, nv_count, @"Incorrect number of registers preserved");
#else
#error Add architecture support
#endif
#undef REQ_REG
}
- (void) testGetSetRegister {
plcrash_async_thread_state_t ts;
plcrash_async_thread_state_mach_thread_init(&ts, pthread_mach_thread_np(_thr_args.thread));
size_t regcount = plcrash_async_thread_state_get_reg_count(&ts);
/* Verify that all registers are marked as available */
STAssertTrue(__builtin_popcountl(ts.valid_regs) >= regcount, @"Incorrect number of 1 bits");
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&ts); i++) {
STAssertTrue(plcrash_async_thread_state_has_reg(&ts, i), @"Register should be marked as set");
}
/* Clear all registers */
plcrash_async_thread_state_clear_all_regs(&ts);
STAssertEquals(ts.valid_regs, (uint64_t)0, @"Registers not marked as clear");
/* Now set+get each individually */
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&ts); i++) {
plcrash_greg_t reg;
plcrash_async_thread_state_set_reg(&ts, i, 5);
reg = plcrash_async_thread_state_get_reg(&ts, i);
STAssertEquals(reg, (plcrash_greg_t)5, @"Unexpected register value");
STAssertTrue(plcrash_async_thread_state_has_reg(&ts, i), @"Register should be marked as set");
STAssertEquals(__builtin_popcountl(ts.valid_regs), i+1, @"Incorrect number of 1 bits");
}
}
/**
* Test mapping of DWARF register values.
*/
- (void) testMapDwarfRegister {
plcrash_async_thread_state_t ts;
#define CHECKREG(plreg, dwreg) do { \
plcrash_regnum_t regnum; \
STAssertTrue(plcrash_async_thread_state_map_dwarf_to_reg(&ts, dwreg, &regnum), @"Failed to map DWARF register"); \
STAssertEquals((plcrash_regnum_t)plreg, regnum, @"Incorrect register mapping for " # plreg); \
\
uint64_t dw_result; \
STAssertTrue(plcrash_async_thread_state_map_reg_to_dwarf(&ts, plreg, &dw_result), @"Failed to map register to DWARF"); \
STAssertEquals((uint64_t)dwreg, dw_result, @"Native register number does not map back to the expected DWARF register number"); \
} while (0)
#if PLCRASH_ASYNC_THREAD_X86_SUPPORT
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
CHECKREG(PLCRASH_X86_EAX, 0);
CHECKREG(PLCRASH_X86_ECX, 1);
CHECKREG(PLCRASH_X86_EDX, 2);
CHECKREG(PLCRASH_X86_EBX, 3);
CHECKREG(PLCRASH_X86_EBP, 4);
CHECKREG(PLCRASH_X86_ESP, 5);
CHECKREG(PLCRASH_X86_ESI, 6);
CHECKREG(PLCRASH_X86_EDI, 7);
CHECKREG(PLCRASH_X86_EIP, 8);
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86_64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
CHECKREG(PLCRASH_X86_64_RAX, 0);
CHECKREG(PLCRASH_X86_64_RDX, 1);
CHECKREG(PLCRASH_X86_64_RCX, 2);
CHECKREG(PLCRASH_X86_64_RBX, 3);
CHECKREG(PLCRASH_X86_64_RSI, 4);
CHECKREG(PLCRASH_X86_64_RDI, 5);
CHECKREG(PLCRASH_X86_64_RBP, 6);
CHECKREG(PLCRASH_X86_64_RSP, 7);
CHECKREG(PLCRASH_X86_64_R8, 8);
CHECKREG(PLCRASH_X86_64_R9, 9);
CHECKREG(PLCRASH_X86_64_R10, 10);
CHECKREG(PLCRASH_X86_64_R11, 11);
CHECKREG(PLCRASH_X86_64_R12, 12);
CHECKREG(PLCRASH_X86_64_R13, 13);
CHECKREG(PLCRASH_X86_64_R14, 14);
CHECKREG(PLCRASH_X86_64_R15, 15);
CHECKREG(PLCRASH_X86_64_RFLAGS, 49);
CHECKREG(PLCRASH_X86_64_CS, 51);
CHECKREG(PLCRASH_X86_64_FS, 54);
CHECKREG(PLCRASH_X86_64_GS, 55);
#endif /* PLCRASH_ASYNC_THREAD_X86_SUPPORT */
#if PLCRASH_ASYNC_THREAD_ARM_SUPPORT
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_ARM), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
CHECKREG(PLCRASH_ARM_R0, 0);
CHECKREG(PLCRASH_ARM_R1, 1);
CHECKREG(PLCRASH_ARM_R2, 2);
CHECKREG(PLCRASH_ARM_R3, 3);
CHECKREG(PLCRASH_ARM_R4, 4);
CHECKREG(PLCRASH_ARM_R5, 5);
CHECKREG(PLCRASH_ARM_R6, 6);
CHECKREG(PLCRASH_ARM_R7, 7);
CHECKREG(PLCRASH_ARM_R8, 8);
CHECKREG(PLCRASH_ARM_R9, 9);
CHECKREG(PLCRASH_ARM_R10, 10);
CHECKREG(PLCRASH_ARM_R11, 11);
CHECKREG(PLCRASH_ARM_R12, 12);
CHECKREG(PLCRASH_ARM_SP, 13);
CHECKREG(PLCRASH_ARM_LR, 14);
CHECKREG(PLCRASH_ARM_PC, 15);
#endif
#undef CHECKREG
}
/* Test plcrash_async_thread_state_init() */
- (void) testEmptyInit {
plcrash_async_thread_state_t ts;
#if PLCRASH_ASYNC_THREAD_X86_SUPPORT
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
STAssertEquals(ts.x86_state.thread.tsh.count, (int)x86_THREAD_STATE32_COUNT, @"Incorrect count");
STAssertEquals(ts.x86_state.thread.tsh.flavor, x86_THREAD_STATE32, @"Incorrect flavor");
STAssertEquals(ts.x86_state.exception.esh.count, (int)x86_EXCEPTION_STATE32_COUNT, @"Incorrect count");
STAssertEquals(ts.x86_state.exception.esh.flavor, x86_EXCEPTION_STATE32, @"Incorrect flavor");
STAssertEquals(ts.stack_direction, PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN, @"Incorrect stack direction");
STAssertEquals(ts.greg_size, (size_t)4, @"Incorrect gpreg size");
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_X86_64), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
STAssertEquals(ts.x86_state.thread.tsh.count, (int)x86_THREAD_STATE64_COUNT, @"Incorrect count");
STAssertEquals(ts.x86_state.thread.tsh.flavor, x86_THREAD_STATE64, @"Incorrect flavor");
STAssertEquals(ts.x86_state.exception.esh.count, (int)x86_EXCEPTION_STATE64_COUNT, @"Incorrect count");
STAssertEquals(ts.x86_state.exception.esh.flavor, x86_EXCEPTION_STATE64, @"Incorrect flavor");
STAssertEquals(ts.stack_direction, PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN, @"Incorrect stack direction");
STAssertEquals(ts.greg_size, (size_t)8, @"Incorrect gpreg size");
#endif /* PLCRASH_ASYNC_THREAD_X86_SUPPORT */
#if PLCRASH_ASYNC_THREAD_ARM_SUPPORT
STAssertEquals(plcrash_async_thread_state_init(&ts, CPU_TYPE_ARM), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
STAssertEquals(ts.stack_direction, PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN, @"Incorrect stack direction");
STAssertEquals(ts.greg_size, (size_t)4, @"Incorrect gpreg size");
#endif
}
/* Test plcrash_async_thread_state_ucontext_init() */
- (void) testThreadStateContextInit {
plcrash_async_thread_state_t thr_state;
pl_mcontext_t mctx;
memset(&mctx, 'A', sizeof(mctx));
plcrash_async_thread_state_mcontext_init(&thr_state, &mctx);
/* Verify that all registers are marked as available */
size_t regcount = plcrash_async_thread_state_get_reg_count(&thr_state);
STAssertTrue(__builtin_popcountl(thr_state.valid_regs) >= regcount, @"Incorrect number of 1 bits");
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&thr_state); i++) {
STAssertTrue(plcrash_async_thread_state_has_reg(&thr_state, i), @"Register should be marked as set");
}
#if defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) && defined(__LP64__)
STAssertTrue(memcmp(&thr_state.arm_state.thread.ts_64, &mctx.__ss, sizeof(thr_state.arm_state.thread.ts_64)) == 0, @"Incorrectly copied");
#elif defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT)
STAssertTrue(memcmp(&thr_state.arm_state.thread.ts_32, &mctx.__ss, sizeof(thr_state.arm_state.thread.ts_32)) == 0, @"Incorrectly copied");
#elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) && defined(__LP64__)
STAssertEquals(thr_state.x86_state.thread.tsh.count, (int)x86_THREAD_STATE64_COUNT, @"Incorrect thread state count for a 64-bit system");
STAssertEquals(thr_state.x86_state.thread.tsh.flavor, (int)x86_THREAD_STATE64, @"Incorrect thread state flavor for a 64-bit system");
STAssertTrue(memcmp(&thr_state.x86_state.thread.uts.ts64, &mctx.__ss, sizeof(thr_state.x86_state.thread.uts.ts64)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.exception.esh.count, (int) x86_EXCEPTION_STATE64_COUNT, @"Incorrect thread state count for a 64-bit system");
STAssertEquals(thr_state.x86_state.exception.esh.flavor, (int) x86_EXCEPTION_STATE64, @"Incorrect thread state flavor for a 64-bit system");
STAssertTrue(memcmp(&thr_state.x86_state.exception.ues.es64, &mctx.__es, sizeof(thr_state.x86_state.exception.ues.es64)) == 0, @"Incorrectly copied");
#elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT)
STAssertEquals(thr_state.x86_state.thread.tsh.count, (int)x86_THREAD_STATE32_COUNT, @"Incorrect thread state count for a 32-bit system");
STAssertEquals(thr_state.x86_state.thread.tsh.flavor, (int)x86_THREAD_STATE32, @"Incorrect thread state flavor for a 32-bit system");
STAssertTrue(memcmp(&thr_state.x86_state.thread.uts.ts32, &mctx.__ss, sizeof(thr_state.x86_state.thread.uts.ts32)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.exception.esh.count, (int)x86_EXCEPTION_STATE32_COUNT, @"Incorrect thread state count for a 32-bit system");
STAssertEquals(thr_state.x86_state.exception.esh.flavor, (int)x86_EXCEPTION_STATE32, @"Incorrect thread state flavor for a 32-bit system");
STAssertTrue(memcmp(&thr_state.x86_state.exception.ues.es32, &mctx.__es, sizeof(thr_state.x86_state.exception.ues.es32)) == 0, @"Incorrectly copied");
#else
#error Add platform support
#endif
}
/* Test plframe_thread_state_thread_init() */
- (void) testThreadStateThreadInit {
plcrash_async_thread_state_t thr_state;
mach_msg_type_number_t state_count;
thread_t thr;
/* Spawn a test thread */
thr = pthread_mach_thread_np(_thr_args.thread);
thread_suspend(thr);
/* Fetch the thread state */
STAssertEquals(plcrash_async_thread_state_mach_thread_init(&thr_state, thr), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
/* Verify that all registers are marked as available */
size_t regcount = plcrash_async_thread_state_get_reg_count(&thr_state);
STAssertTrue(__builtin_popcountl(thr_state.valid_regs) >= regcount, @"Incorrect number of 1 bits");
for (int i = 0; i < plcrash_async_thread_state_get_reg_count(&thr_state); i++) {
STAssertTrue(plcrash_async_thread_state_has_reg(&thr_state, i), @"Register should be marked as set");
}
/* Test the results */
#if defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) && defined(__LP64__)
arm_thread_state64_t local_thr_state;
state_count = ARM_THREAD_STATE64_COUNT;
STAssertEquals(thread_get_state(thr, ARM_THREAD_STATE64, (thread_state_t) &local_thr_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.arm_state.thread.ts_64, &local_thr_state, sizeof(thr_state.arm_state.thread.ts_64)) == 0, @"Incorrectly copied");
#elif defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT)
arm_thread_state_t local_thr_state;
state_count = ARM_THREAD_STATE_COUNT;
STAssertEquals(thread_get_state(thr, ARM_THREAD_STATE, (thread_state_t) &local_thr_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.arm_state.thread.ts_32, &local_thr_state, sizeof(thr_state.arm_state.thread.ts_32)) == 0, @"Incorrectly copied");
#elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) && defined(__LP64__)
state_count = x86_THREAD_STATE64_COUNT;
x86_thread_state64_t local_thr_state;
STAssertEquals(thread_get_state(thr, x86_THREAD_STATE64, (thread_state_t) &local_thr_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.x86_state.thread.uts.ts64, &local_thr_state, sizeof(thr_state.x86_state.thread.uts.ts64)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.thread.tsh.count, (int)x86_THREAD_STATE64_COUNT, @"Incorrect thread state count for a 64-bit system");
STAssertEquals(thr_state.x86_state.thread.tsh.flavor, (int)x86_THREAD_STATE64, @"Incorrect thread state flavor for a 64-bit system");
state_count = x86_EXCEPTION_STATE64_COUNT;
x86_exception_state64_t local_exc_state;
STAssertEquals(thread_get_state(thr, x86_EXCEPTION_STATE64, (thread_state_t) &local_exc_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.x86_state.exception.ues.es64, &local_exc_state, sizeof(thr_state.x86_state.exception.ues.es64)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.exception.esh.count, (int) x86_EXCEPTION_STATE64_COUNT, @"Incorrect thread state count for a 64-bit system");
STAssertEquals(thr_state.x86_state.exception.esh.flavor, (int) x86_EXCEPTION_STATE64, @"Incorrect thread state flavor for a 64-bit system");
#elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT)
state_count = x86_THREAD_STATE32_COUNT;
x86_thread_state32_t local_thr_state;
STAssertEquals(thread_get_state(thr, x86_THREAD_STATE32, (thread_state_t) &local_thr_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.x86_state.thread.uts.ts32, &local_thr_state, sizeof(thr_state.x86_state.thread.uts.ts32)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.thread.tsh.count, (int)x86_THREAD_STATE32_COUNT, @"Incorrect thread state count for a 64-bit system");
STAssertEquals(thr_state.x86_state.thread.tsh.flavor, (int)x86_THREAD_STATE32, @"Incorrect thread state flavor for a 32-bit system");
state_count = x86_EXCEPTION_STATE32_COUNT;
x86_exception_state32_t local_exc_state;
STAssertEquals(thread_get_state(thr, x86_EXCEPTION_STATE32, (thread_state_t) &local_exc_state, &state_count), KERN_SUCCESS, @"Failed to fetch thread state");
STAssertTrue(memcmp(&thr_state.x86_state.exception.ues.es32, &local_exc_state, sizeof(thr_state.x86_state.exception.ues.es32)) == 0, @"Incorrectly copied");
STAssertEquals(thr_state.x86_state.exception.esh.count, (int) x86_EXCEPTION_STATE32_COUNT, @"Incorrect thread state count for a 32-bit system");
STAssertEquals(thr_state.x86_state.exception.esh.flavor, (int) x86_EXCEPTION_STATE32, @"Incorrect thread state flavor for a 32-bit system");
#else
#error Add platform support
#endif
/* Verify the platform metadata */
#ifdef __LP64__
STAssertEquals(plcrash_async_thread_state_get_greg_size(&thr_state), (size_t)8, @"Incorrect greg size");
#else
STAssertEquals(plcrash_async_thread_state_get_greg_size(&thr_state), (size_t)4, @"Incorrect greg size");
#endif
#if defined(__arm64__) || defined(__arm__) || defined(__i386__) || defined(__x86_64__)
// This is true on just about every modern platform
STAssertEquals(plcrash_async_thread_state_get_stack_direction(&thr_state), PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN, @"Incorrect stack growth direction");
#else
#error Add platform support!
#endif
/* Clean up */
thread_resume(thr);
}
__attribute__ ((noinline)) static uintptr_t getPC () {
return (uintptr_t) __builtin_return_address(0);
}
static plcrash_error_t write_current_thread_callback (plcrash_async_thread_state_t *state, void *context) {
plcrash_async_thread_state_t *result = context;
plcrash_async_thread_state_copy(result, state);
return PLCRASH_ESUCCESS;
}
/**
* Test fetching the current thread's state
*/
- (void) testFetchCurrentThreadState {
/* Write the crash report */
plcrash_async_thread_state_t thr_state;
plcrash_error_t ret = plcrash_async_thread_state_current(write_current_thread_callback, &thr_state);
uintptr_t expectedPC = getPC();
STAssertEquals(PLCRASH_ESUCCESS, ret, @"Crash log failed");
/* Validate PC. This check is inexact and fragile, as otherwise we would need to carefully instrument the
* call to plcrash_log_writer_write_curthread() in order to determine the exact PC value. */
STAssertTrue(expectedPC - plcrash_async_thread_state_get_reg(&thr_state, PLCRASH_REG_IP) <= 40, @"PC value not within reasonable range");
/* Fetch stack info for validation */
uint8_t *stackaddr = pthread_get_stackaddr_np(pthread_self());
size_t stacksize = pthread_get_stacksize_np(pthread_self());
/* Verify that the stack pointer is sane */
plcrash_greg_t sp = plcrash_async_thread_state_get_reg(&thr_state, PLCRASH_REG_SP);
if (plcrash_async_thread_state_get_stack_direction(&thr_state) == PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN) {
STAssertTrue((uint8_t *)sp < stackaddr && (uint8_t *) sp >= stackaddr-stacksize, @"Stack pointer outside of stack range");
} else {
STAssertTrue((uint8_t *)sp > stackaddr && (uint8_t *) sp < stackaddr+stacksize, @"Stack pointer outside of stack range");
}
/* Architecture-specific validations */
#if __arm__ || __arm64__
# if __arm__
plcrash_regnum_t lrnum = PLCRASH_ARM_LR;
# else
plcrash_regnum_t lrnum = PLCRASH_ARM64_LR;
# endif
/* Validate LR */
void *retaddr = __builtin_return_address(0);
uintptr_t lr = plcrash_async_thread_state_get_reg(&thr_state, lrnum);
STAssertEquals(retaddr, (void *)lr, @"Incorrect lr: %p", (void *) lr);
#endif
}
/**
* Test copying of a thread state.
*/
- (void) testThreadStateCopy {
plcrash_async_thread_state_t thr_state;
thread_t thr;
/* Spawn a test thread */
thr = pthread_mach_thread_np(_thr_args.thread);
thread_suspend(thr);
/* Fetch the thread state */
STAssertEquals(plcrash_async_thread_state_mach_thread_init(&thr_state, thr), PLCRASH_ESUCCESS, @"Failed to initialize thread state");
/* Try a copy */
plcrash_async_thread_state_t thr_copy;
plcrash_async_thread_state_copy(&thr_copy, &thr_state);
STAssertEquals(memcmp(&thr_copy, &thr_state, sizeof(thr_copy)), 0, @"Did not correctly copy thread state");
/* Clean up */
thread_resume(thr);
}
@end