Mirror of BoringSSL (grpc依赖)
https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
790 lines
24 KiB
790 lines
24 KiB
/* Copyright (c) 2018, Google Inc. |
|
* |
|
* Permission to use, copy, modify, and/or distribute this software for any |
|
* purpose with or without fee is hereby granted, provided that the above |
|
* copyright notice and this permission notice appear in all copies. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ |
|
|
|
#include "abi_test.h" |
|
|
|
#include <stdarg.h> |
|
#include <stdio.h> |
|
|
|
#include <algorithm> |
|
#include <array> |
|
|
|
#include <openssl/mem.h> |
|
#include <openssl/rand.h> |
|
#include <openssl/span.h> |
|
|
|
#if defined(OPENSSL_X86_64) && defined(SUPPORTS_ABI_TEST) |
|
#if defined(OPENSSL_LINUX) && defined(BORINGSSL_HAVE_LIBUNWIND) |
|
#define SUPPORTS_UNWIND_TEST |
|
#define UNW_LOCAL_ONLY |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <libunwind.h> |
|
#include <pthread.h> |
|
#include <signal.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <unistd.h> |
|
#elif defined(OPENSSL_WINDOWS) |
|
#define SUPPORTS_UNWIND_TEST |
|
OPENSSL_MSVC_PRAGMA(warning(push, 3)) |
|
#include <windows.h> |
|
#include <dbghelp.h> |
|
OPENSSL_MSVC_PRAGMA(warning(pop)) |
|
#endif |
|
#endif // X86_64 && SUPPORTS_ABI_TEST |
|
|
|
// FIPS mode breaks unwind tests. See https://crbug.com/boringssl/289. |
|
#if defined(BORINGSSL_FIPS) |
|
#undef SUPPORTS_UNWIND_TEST |
|
#endif |
|
|
|
|
|
namespace abi_test { |
|
|
|
namespace internal { |
|
|
|
static bool g_unwind_tests_enabled = false; |
|
|
|
std::string FixVAArgsString(const char *str) { |
|
std::string ret = str; |
|
size_t idx = ret.find(','); |
|
if (idx == std::string::npos) { |
|
return ret + "()"; |
|
} |
|
size_t idx2 = idx + 1; |
|
while (idx2 < ret.size() && ret[idx2] == ' ') { |
|
idx2++; |
|
} |
|
while (idx > 0 && ret[idx - 1] == ' ') { |
|
idx--; |
|
} |
|
return ret.substr(0, idx) + "(" + ret.substr(idx2) + ")"; |
|
} |
|
|
|
#if defined(SUPPORTS_ABI_TEST) |
|
// ForEachMismatch calls |func| for each register where |a| and |b| differ. |
|
template <typename Func> |
|
static void ForEachMismatch(const CallerState &a, const CallerState &b, |
|
const Func &func) { |
|
#define CALLER_STATE_REGISTER(type, name) \ |
|
if (a.name != b.name) { \ |
|
func(#name); \ |
|
} |
|
LOOP_CALLER_STATE_REGISTERS() |
|
#undef CALLER_STATE_REGISTER |
|
} |
|
|
|
// ReadUnwindResult adds the results of the most recent unwind test to |out|. |
|
static void ReadUnwindResult(Result *out); |
|
|
|
crypto_word_t RunTrampoline(Result *out, crypto_word_t func, |
|
const crypto_word_t *argv, size_t argc, |
|
bool unwind) { |
|
CallerState state; |
|
RAND_bytes(reinterpret_cast<uint8_t *>(&state), sizeof(state)); |
|
|
|
unwind &= g_unwind_tests_enabled; |
|
CallerState state2 = state; |
|
crypto_word_t ret = abi_test_trampoline(func, &state2, argv, argc, unwind); |
|
#if defined(OPENSSL_X86_64) || defined(OPENSSL_X86) |
|
// Query and clear the direction flag early, so negative tests do not |
|
// interfere with |malloc|. |
|
bool direction_flag = abi_test_get_and_clear_direction_flag(); |
|
#endif // OPENSSL_X86_64 || OPENSSL_X86 |
|
|
|
*out = Result(); |
|
ForEachMismatch(state, state2, [&](const char *reg) { |
|
out->errors.push_back(std::string(reg) + " was not restored after return"); |
|
}); |
|
#if defined(OPENSSL_X86_64) || defined(OPENSSL_X86) |
|
// Linux and Windows ABIs for x86 require the direction flag be cleared on |
|
// return. (Some OpenSSL assembly preserves it, which is stronger, but we only |
|
// require what is specified by the ABI so |CHECK_ABI| works with C compiler |
|
// output.) |
|
if (direction_flag) { |
|
out->errors.emplace_back("Direction flag set after return"); |
|
} |
|
#endif // OPENSSL_X86_64 || OPENSSL_X86 |
|
if (unwind) { |
|
ReadUnwindResult(out); |
|
} |
|
return ret; |
|
} |
|
#endif // SUPPORTS_ABI_TEST |
|
|
|
#if defined(SUPPORTS_UNWIND_TEST) |
|
// We test unwind metadata by running the function under test with the trap flag |
|
// set. This results in |SIGTRAP| and |EXCEPTION_SINGLE_STEP| on Linux and |
|
// Windows, respectively. We hande these and verify libunwind or the Windows |
|
// unwind APIs unwind successfully. |
|
|
|
// IsAncestorStackFrame returns true if |a_sp| is an ancestor stack frame of |
|
// |b_sp|. |
|
static bool IsAncestorStackFrame(crypto_word_t a_sp, crypto_word_t b_sp) { |
|
#if defined(OPENSSL_X86_64) |
|
// The stack grows down, so ancestor stack frames have higher addresses. |
|
return a_sp > b_sp; |
|
#else |
|
#error "unknown architecture" |
|
#endif |
|
} |
|
|
|
// Implement some string formatting utilties. Ideally we would use |snprintf|, |
|
// but this is called in a signal handler and |snprintf| is not async-signal- |
|
// safe. |
|
|
|
#if !defined(OPENSSL_WINDOWS) |
|
static std::array<char, DECIMAL_SIZE(crypto_word_t) + 1> WordToDecimal( |
|
crypto_word_t v) { |
|
std::array<char, DECIMAL_SIZE(crypto_word_t) + 1> ret; |
|
size_t len = 0; |
|
do { |
|
ret[len++] = '0' + v % 10; |
|
v /= 10; |
|
} while (v != 0); |
|
for (size_t i = 0; i < len / 2; i++) { |
|
std::swap(ret[i], ret[len - 1 - i]); |
|
} |
|
ret[len] = '\0'; |
|
return ret; |
|
} |
|
#endif // !OPENSSL_WINDOWS |
|
|
|
static std::array<char, sizeof(crypto_word_t) * 2 + 1> WordToHex( |
|
crypto_word_t v) { |
|
static const char kHex[] = "0123456789abcdef"; |
|
std::array<char, sizeof(crypto_word_t) * 2 + 1> ret; |
|
for (size_t i = sizeof(crypto_word_t) - 1; i < sizeof(crypto_word_t); i--) { |
|
uint8_t b = v & 0xff; |
|
v >>= 8; |
|
ret[i * 2] = kHex[b >> 4]; |
|
ret[i * 2 + 1] = kHex[b & 0xf]; |
|
} |
|
ret[sizeof(crypto_word_t) * 2] = '\0'; |
|
return ret; |
|
} |
|
|
|
static void StrCatSignalSafeImpl(bssl::Span<char> out) {} |
|
|
|
template <typename... Args> |
|
static void StrCatSignalSafeImpl(bssl::Span<char> out, const char *str, |
|
Args... args) { |
|
OPENSSL_strlcat(out.data(), str, out.size()); |
|
StrCatSignalSafeImpl(out, args...); |
|
} |
|
|
|
template <typename... Args> |
|
static void StrCatSignalSafe(bssl::Span<char> out, Args... args) { |
|
if (out.empty()) { |
|
return; |
|
} |
|
out[0] = '\0'; |
|
StrCatSignalSafeImpl(out, args...); |
|
} |
|
|
|
template <typename... Args> |
|
[[noreturn]] static void FatalError(Args... args) { |
|
// We cannot use |snprintf| here because it is not async-signal-safe. |
|
char buf[512]; |
|
StrCatSignalSafe(buf, args..., "\n"); |
|
#if defined(OPENSSL_WINDOWS) |
|
HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); |
|
if (stderr_handle != INVALID_HANDLE_VALUE) { |
|
DWORD unused; |
|
WriteFile(stderr_handle, buf, strlen(buf), &unused, nullptr); |
|
} |
|
#else |
|
write(STDERR_FILENO, buf, strlen(buf)); |
|
#endif |
|
abort(); |
|
} |
|
|
|
class UnwindStatus { |
|
public: |
|
UnwindStatus() : err_(nullptr) {} |
|
explicit UnwindStatus(const char *err) : err_(err) {} |
|
|
|
bool ok() const { return err_ == nullptr; } |
|
const char *Error() const { return err_; } |
|
|
|
private: |
|
const char *err_; |
|
}; |
|
|
|
template<typename T> |
|
class UnwindStatusOr { |
|
public: |
|
UnwindStatusOr(UnwindStatus status) : status_(status) { |
|
assert(!status_.ok()); |
|
} |
|
|
|
UnwindStatusOr(const T &value) : status_(UnwindStatus()), value_(value) {} |
|
|
|
bool ok() const { return status_.ok(); } |
|
const char *Error() const { return status_.Error(); } |
|
|
|
const T &ValueOrDie(const char *msg = "Unexpected error") const { |
|
if (!ok()) { |
|
FatalError(msg, ": ", Error()); |
|
} |
|
return value_; |
|
} |
|
|
|
private: |
|
UnwindStatus status_; |
|
T value_; |
|
}; |
|
|
|
// UnwindCursor abstracts between libunwind and Windows unwind APIs. It is |
|
// async-signal-safe. |
|
#if defined(OPENSSL_WINDOWS) |
|
class UnwindCursor { |
|
public: |
|
explicit UnwindCursor(const CONTEXT &ctx) : ctx_(ctx) { |
|
starting_ip_ = ctx_.Rip; |
|
} |
|
|
|
crypto_word_t starting_ip() const { return starting_ip_; } |
|
|
|
// Step unwinds the cursor by one frame. On success, it returns whether there |
|
// were more frames to unwind. |
|
UnwindStatusOr<bool> Step() { |
|
bool is_top = is_top_; |
|
is_top_ = false; |
|
|
|
DWORD64 image_base; |
|
RUNTIME_FUNCTION *entry = |
|
RtlLookupFunctionEntry(ctx_.Rip, &image_base, nullptr); |
|
if (entry == nullptr) { |
|
// This is a leaf function. Leaf functions do not touch stack or |
|
// callee-saved registers, so they may be unwound by simulating a ret. |
|
if (!is_top) { |
|
return UnwindStatus("leaf function found below the top frame"); |
|
} |
|
memcpy(&ctx_.Rip, reinterpret_cast<const void *>(ctx_.Rsp), |
|
sizeof(ctx_.Rip)); |
|
ctx_.Rsp += 8; |
|
return true; |
|
} |
|
|
|
// This is a frame function. Call into the Windows unwinder. |
|
void *handler_data; |
|
DWORD64 establisher_frame; |
|
RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, ctx_.Rip, entry, &ctx_, |
|
&handler_data, &establisher_frame, nullptr); |
|
return ctx_.Rip != 0; |
|
} |
|
|
|
// GetIP returns the instruction pointer at the current frame. |
|
UnwindStatusOr<crypto_word_t> GetIP() { return ctx_.Rip; } |
|
|
|
// GetSP returns the stack pointer at the current frame. |
|
UnwindStatusOr<crypto_word_t> GetSP() { return ctx_.Rsp; } |
|
|
|
// GetCallerState returns the callee-saved registers at the current frame. |
|
UnwindStatusOr<CallerState> GetCallerState() { |
|
CallerState state; |
|
state.rbx = ctx_.Rbx; |
|
state.rbp = ctx_.Rbp; |
|
state.rdi = ctx_.Rdi; |
|
state.rsi = ctx_.Rsi; |
|
state.r12 = ctx_.R12; |
|
state.r13 = ctx_.R13; |
|
state.r14 = ctx_.R14; |
|
state.r15 = ctx_.R15; |
|
memcpy(&state.xmm6, &ctx_.Xmm6, sizeof(Reg128)); |
|
memcpy(&state.xmm7, &ctx_.Xmm7, sizeof(Reg128)); |
|
memcpy(&state.xmm8, &ctx_.Xmm8, sizeof(Reg128)); |
|
memcpy(&state.xmm9, &ctx_.Xmm9, sizeof(Reg128)); |
|
memcpy(&state.xmm10, &ctx_.Xmm10, sizeof(Reg128)); |
|
memcpy(&state.xmm11, &ctx_.Xmm11, sizeof(Reg128)); |
|
memcpy(&state.xmm12, &ctx_.Xmm12, sizeof(Reg128)); |
|
memcpy(&state.xmm13, &ctx_.Xmm13, sizeof(Reg128)); |
|
memcpy(&state.xmm14, &ctx_.Xmm14, sizeof(Reg128)); |
|
memcpy(&state.xmm15, &ctx_.Xmm15, sizeof(Reg128)); |
|
return state; |
|
} |
|
|
|
// ToString returns a human-readable representation of the address the cursor |
|
// started at. |
|
const char *ToString() { |
|
StrCatSignalSafe(starting_ip_buf_, "0x", WordToHex(starting_ip_).data()); |
|
return starting_ip_buf_; |
|
} |
|
|
|
private: |
|
CONTEXT ctx_; |
|
crypto_word_t starting_ip_; |
|
char starting_ip_buf_[64]; |
|
bool is_top_ = true; |
|
}; |
|
#else // !OPENSSL_WINDOWS |
|
class UnwindCursor { |
|
public: |
|
explicit UnwindCursor(unw_context_t *ctx) : ctx_(ctx) { |
|
int ret = InitAtSignalFrame(&cursor_); |
|
if (ret < 0) { |
|
FatalError("Error getting unwind context: ", unw_strerror(ret)); |
|
} |
|
starting_ip_ = GetIP().ValueOrDie("Error getting instruction pointer"); |
|
} |
|
|
|
// Step unwinds the cursor by one frame. On success, it returns whether there |
|
// were more frames to unwind. |
|
UnwindStatusOr<bool> Step() { |
|
int ret = unw_step(&cursor_); |
|
if (ret < 0) { |
|
return UNWError(ret); |
|
} |
|
return ret != 0; |
|
} |
|
|
|
// GetIP returns the instruction pointer at the current frame. |
|
UnwindStatusOr<crypto_word_t> GetIP() { |
|
crypto_word_t ip; |
|
int ret = GetReg(&ip, UNW_REG_IP); |
|
if (ret < 0) { |
|
return UNWError(ret); |
|
} |
|
return ip; |
|
} |
|
|
|
// GetSP returns the stack pointer at the current frame. |
|
UnwindStatusOr<crypto_word_t> GetSP() { |
|
crypto_word_t sp; |
|
int ret = GetReg(&sp, UNW_REG_SP); |
|
if (ret < 0) { |
|
return UNWError(ret); |
|
} |
|
return sp; |
|
} |
|
|
|
// GetCallerState returns the callee-saved registers at the current frame. |
|
UnwindStatusOr<CallerState> GetCallerState() { |
|
CallerState state; |
|
int ret = 0; |
|
#if defined(OPENSSL_X86_64) |
|
ret = ret < 0 ? ret : GetReg(&state.rbx, UNW_X86_64_RBX); |
|
ret = ret < 0 ? ret : GetReg(&state.rbp, UNW_X86_64_RBP); |
|
ret = ret < 0 ? ret : GetReg(&state.r12, UNW_X86_64_R12); |
|
ret = ret < 0 ? ret : GetReg(&state.r13, UNW_X86_64_R13); |
|
ret = ret < 0 ? ret : GetReg(&state.r14, UNW_X86_64_R14); |
|
ret = ret < 0 ? ret : GetReg(&state.r15, UNW_X86_64_R15); |
|
#else |
|
#error "unknown architecture" |
|
#endif |
|
if (ret < 0) { |
|
return UNWError(ret); |
|
} |
|
return state; |
|
} |
|
|
|
// ToString returns a human-readable representation of the address the cursor |
|
// started at, using debug information if available. |
|
const char *ToString() { |
|
// Use a new cursor. |cursor_| has already been unwound, and |
|
// |unw_get_proc_name| is slow so we do not sample it unconditionally in the |
|
// constructor. |
|
unw_cursor_t cursor; |
|
unw_word_t off; |
|
if (InitAtSignalFrame(&cursor) != 0 || |
|
unw_get_proc_name(&cursor, starting_ip_buf_, sizeof(starting_ip_buf_), |
|
&off) != 0) { |
|
StrCatSignalSafe(starting_ip_buf_, "0x", WordToHex(starting_ip_).data()); |
|
return starting_ip_buf_; |
|
} |
|
size_t len = strlen(starting_ip_buf_); |
|
// Print the offset in decimal, to match gdb's disassembly output and ease |
|
// debugging. |
|
StrCatSignalSafe(bssl::Span<char>(starting_ip_buf_).subspan(len), "+", |
|
WordToDecimal(off).data(), " (0x", |
|
WordToHex(starting_ip_).data(), ")"); |
|
return starting_ip_buf_; |
|
} |
|
|
|
private: |
|
static UnwindStatus UNWError(int ret) { |
|
assert(ret < 0); |
|
const char *msg = unw_strerror(ret); |
|
return UnwindStatus(msg == nullptr ? "unknown error" : msg); |
|
} |
|
|
|
int InitAtSignalFrame(unw_cursor_t *cursor) { |
|
// Work around a bug in libunwind which breaks rax and rdx recovery. This |
|
// breaks functions which temporarily use rax as the CFA register. See |
|
// https://git.savannah.gnu.org/gitweb/?p=libunwind.git;a=commit;h=819bf51bbd2da462c2ec3401e8ac9153b6e725e3 |
|
OPENSSL_memset(cursor, 0, sizeof(*cursor)); |
|
int ret = unw_init_local(cursor, ctx_); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
for (;;) { |
|
ret = unw_is_signal_frame(cursor); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
if (ret != 0) { |
|
return 0; // Found the signal frame. |
|
} |
|
ret = unw_step(cursor); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
} |
|
} |
|
|
|
int GetReg(crypto_word_t *out, unw_regnum_t reg) { |
|
unw_word_t val; |
|
int ret = unw_get_reg(&cursor_, reg, &val); |
|
if (ret >= 0) { |
|
static_assert(sizeof(crypto_word_t) == sizeof(unw_word_t), |
|
"crypto_word_t and unw_word_t are inconsistent"); |
|
*out = val; |
|
} |
|
return ret; |
|
} |
|
|
|
unw_context_t *ctx_; |
|
unw_cursor_t cursor_; |
|
crypto_word_t starting_ip_; |
|
char starting_ip_buf_[64]; |
|
}; |
|
#endif // OPENSSL_WINDOWS |
|
|
|
// g_in_trampoline is true if we are in an instrumented |abi_test_trampoline| |
|
// call, in the region that triggers |SIGTRAP|. |
|
static bool g_in_trampoline = false; |
|
// g_unwind_function_done, if |g_in_trampoline| is true, is whether the function |
|
// under test has returned. It is undefined otherwise. |
|
static bool g_unwind_function_done; |
|
// g_trampoline_state, if |g_in_trampoline| is true, is the state the function |
|
// under test must preserve. It is undefined otherwise. |
|
static CallerState g_trampoline_state; |
|
// g_trampoline_sp, if |g_in_trampoline| is true, is the stack pointer of the |
|
// trampoline frame. It is undefined otherwise. |
|
static crypto_word_t g_trampoline_sp; |
|
|
|
// kMaxUnwindErrors is the maximum number of unwind errors reported per |
|
// function. If a function's unwind tables are wrong, we are otherwise likely to |
|
// repeat the same error at multiple addresses. |
|
static constexpr size_t kMaxUnwindErrors = 10; |
|
|
|
// Errors are saved in a signal handler. We use a static buffer to avoid |
|
// allocation. |
|
static size_t g_num_unwind_errors = 0; |
|
|
|
struct UnwindError { |
|
#if defined(OPENSSL_WINDOWS) |
|
crypto_word_t ip; |
|
#endif |
|
char str[512]; |
|
}; |
|
|
|
static UnwindError g_unwind_errors[kMaxUnwindErrors]; |
|
|
|
template <typename... Args> |
|
static void AddUnwindError(UnwindCursor *cursor, Args... args) { |
|
if (g_num_unwind_errors >= kMaxUnwindErrors) { |
|
return; |
|
} |
|
#if defined(OPENSSL_WINDOWS) |
|
// Windows symbol functions should not be called when handling an |
|
// exception. Stash the instruction pointer, to be symbolized later. |
|
g_unwind_errors[g_num_unwind_errors].ip = cursor->starting_ip(); |
|
StrCatSignalSafe(g_unwind_errors[g_num_unwind_errors].str, args...); |
|
#else |
|
StrCatSignalSafe(g_unwind_errors[g_num_unwind_errors].str, |
|
"unwinding at ", cursor->ToString(), ": ", args...); |
|
#endif |
|
g_num_unwind_errors++; |
|
} |
|
|
|
static void CheckUnwind(UnwindCursor *cursor) { |
|
const crypto_word_t kStartAddress = |
|
reinterpret_cast<crypto_word_t>(&abi_test_unwind_start); |
|
const crypto_word_t kReturnAddress = |
|
reinterpret_cast<crypto_word_t>(&abi_test_unwind_return); |
|
const crypto_word_t kStopAddress = |
|
reinterpret_cast<crypto_word_t>(&abi_test_unwind_stop); |
|
|
|
crypto_word_t sp = cursor->GetSP().ValueOrDie("Error getting stack pointer"); |
|
crypto_word_t ip = |
|
cursor->GetIP().ValueOrDie("Error getting instruction pointer"); |
|
if (!g_in_trampoline) { |
|
if (ip != kStartAddress) { |
|
FatalError("Unexpected SIGTRAP at ", cursor->ToString()); |
|
} |
|
|
|
// Save the current state and begin. |
|
g_in_trampoline = true; |
|
g_unwind_function_done = false; |
|
g_trampoline_sp = sp; |
|
g_trampoline_state = cursor->GetCallerState().ValueOrDie( |
|
"Error getting initial caller state"); |
|
} else { |
|
if (sp == g_trampoline_sp || g_unwind_function_done) { |
|
// |g_unwind_function_done| should imply |sp| is |g_trampoline_sp|, but |
|
// clearing the trap flag in x86 briefly displaces the stack pointer. |
|
// |
|
// Also note we check both |ip| and |sp| below, in case the function under |
|
// test is also |abi_test_trampoline|. |
|
if (ip == kReturnAddress && sp == g_trampoline_sp) { |
|
g_unwind_function_done = true; |
|
} |
|
if (ip == kStopAddress && sp == g_trampoline_sp) { |
|
// |SIGTRAP| is fatal again. |
|
g_in_trampoline = false; |
|
} |
|
} else if (IsAncestorStackFrame(sp, g_trampoline_sp)) { |
|
// This should never happen. We went past |g_trampoline_sp| without |
|
// stopping at |kStopAddress|. |
|
AddUnwindError(cursor, "stack frame is before caller"); |
|
g_in_trampoline = false; |
|
} else if (g_num_unwind_errors < kMaxUnwindErrors) { |
|
for (;;) { |
|
UnwindStatusOr<bool> step_ret = cursor->Step(); |
|
if (!step_ret.ok()) { |
|
AddUnwindError(cursor, "error unwinding: ", step_ret.Error()); |
|
break; |
|
} |
|
// |Step| returns whether there was a frame to unwind. |
|
if (!step_ret.ValueOrDie()) { |
|
AddUnwindError(cursor, "could not unwind to starting frame"); |
|
break; |
|
} |
|
|
|
UnwindStatusOr<crypto_word_t> cur_sp = cursor->GetSP(); |
|
if (!cur_sp.ok()) { |
|
AddUnwindError(cursor, |
|
"error recovering stack pointer: ", cur_sp.Error()); |
|
break; |
|
} |
|
if (IsAncestorStackFrame(cur_sp.ValueOrDie(), g_trampoline_sp)) { |
|
AddUnwindError(cursor, "unwound past starting frame"); |
|
break; |
|
} |
|
if (cur_sp.ValueOrDie() == g_trampoline_sp) { |
|
// We found the parent frame. Check the return address. |
|
UnwindStatusOr<crypto_word_t> cur_ip = cursor->GetIP(); |
|
if (!cur_ip.ok()) { |
|
AddUnwindError(cursor, |
|
"error recovering return address: ", cur_ip.Error()); |
|
} else if (cur_ip.ValueOrDie() != kReturnAddress) { |
|
AddUnwindError(cursor, "wrong return address"); |
|
} |
|
|
|
// Check the remaining registers. |
|
UnwindStatusOr<CallerState> state = cursor->GetCallerState(); |
|
if (!state.ok()) { |
|
AddUnwindError(cursor, |
|
"error recovering registers: ", state.Error()); |
|
} else { |
|
ForEachMismatch(state.ValueOrDie(), g_trampoline_state, |
|
[&](const char *reg) { |
|
AddUnwindError(cursor, reg, " was not recovered"); |
|
}); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void ReadUnwindResult(Result *out) { |
|
for (size_t i = 0; i < g_num_unwind_errors; i++) { |
|
#if defined(OPENSSL_WINDOWS) |
|
const crypto_word_t ip = g_unwind_errors[i].ip; |
|
char buf[256]; |
|
DWORD64 displacement; |
|
struct { |
|
SYMBOL_INFO info; |
|
char name_buf[128]; |
|
} symbol; |
|
memset(&symbol, 0, sizeof(symbol)); |
|
symbol.info.SizeOfStruct = sizeof(symbol.info); |
|
symbol.info.MaxNameLen = sizeof(symbol.name_buf); |
|
if (SymFromAddr(GetCurrentProcess(), ip, &displacement, &symbol.info)) { |
|
snprintf(buf, sizeof(buf), "unwinding at %s+%llu (0x%s): %s", |
|
symbol.info.Name, displacement, WordToHex(ip).data(), |
|
g_unwind_errors[i].str); |
|
} else { |
|
snprintf(buf, sizeof(buf), "unwinding at 0x%s: %s", |
|
WordToHex(ip).data(), g_unwind_errors[i].str); |
|
} |
|
out->errors.emplace_back(buf); |
|
#else |
|
out->errors.emplace_back(g_unwind_errors[i].str); |
|
#endif |
|
} |
|
if (g_num_unwind_errors == kMaxUnwindErrors) { |
|
out->errors.emplace_back("(additional errors omitted)"); |
|
} |
|
g_num_unwind_errors = 0; |
|
} |
|
|
|
#if defined(OPENSSL_WINDOWS) |
|
static DWORD g_main_thread; |
|
|
|
static long ExceptionHandler(EXCEPTION_POINTERS *info) { |
|
if (info->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP || |
|
GetCurrentThreadId() != g_main_thread) { |
|
return EXCEPTION_CONTINUE_SEARCH; |
|
} |
|
|
|
UnwindCursor cursor(*info->ContextRecord); |
|
CheckUnwind(&cursor); |
|
if (g_in_trampoline) { |
|
// Windows clears the trap flag, so we must restore it. |
|
info->ContextRecord->EFlags |= 0x100; |
|
} |
|
return EXCEPTION_CONTINUE_EXECUTION; |
|
} |
|
|
|
static void EnableUnwindTestsImpl() { |
|
if (IsDebuggerPresent()) { |
|
// Unwind tests drive logic via |EXCEPTION_SINGLE_STEP|, which conflicts with |
|
// debuggers. |
|
fprintf(stderr, "Debugger detected. Disabling unwind tests.\n"); |
|
return; |
|
} |
|
|
|
g_main_thread = GetCurrentThreadId(); |
|
|
|
SymSetOptions(SYMOPT_DEFERRED_LOADS); |
|
if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) { |
|
fprintf(stderr, "Could not initialize symbols.\n"); |
|
} |
|
|
|
if (AddVectoredExceptionHandler(0, ExceptionHandler) == nullptr) { |
|
fprintf(stderr, "Error installing exception handler.\n"); |
|
abort(); |
|
} |
|
|
|
g_unwind_tests_enabled = true; |
|
} |
|
#else // !OPENSSL_WINDOWS |
|
// HandleEINTR runs |func| and returns the result, retrying the operation on |
|
// |EINTR|. |
|
template <typename Func> |
|
static auto HandleEINTR(const Func &func) -> decltype(func()) { |
|
decltype(func()) ret; |
|
do { |
|
ret = func(); |
|
} while (ret < 0 && errno == EINTR); |
|
return ret; |
|
} |
|
|
|
static bool ReadFileToString(std::string *out, const char *path) { |
|
out->clear(); |
|
|
|
int fd = HandleEINTR([&] { return open(path, O_RDONLY); }); |
|
if (fd < 0) { |
|
return false; |
|
} |
|
|
|
for (;;) { |
|
char buf[1024]; |
|
ssize_t ret = HandleEINTR([&] { return read(fd, buf, sizeof(buf)); }); |
|
if (ret < 0) { |
|
close(fd); |
|
return false; |
|
} |
|
if (ret == 0) { |
|
close(fd); |
|
return true; |
|
} |
|
out->append(buf, static_cast<size_t>(ret)); |
|
} |
|
} |
|
|
|
static bool IsBeingDebugged() { |
|
std::string status; |
|
if (!ReadFileToString(&status, "/proc/self/status")) { |
|
perror("error reading /proc/self/status"); |
|
return false; |
|
} |
|
std::string key = "\nTracerPid:\t"; |
|
size_t idx = status.find(key); |
|
if (idx == std::string::npos) { |
|
return false; |
|
} |
|
idx += key.size(); |
|
return idx < status.size() && status[idx] != '0'; |
|
} |
|
|
|
static pthread_t g_main_thread; |
|
|
|
static void TrapHandler(int sig) { |
|
// Note this is a signal handler, so only async-signal-safe functions may be |
|
// used here. See signal-safety(7). libunwind promises local unwind is |
|
// async-signal-safe. |
|
|
|
// |pthread_equal| is not listed as async-signal-safe, but this is clearly an |
|
// oversight. |
|
if (!pthread_equal(g_main_thread, pthread_self())) { |
|
FatalError("SIGTRAP on background thread"); |
|
} |
|
|
|
unw_context_t ctx; |
|
int ret = unw_getcontext(&ctx); |
|
if (ret < 0) { |
|
FatalError("Error getting unwind context: ", unw_strerror(ret)); |
|
} |
|
|
|
UnwindCursor cursor(&ctx); |
|
CheckUnwind(&cursor); |
|
} |
|
|
|
static void EnableUnwindTestsImpl() { |
|
if (IsBeingDebugged()) { |
|
// Unwind tests drive logic via |SIGTRAP|, which conflicts with debuggers. |
|
fprintf(stderr, "Debugger detected. Disabling unwind tests.\n"); |
|
return; |
|
} |
|
|
|
g_main_thread = pthread_self(); |
|
|
|
struct sigaction trap_action; |
|
OPENSSL_memset(&trap_action, 0, sizeof(trap_action)); |
|
sigemptyset(&trap_action.sa_mask); |
|
trap_action.sa_handler = TrapHandler; |
|
if (sigaction(SIGTRAP, &trap_action, NULL) != 0) { |
|
perror("sigaction"); |
|
abort(); |
|
} |
|
|
|
g_unwind_tests_enabled = true; |
|
} |
|
#endif // OPENSSL_WINDOWS |
|
|
|
#else // !SUPPORTS_UNWIND_TEST |
|
|
|
#if defined(SUPPORTS_ABI_TEST) |
|
static void ReadUnwindResult(Result *) {} |
|
#endif |
|
static void EnableUnwindTestsImpl() {} |
|
|
|
#endif // SUPPORTS_UNWIND_TEST |
|
|
|
} // namespace internal |
|
|
|
void EnableUnwindTests() { internal::EnableUnwindTestsImpl(); } |
|
|
|
bool UnwindTestsEnabled() { return internal::g_unwind_tests_enabled; } |
|
|
|
} // namespace abi_test
|
|
|