Loading...
--- Libc/Libc-1725.40.4/tests/thread_stack_pcs.c
+++ Libc/Libc-1583.60.2/tests/thread_stack_pcs.c
@@ -1,17 +1,8 @@
#include <darwintest.h>
-#include <mach-o/dyld_priv.h>
#include <thread_stack_pcs.h>
-#include <execinfo.h>
#include <fake_swift_async.h>
#include <pthread/private.h>
#include <stdint.h>
-#include <stdlib.h>
-
-#if __arm64e__
-#define TARGET_CPU_ARM64E true
-#else
-#define TARGET_CPU_ARM64E false
-#endif
static void *test_function_ret_addr;
@@ -52,8 +43,8 @@
// 4 level1_func
T_EXPECT_EQ(frames, 5, "Got the right number of async frames");
T_EXPECT_EQ(callstack[2], __builtin_return_address(0), "Found fake_async_frame");
- T_EXPECT_EQ(callstack[3], ptrauth_strip((void*)&level2_func, ptrauth_key_function_pointer) + 1, "Found level2_func");
- T_EXPECT_EQ(callstack[4], ptrauth_strip((void*)&level1_func, ptrauth_key_function_pointer) + 1, "Found level1_func");
+ T_EXPECT_EQ(callstack[3], ptrauth_strip(&level2_func, ptrauth_key_function_pointer) + 1, "Found level2_func");
+ T_EXPECT_EQ(callstack[4], ptrauth_strip(&level1_func, ptrauth_key_function_pointer) + 1, "Found level1_func");
} else {
unsigned frames;
int ret = thread_stack_pcs(callstack, 16, &frames);
@@ -112,527 +103,3 @@
fake_async_frame(true);
}
-//
-// split_stack_call_impl assembly implementation.
-//
-
-#if defined(__x86_64__)
-asm("\t.private_extern _split_stack_call_impl\n\t"
- ".p2align 4, 0x90\n"
-"_split_stack_call_impl:\n\t"
- "## new_stack -> %rdi\n\t"
- "## new_stack_size -> %rsi\n\t"
- "## func -> %rdx\n\t"
- "## data -> %rcx\n\t"
- ".cfi_startproc\n\t"
- "pushq %rbp\n\t"
- ".cfi_def_cfa_offset 16\n\t"
- ".cfi_offset %rbp, -16\n\t"
- "movq %rsp, %rbp\n\t"
- ".cfi_def_cfa_register %rbp\n\t"
- "# setup %rsp in the new stack space\n\t"
- "leaq (%rdi, %rsi), %rsp\n\t"
- "# move 'data' argument in %rdi\n\t"
- "movq %rcx, %rdi\n\t"
- "# move return address in %rsi\n\t"
- "movq 0x8(%rbp), %rsi\n\t"
- "# indirect jump to the function\n\t"
- "callq *%rdx\n\t"
- "# restore original %rsp\n\t"
- "movq %rbp, %rsp\n\t"
- "popq %rbp\n\t"
- "retq\n\t"
- ".cfi_endproc");
-#elif defined(__arm64__)
-#ifdef __arm64e__
-asm(".macro FN_PROLOG\n\t"
- "pacibsp\n"
-".endmacro\n"
-".macro FN_EPILOG\n\t"
- "retab\n"
-".endmacro\n"
-".macro RETADDR reg\n\t"
- "mov \\reg, x30\n\t"
- "xpaci \\reg\n"
-".endmacro\n"
-".macro INDCALL reg\n\t"
- "blraaz \\reg\n"
-".endmacro");
-
-#else
-asm(".macro FN_PROLOG\n"
-".endmacro\n"
-".macro FN_EPILOG\n\t"
- "ret\n"
-".endmacro\n"
-".macro RETADDR reg\n\t"
- "mov \\reg, x30\n"
-".endmacro\n"
-".macro INDCALL reg\n\t"
- "blr \\reg\n"
-".endmacro");
-#endif
-asm("\t.private_extern _split_stack_call_impl\n\t"
- ".p2align 2\n"
-"_split_stack_call_impl:\n\t"
- "; new_stack -> x0\n\t"
- "; new_stack_size -> x1\n\t"
- "; func -> x2\n\t"
- "; data -> x3\n\t"
- ".cfi_startproc\n\t"
- "FN_PROLOG\n\t"
- "stp x29, x30, [sp, #-16]!\n\t"
- "mov x29, sp\n\t"
- ".cfi_def_cfa w29, 16\n\t"
- ".cfi_offset w30, -8\n\t"
- ".cfi_offset w29, -16\n\t"
- "; setup `sp` in the new stack space\n\t"
- "add sp, x0, x1\n\t"
- "; move 'data' argument in `x0`\n\t"
- "mov x0, x3\n\t"
- "; move our return address (in lr) into `x1`\n\t"
- "RETADDR x1\n\t"
- "; indirect call to the function\n\t"
- "INDCALL x2\n\t"
- "; restore original `sp`\n\t"
- "mov sp, x29\n\t"
- "ldp x29, x30, [sp], #16\n\t"
- "FN_EPILOG\n\t"
- ".cfi_endproc");
-#else
-#error "Unsupported architecture"
-#endif
-
-// Passes ctx to f in the first argument, and the return address of
-// _split_stack_call_impl in the second
-void split_stack_call_impl(void * stack, size_t stack_size, void (*f)(void*, void*), void *ctx);
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-void non_default_stack_test_l2(void *ret2, void *ret3, void *ret4)
-{
- void *ret1 = __builtin_return_address(0);
-
- vm_address_t callstack[16];
- unsigned frames;
- thread_stack_pcs(callstack, 16, &frames);
-
- // The 4 pcs we expect
- // 0 thread_stack_pcs
- // 1 non_default_stack_test_l2
- // 2 non_default_stack_test (in ret1)
- // 3 split_stack_call_impl (in ret2)
- // 4 testmain (in ret3)
- // 5 darwintest testrunner (in ret4)
- // ...
- T_EXPECT_GE(frames, 4, "Got the right number of stack frames");
- T_EXPECT_EQ((void *)callstack[2], ret1, "Found non_default_stack_test_l2 frame");
- T_EXPECT_EQ((void *)callstack[3], ret2, "Found non_default_stack_test frame");
- T_EXPECT_EQ((void *)callstack[4], ret3, "Found split_stack_call_impl frame");
- T_EXPECT_EQ((void *)callstack[5], ret4, "Found test main frame");
-}
-
-__attribute__((noinline))
-void non_default_stack_test(void *testmain_retaddr, void *stack_call_retaddr)
-{
- void *our_retaddr = __builtin_return_address(0);
- non_default_stack_test_l2(our_retaddr, stack_call_retaddr, testmain_retaddr);
-}
-
-T_DECL(thread_stack_pcs_alt_stack, "tests thread_stack_pcs when called from a non-default stack")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- void *ret_addr = __builtin_return_address(0);
- split_stack_call_impl(stack, stack_size, non_default_stack_test, ret_addr);
-
- free(stack);
-}
-
-// Sets some high bits in the stacked link register to corrupt the fp chain,
-// then calls into backtrace() to make sure we don't crash inside libc.
-void call_func_with_broken_fp_chain(void (*f)(void), void *ssi_retaddr);
-
-
-// On 64 bit systems, increment the stacked frame pointer by 16GB to make it
-// unlikely that it lands on a mapped address. On 32 bit systems, we need to
-// decrease this to 1GB to avoid overflowing to the same (legal) value
-#ifdef __LP64__
-#define FP_BREAK_OFFSET "0x400000000"
-#else
-#define FP_BREAK_OFFSET "0x40000000"
-#endif
-
-#if defined(__x86_64__)
-asm("\t.private_extern _call_func_with_broken_fp_chain\n\t"
- ".p2align 4, 0x90\n"
-"_call_func_with_broken_fp_chain:\n\t"
- "## ctx -> %rdi\n\t"
- "## ssi_retaddr (unused) -> %rsi\n\t"
- ".cfi_startproc\n\t"
- "pushq %rbp\n\t"
- ".cfi_def_cfa_offset 16\n\t"
- ".cfi_offset %rbp, -16\n\t"
- "movq %rsp, %rbp\n\t"
- "# increment bp by 16GB to get callee to push a broken fp chain\n\t"
- "movq $" FP_BREAK_OFFSET ", %rsi\n\t"
- "addq %rsi, %rbp\n\t"
- ".cfi_def_cfa_register %rbp\n\t"
- "# indirect call to f\n\t"
- "callq *%rdi\n\t"
- "# decrement bp by 16GB before moving it to sp\n\t"
- "movq $" FP_BREAK_OFFSET ", %rsi\n\t"
- "subq %rsi, %rbp\n\t"
- "# restore original %rsp\n\t"
- "movq %rbp, %rsp\n\t"
- "popq %rbp\n\t"
- "retq\n\t"
- ".cfi_endproc");
-#elif defined(__arm64__)
-asm("\t.private_extern _call_func_with_broken_fp_chain\n\t"
- ".p2align 2\n"
-"_call_func_with_broken_fp_chain:\n\t"
- "; f -> x0\n\t"
- "; ssi_retaddr (unused) -> x1\n\t"
- ".cfi_startproc\n\t"
- "FN_PROLOG\n\t"
- "; store fp+16GB on the stack (clobbers unused arg1)\n\t"
- "mov x1, #" FP_BREAK_OFFSET "\n\t"
- "add x1, x29, x1\n\t"
- "stp x1, x30, [sp, #-16]!\n\t"
- "mov x29, sp\n\t"
- ".cfi_def_cfa w29, 16\n\t"
- ".cfi_offset w30, -8\n\t"
- ".cfi_offset w29, -16\n\t"
- "; indirect call to f\n\t"
- "INDCALL x0\n\t"
- "; restore original `sp`\n\t"
- "mov sp, x29\n\t"
- "ldp x29, x30, [sp], #16\n\t"
- "; Remove 16GB offset from fp on stack\n\t"
- "mov x1, #" FP_BREAK_OFFSET "\n\t"
- "sub x29, x29, x1\n\t"
- "FN_EPILOG\n\t"
- ".cfi_endproc");
-#else
-#error "Unsupported architecture"
-#endif
-
-static void call_backtrace_helper(void)
-{
- // called on non-default stack with a broken fp chain.
- void * backtrace_buffer[10];
- int frames = backtrace(&backtrace_buffer[0], 10);
- // Without the maximum frame size, we'd typically crash here. Assert that
- // we didn't enumerate past the broken fp linkage, so that we're guaranteed
- // to assert at least one thing in this test case and get a pass
- T_ASSERT_LT(frames, 5, "Didn't enumerate too many frames\n", frames);
-}
-
-T_DECL(thread_stack_pcs_alt_broken_fp_chain,
- "Check that backtrace() doesn't crash when fp chain is broken")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- split_stack_call_impl(stack, stack_size,
- (void *)&call_func_with_broken_fp_chain, (void *)&call_backtrace_helper);
-
- free(stack);
-}
-
-struct many_frames_args {
- vm_address_t *buffer;
- unsigned buffer_size;
- unsigned recurse_depth;
- unsigned ret_frames;
- bool use_backtrace;
-};
-
-__attribute__((noinline))
-__attribute__((disable_tail_calls))
-void recursive_thread_stack_pcs(struct many_frames_args* args, void *unused)
-{
- T_QUIET; T_ASSERT_NE(args->recurse_depth, 0, "Invalid recurse depth");
- if (--args->recurse_depth) {
- return recursive_thread_stack_pcs(args, NULL);
- } else {
- if (args->use_backtrace) {
- args->ret_frames = backtrace(args->buffer, args->buffer_size);
- } else {
- thread_stack_pcs(args->buffer, args->buffer_size, &args->ret_frames);
- }
- }
-}
-
-T_DECL(thread_stack_pcs_alt_exact_frames,
- "Enumerate a max of 16 frames with 16 frames in the non-default stack")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- // Oversize buffer to avoid breaking the stack and crashing
- vm_address_t buffer[32] = { 0 };
- struct many_frames_args arg = {
- .buffer = &buffer,
- .buffer_size = 16,
- // The top frame is thread_stack_pcs, so only recurse 15 times
- .recurse_depth = 15,
- .ret_frames = 0,
- .use_backtrace = false,
- };
-
- split_stack_call_impl(stack, stack_size, recursive_thread_stack_pcs, &arg);
-
- T_ASSERT_LE(arg.ret_frames, 16, "Too many frames walked");
- T_ASSERT_EQ(buffer[16], 0, "Buffer not overwritten");
-
- free(stack);
-}
-
-T_DECL(thread_stack_pcs_non_default_many_frames,
- "Enumerate a max of 16 frames with more than that in non-default stack")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- vm_address_t buffer[32] = { 0 };
- struct many_frames_args arg = {
- .buffer = &buffer,
- .buffer_size = 16,
- .recurse_depth = 20,
- .ret_frames = 0,
- .use_backtrace = false,
- };
-
- split_stack_call_impl(stack, stack_size, recursive_thread_stack_pcs, &arg);
-
- T_ASSERT_LE(arg.ret_frames, 16, "Too many frames walked");
- T_ASSERT_EQ(buffer[16], 0, "Buffer not overwritten");
-
- free(stack);
-}
-
-T_DECL(thread_stack_pcs_alt_stack_with_backtrace,
- "Check that we can skip the top frame in non-default stack")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- vm_address_t buffer[16] = { 0 };
- struct many_frames_args arg = {
- .buffer = &buffer,
- .buffer_size = 16,
- .recurse_depth = 20,
- .ret_frames = 0,
- // backtrace() sets skip to 1 (to drop the backtrace() frame)
- .use_backtrace = true,
- };
-
- split_stack_call_impl(stack, stack_size, recursive_thread_stack_pcs, &arg);
-
- T_ASSERT_EQ(arg.ret_frames, 16, "Walked 16 frames");
- // The top two frames should both be within recursive_thread_stack_pcs.
- // They'll be slightly different from each other, but should both be within
- // the same function. To check the latter, assume that they should be
- // within 1k of each other
- uintptr_t start =
- (uintptr_t)ptrauth_strip((void*)&recursive_thread_stack_pcs,
- ptrauth_key_function_pointer);
- const vm_address_t max_allowed_difference = 1024;
- T_ASSERT_GT((uintptr_t)buffer[0], start,
- "Top frame is in recursive_thread_stack_pcs");
- T_ASSERT_GT((uintptr_t)buffer[1], start,
- "Second frame is in recursive_thread_stack_pcs");
- T_ASSERT_NE((uintptr_t)buffer[0], (uintptr_t)buffer[1],
- "Top frame and second frame differ (0x%lx, 0x%lx)", buffer[0], buffer[1]);
- T_ASSERT_EQ((uintptr_t)buffer[1], (uintptr_t)buffer[2],
- "Frames 2 and 3 are equal (recursive function)");
- if (buffer[0] > buffer[1]) {
- T_ASSERT_LE(buffer[0] - buffer[1], max_allowed_difference,
- "Top two frames are close to each other");
- } else {
- T_ASSERT_LE(buffer[1] - buffer[0], max_allowed_difference,
- "Top two frames are close to each other");
- }
-
- free(stack);
-}
-
-T_DECL(thread_stack_pcs_alt_stack_few_frames,
- "Check that backtrace() works with fewer than max stack frames")
-{
- const size_t stack_size = 32768;
- void *stack = malloc(stack_size);
- T_ASSERT_NOTNULL(stack, "Allocated non-default stack %p", stack);
-
- vm_address_t buffer[16] = { 0 };
- struct many_frames_args arg = {
- .buffer = &buffer,
- .buffer_size = 16,
- .recurse_depth = 3,
- .ret_frames = 0,
- // backtrace() sets skip to 1 (to drop the backtrace() frame)
- .use_backtrace = true,
- };
-
- split_stack_call_impl(stack, stack_size, recursive_thread_stack_pcs, &arg);
-
- T_ASSERT_LT(arg.ret_frames, 16, "Walked fewer than 16 frames");
- // The top two frames should both be within recursive_thread_stack_pcs.
- // They'll be slightly different from each other, but should both be within
- // the same function. To check the latter, assume that they should be
- // within 1k of each other
- uintptr_t start =
- (uintptr_t)ptrauth_strip((void*)&recursive_thread_stack_pcs,
- ptrauth_key_function_pointer);
- const vm_address_t max_allowed_difference = 1024;
- T_ASSERT_GT((uintptr_t)buffer[0], start,
- "Top frame is in recursive_thread_stack_pcs");
- T_ASSERT_GT((uintptr_t)buffer[1], start,
- "Second frame is in recursive_thread_stack_pcs");
- T_ASSERT_NE((uintptr_t)buffer[0], (uintptr_t)buffer[1],
- "Top frame and second frame differ (0x%lx, 0x%lx)", buffer[0], buffer[1]);
- T_ASSERT_EQ((uintptr_t)buffer[1], (uintptr_t)buffer[2],
- "Frames 2 and 3 are equal (recursive function)");
- if (buffer[0] > buffer[1]) {
- T_ASSERT_LE(buffer[0] - buffer[1], max_allowed_difference,
- "Top two frames are close to each other");
- } else {
- T_ASSERT_LE(buffer[1] - buffer[0], max_allowed_difference,
- "Top two frames are close to each other");
- }
-
- free(stack);
-}
-
-#if defined(__arm64e__)
-
-struct dyld_stack_context
-{
- // We might go dyld->regular->dyld->...
- // These track the next stack location to push a new alternative/regular frame
- void* nextAlternativeStackAddr;
- void* nextRegularStackAddr;
-
- // For the test methods to record their data in
- void* thread_stack_pcs_dyld_stack_retaddr;
- void* dyld_call_alternative_stack_retaddr;
- void* dyld_call_regular_stack_retaddr;
- void* dyld_call_alternative_stack_again_retaddr;
- void* dyld_call_regular_stack_again_retaddr;
-};
-
-__attribute__((noinline))
-__attribute__((naked))
-__attribute__((not_tail_called))
-static void callAndSwapStack(void* nextStackPtr, void** prevStackPtr, void (*callback)(struct dyld_stack_context*), struct dyld_stack_context* context)
-{
- asm volatile("pacibsp\n"
- "mov x16, sp\n"
- "ldr x8, [x1]\n" // load the old value in prevStackPtr
- "str x16, [x1]\n" // save next stack value to prevStackPtr
- "mov x17, x0\n"
- "pacdb x16, x17\n" // sign the old sp
- "sub x17, x17, #0x30\n" // subtract space from stack
- "stp x1, x8, [x17, #0x00]\n" // save prevStackPtr and its old target value
- "stp xzr, x16, [x17, #0x10]\n" // save old sp
- "stp x29, x30, [x17, #0x20]\n" // save fp, lr
- "mov sp, x17\n" // switch to new stack
- "add x29, x17, #0x20\n" // switch to new frame
- "mov x0, x3\n" // move context in to the first argument
- "blraaz x2\n" // call the function
- "ldp x1, x8, [sp, #0x00]\n" // load prevStackPtr and its current value when we started this function
- "ldp x29, x30, [sp, #0x20]\n" // restore fp, lr
- "ldp xzr, x16, [sp, #0x10]\n" // load old sp
- "add sp, sp, #0x30\n" // move the stack back up before the auth
- "autdb x16, sp\n" // auth old sp
- "mov sp, x16\n" // restore old sp
- "str x8, [x1]\n" // restore the old value in prevStackPtr
- "retab\n");
-}
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-static void thread_stack_pcs_dyld_stack_test(struct dyld_stack_context* context)
-{
- void *ret1 = __builtin_return_address(0);
-
- vm_address_t* callstack[16];
- unsigned frames;
- thread_stack_pcs(callstack, 16, &frames);
-
- // The pcs we expect
- // 0 thread_stack_pcs
- // 1 thread_stack_pcs_dyld_stack_test
- // 2 callAndSwapStack
- // 3 dyld_call_regular_stack_again
- // 4 callAndSwapStack
- // 5 dyld_call_alternative_stack_again
- // 6 callAndSwapStack
- // 7 dyld_call_regular_stack
- // 8 callAndSwapStack
- // 9 dyld_call_alternative_stack
- // 10 testmain
- // 11 darwintest testrunner
- // ...
- T_EXPECT_GE(frames, 10, "Got the right number of stack frames");
- T_EXPECT_EQ(callstack[4], context->dyld_call_regular_stack_again_retaddr, "Found dyld_call_regular_stack_again frame");
- T_EXPECT_EQ(callstack[6], context->dyld_call_alternative_stack_again_retaddr, "Found dyld_call_alternative_stack_again frame");
- T_EXPECT_EQ(callstack[8], context->dyld_call_regular_stack_again_retaddr, "Found dyld_call_regular_stack frame");
- T_EXPECT_EQ(callstack[10], context->dyld_call_alternative_stack_retaddr, "Found dyld_call_alternative_stack frame");
-}
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-static void dyld_call_regular_stack_again(struct dyld_stack_context* context)
-{
- context->dyld_call_regular_stack_again_retaddr = __builtin_return_address(0);
- callAndSwapStack(context->nextRegularStackAddr, &context->nextAlternativeStackAddr, &thread_stack_pcs_dyld_stack_test, context);
-}
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-static void dyld_call_alternative_stack_again(struct dyld_stack_context* context)
-{
- context->dyld_call_alternative_stack_again_retaddr = __builtin_return_address(0);
- callAndSwapStack(context->nextAlternativeStackAddr, &context->nextRegularStackAddr, &dyld_call_regular_stack_again, context);
-}
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-static void dyld_call_regular_stack(struct dyld_stack_context* context)
-{
- context->dyld_call_regular_stack_retaddr = __builtin_return_address(0);
- callAndSwapStack(context->nextRegularStackAddr, &context->nextAlternativeStackAddr, &dyld_call_alternative_stack_again, context);
-}
-
-__attribute__((noinline)) __attribute__((not_tail_called))
-static void dyld_call_alternative_stack(struct dyld_stack_context* context)
-{
- context->dyld_call_alternative_stack_retaddr = __builtin_return_address(0);
- callAndSwapStack(context->nextAlternativeStackAddr, &context->nextRegularStackAddr, &dyld_call_regular_stack, context);
-}
-
-T_DECL(thread_stack_pcs_dyld_stack, "tests thread_stack_pcs when called from the dyld stack",
- T_META_ENVVAR("DYLD_INSERT_LIBRARIES=@executable_path/thread_stack_pcs_helper"),
- T_META_ENABLED(TARGET_CPU_ARM64E && !TARGET_OS_OSX))
-{
- struct dyld_stack_context context;
- const void *dyldstacktop = NULL;
- const void *dyldstackbot = NULL;
-
- _dyld_stack_range(&dyldstackbot, &dyldstacktop);
-
- context.nextAlternativeStackAddr = dyldstacktop;
- context.nextRegularStackAddr = NULL;
- context.thread_stack_pcs_dyld_stack_retaddr = __builtin_return_address(0);
- context.dyld_call_alternative_stack_retaddr = NULL;
- context.dyld_call_regular_stack_retaddr = NULL;
- context.dyld_call_alternative_stack_again_retaddr = NULL;
- context.dyld_call_regular_stack_again_retaddr = NULL;
-
- dyld_call_alternative_stack(&context);
-}
-#endif // defined(__arm64e__)