| // -*- C++ -*- | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // TODO: Investigate this failure on x86_64 macOS back deployment | |
| // XFAIL: stdlib=system && target=x86_64-apple-macosx{{10.9|10.10|10.11|10.12|10.13|10.14|10.15|11.0|12.0}} | |
| // TODO: Figure out why this fails with Memory Sanitizer. | |
| // XFAIL: msan | |
| #include <libunwind.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| void backtrace(int lower_bound) { | |
| unw_context_t context; | |
| unw_getcontext(&context); | |
| unw_cursor_t cursor; | |
| unw_init_local(&cursor, &context); | |
| char buffer[1024]; | |
| unw_word_t offset = 0; | |
| int n = 0; | |
| do { | |
| n++; | |
| if (unw_get_proc_name(&cursor, buffer, sizeof(buffer), &offset) == 0) { | |
| fprintf(stderr, "Frame %d: %s+%p\n", n, buffer, (void*)offset); | |
| } else { | |
| fprintf(stderr, "Frame %d: Could not get name for cursor\n", n); | |
| } | |
| if (n > 100) { | |
| abort(); | |
| } | |
| } while (unw_step(&cursor) > 0); | |
| if (n < lower_bound) { | |
| abort(); | |
| } | |
| } | |
| __attribute__((noinline)) void test1(int i) { | |
| fprintf(stderr, "starting %s\n", __func__); | |
| backtrace(i); | |
| fprintf(stderr, "finished %s\n", __func__); // ensure return address is saved | |
| } | |
| __attribute__((noinline)) void test2(int i, int j) { | |
| fprintf(stderr, "starting %s\n", __func__); | |
| backtrace(i); | |
| test1(j); | |
| fprintf(stderr, "finished %s\n", __func__); // ensure return address is saved | |
| } | |
| __attribute__((noinline)) void test3(int i, int j, int k) { | |
| fprintf(stderr, "starting %s\n", __func__); | |
| backtrace(i); | |
| test2(j, k); | |
| fprintf(stderr, "finished %s\n", __func__); // ensure return address is saved | |
| } | |
| void test_no_info() { | |
| unw_context_t context; | |
| unw_getcontext(&context); | |
| unw_cursor_t cursor; | |
| unw_init_local(&cursor, &context); | |
| unw_proc_info_t info; | |
| int ret = unw_get_proc_info(&cursor, &info); | |
| if (ret != UNW_ESUCCESS) | |
| abort(); | |
| // Set the IP to an address clearly outside any function. | |
| void *ip = 0; | |
| #if defined(__PTRAUTH__) || __has_feature(ptrauth_calls) | |
| unw_word_t sp; | |
| if (unw_get_reg(&cursor, UNW_REG_SP, &sp) != UNW_ESUCCESS) | |
| abort(); | |
| ip = ptrauth_sign_unauthenticated(ip, ptrauth_key_return_address, (void *)sp); | |
| #endif | |
| unw_set_reg(&cursor, UNW_REG_IP, (unw_word_t)ip); | |
| ret = unw_get_proc_info(&cursor, &info); | |
| if (ret != UNW_ENOINFO) | |
| abort(); | |
| } | |
| void test_reg_names() { | |
| unw_context_t context; | |
| unw_getcontext(&context); | |
| unw_cursor_t cursor; | |
| unw_init_local(&cursor, &context); | |
| int max_reg_num = -100; | |
| #if defined(__i386__) | |
| max_reg_num = 7; | |
| #elif defined(__x86_64__) | |
| max_reg_num = 32; | |
| #endif | |
| const char prefix[] = "unknown"; | |
| for (int i = -2; i < max_reg_num; ++i) { | |
| if (strncmp(prefix, unw_regname(&cursor, i), sizeof(prefix) - 1) == 0) | |
| abort(); | |
| } | |
| if (strncmp(prefix, unw_regname(&cursor, max_reg_num + 1), | |
| sizeof(prefix) - 1) != 0) | |
| abort(); | |
| } | |
| #if defined(__x86_64__) | |
| void test_reg_get_set() { | |
| unw_context_t context; | |
| unw_getcontext(&context); | |
| unw_cursor_t cursor; | |
| unw_init_local(&cursor, &context); | |
| for (int i = 0; i < 17; ++i) { | |
| const unw_word_t set_value = 7; | |
| if (unw_set_reg(&cursor, i, set_value) != UNW_ESUCCESS) | |
| abort(); | |
| unw_word_t get_value = 0; | |
| if (unw_get_reg(&cursor, i, &get_value) != UNW_ESUCCESS) | |
| abort(); | |
| if (set_value != get_value) | |
| abort(); | |
| } | |
| } | |
| void test_fpreg_get_set() { | |
| unw_context_t context; | |
| unw_getcontext(&context); | |
| unw_cursor_t cursor; | |
| unw_init_local(&cursor, &context); | |
| // get/set is not implemented for x86_64 fpregs. | |
| for (int i = 17; i < 33; ++i) { | |
| const unw_fpreg_t set_value = 7; | |
| if (unw_set_fpreg(&cursor, i, set_value) != UNW_EBADREG) | |
| abort(); | |
| unw_fpreg_t get_value = 0; | |
| if (unw_get_fpreg(&cursor, i, &get_value) != UNW_EBADREG) | |
| abort(); | |
| } | |
| } | |
| #else | |
| void test_reg_get_set() {} | |
| void test_fpreg_get_set() {} | |
| #endif | |
| int main(int, char**) { | |
| test1(3); | |
| test2(3, 4); | |
| test3(3, 4, 5); | |
| test_no_info(); | |
| test_reg_names(); | |
| test_reg_get_set(); | |
| test_fpreg_get_set(); | |
| return 0; | |
| } |