mirror of
https://github.com/QuasarApp/backward-cpp.git
synced 2025-04-29 03:44:31 +00:00
Windows (Clang9, MSVC2017) implementation
Remaining issues: * some warinings
This commit is contained in:
parent
ec14c48e3e
commit
7cbf4c25e9
@ -102,7 +102,7 @@ if(BACKWARD_TESTS)
|
|||||||
|
|
||||||
add_executable(${test_name} ${src} ${ARGN} $<TARGET_OBJECTS:test_main>)
|
add_executable(${test_name} ${src} ${ARGN} $<TARGET_OBJECTS:test_main>)
|
||||||
|
|
||||||
target_link_libraries(${test_name} PRIVATE Backward::Backward test_main)
|
target_link_libraries(${test_name} PRIVATE Backward::Backward)
|
||||||
|
|
||||||
add_test(NAME ${name} COMMAND ${test_name})
|
add_test(NAME ${name} COMMAND ${test_name})
|
||||||
endmacro()
|
endmacro()
|
||||||
|
458
backward.hpp
458
backward.hpp
@ -31,7 +31,7 @@
|
|||||||
#if defined(BACKWARD_CXX11)
|
#if defined(BACKWARD_CXX11)
|
||||||
#elif defined(BACKWARD_CXX98)
|
#elif defined(BACKWARD_CXX98)
|
||||||
#else
|
#else
|
||||||
#if __cplusplus >= 201103L
|
#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)
|
||||||
#define BACKWARD_CXX11
|
#define BACKWARD_CXX11
|
||||||
#define BACKWARD_ATLEAST_CXX11
|
#define BACKWARD_ATLEAST_CXX11
|
||||||
#define BACKWARD_ATLEAST_CXX98
|
#define BACKWARD_ATLEAST_CXX98
|
||||||
@ -55,11 +55,14 @@
|
|||||||
#if defined(BACKWARD_SYSTEM_LINUX)
|
#if defined(BACKWARD_SYSTEM_LINUX)
|
||||||
#elif defined(BACKWARD_SYSTEM_DARWIN)
|
#elif defined(BACKWARD_SYSTEM_DARWIN)
|
||||||
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
|
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
|
||||||
|
#elif defined(BACKWARD_SYSTEM_WINDOWS)
|
||||||
#else
|
#else
|
||||||
#if defined(__linux) || defined(__linux__)
|
#if defined(__linux) || defined(__linux__)
|
||||||
#define BACKWARD_SYSTEM_LINUX
|
#define BACKWARD_SYSTEM_LINUX
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#define BACKWARD_SYSTEM_DARWIN
|
#define BACKWARD_SYSTEM_DARWIN
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define BACKWARD_SYSTEM_WINDOWS
|
||||||
#else
|
#else
|
||||||
#define BACKWARD_SYSTEM_UNKNOWN
|
#define BACKWARD_SYSTEM_UNKNOWN
|
||||||
#endif
|
#endif
|
||||||
@ -301,6 +304,49 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif // defined(BACKWARD_SYSTEM_DARWIN)
|
#endif // defined(BACKWARD_SYSTEM_DARWIN)
|
||||||
|
|
||||||
|
#if defined(BACKWARD_SYSTEM_WINDOWS)
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <BaseTsd.h>
|
||||||
|
typedef SSIZE_T ssize_t;
|
||||||
|
|
||||||
|
#define NOMINMAX
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
|
||||||
|
#include <Psapi.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#ifndef __clang__
|
||||||
|
#undef NOINLINE
|
||||||
|
#define NOINLINE __declspec(noinline)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma comment(lib, "psapi.lib")
|
||||||
|
#pragma comment(lib, "dbghelp.lib")
|
||||||
|
|
||||||
|
// Comment / packing is from stackoverflow:
|
||||||
|
// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227
|
||||||
|
// Some versions of imagehlp.dll lack the proper packing directives themselves
|
||||||
|
// so we need to do it.
|
||||||
|
#pragma pack(push, before_imagehlp, 8)
|
||||||
|
#include <imagehlp.h>
|
||||||
|
#pragma pack(pop, before_imagehlp)
|
||||||
|
|
||||||
|
// TODO maybe these should be undefined somewhere else?
|
||||||
|
#undef BACKWARD_HAS_UNWIND
|
||||||
|
#undef BACKWARD_HAS_BACKTRACE
|
||||||
|
#if BACKWARD_HAS_PDB_SYMBOL == 1
|
||||||
|
#else
|
||||||
|
#undef BACKWARD_HAS_PDB_SYMBOL
|
||||||
|
#define BACKWARD_HAS_PDB_SYMBOL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#if BACKWARD_HAS_UNWIND == 1
|
#if BACKWARD_HAS_UNWIND == 1
|
||||||
|
|
||||||
#include <unwind.h>
|
#include <unwind.h>
|
||||||
@ -357,12 +403,15 @@ namespace system_tag {
|
|||||||
struct linux_tag; // seems that I cannot call that "linux" because the name
|
struct linux_tag; // seems that I cannot call that "linux" because the name
|
||||||
// is already defined... so I am adding _tag everywhere.
|
// is already defined... so I am adding _tag everywhere.
|
||||||
struct darwin_tag;
|
struct darwin_tag;
|
||||||
|
struct windows_tag;
|
||||||
struct unknown_tag;
|
struct unknown_tag;
|
||||||
|
|
||||||
#if defined(BACKWARD_SYSTEM_LINUX)
|
#if defined(BACKWARD_SYSTEM_LINUX)
|
||||||
typedef linux_tag current_tag;
|
typedef linux_tag current_tag;
|
||||||
#elif defined(BACKWARD_SYSTEM_DARWIN)
|
#elif defined(BACKWARD_SYSTEM_DARWIN)
|
||||||
typedef darwin_tag current_tag;
|
typedef darwin_tag current_tag;
|
||||||
|
#elif defined(BACKWARD_SYSTEM_WINDOWS)
|
||||||
|
typedef windows_tag current_tag;
|
||||||
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
|
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
|
||||||
typedef unknown_tag current_tag;
|
typedef unknown_tag current_tag;
|
||||||
#else
|
#else
|
||||||
@ -396,6 +445,13 @@ typedef backtrace_symbol current;
|
|||||||
#else
|
#else
|
||||||
#error "You shall not pass, until you know what you want."
|
#error "You shall not pass, until you know what you want."
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(BACKWARD_SYSTEM_WINDOWS)
|
||||||
|
struct pdb_symbol;
|
||||||
|
#if BACKWARD_HAS_PDB_SYMBOL == 1
|
||||||
|
typedef pdb_symbol current;
|
||||||
|
#else
|
||||||
|
#error "You shall not pass, until you know what you want."
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
} // namespace trace_resolver_tag
|
} // namespace trace_resolver_tag
|
||||||
|
|
||||||
@ -747,7 +803,7 @@ private:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#else // BACKWARD_HAS_UNWIND == 0
|
#elif defined(BACKWARD_HAS_BACKTRACE)
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
|
class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
|
||||||
@ -781,7 +837,102 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BACKWARD_HAS_UNWIND
|
#elif defined(BACKWARD_SYSTEM_WINDOWS)
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class StackTraceImpl<system_tag::current_tag> : public StackTraceImplHolder {
|
||||||
|
public:
|
||||||
|
// We have to load the machine type from the image info
|
||||||
|
// So we first initialize the resolver, and it tells us this info
|
||||||
|
void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; }
|
||||||
|
void set_context(CONTEXT *ctx) { ctx_ = ctx; }
|
||||||
|
void set_thread_handle(HANDLE handle) { thd_ = handle; }
|
||||||
|
|
||||||
|
NOINLINE
|
||||||
|
size_t load_here(size_t depth = 32) {
|
||||||
|
|
||||||
|
CONTEXT localCtx; // used when no context is provided
|
||||||
|
|
||||||
|
if (depth == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx_) {
|
||||||
|
ctx_ = &localCtx;
|
||||||
|
RtlCaptureContext(ctx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!thd_) {
|
||||||
|
thd_ = GetCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
|
||||||
|
STACKFRAME64 s;
|
||||||
|
memset(&s, 0, sizeof(STACKFRAME64));
|
||||||
|
|
||||||
|
// TODO: 32 bit context capture
|
||||||
|
s.AddrStack.Mode = AddrModeFlat;
|
||||||
|
s.AddrFrame.Mode = AddrModeFlat;
|
||||||
|
s.AddrPC.Mode = AddrModeFlat;
|
||||||
|
#ifdef _M_X64
|
||||||
|
s.AddrPC.Offset = ctx_->Rip;
|
||||||
|
s.AddrStack.Offset = ctx_->Rsp;
|
||||||
|
s.AddrFrame.Offset = ctx_->Rbp;
|
||||||
|
#else
|
||||||
|
s.AddrPC.Offset = ctx_->Eip;
|
||||||
|
s.AddrStack.Offset = ctx_->Esp;
|
||||||
|
s.AddrFrame.Offset = ctx_->Ebp;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!machine_type_) {
|
||||||
|
#ifdef _M_X64
|
||||||
|
machine_type_ = IMAGE_FILE_MACHINE_AMD64;
|
||||||
|
#else
|
||||||
|
machine_type_ = IMAGE_FILE_MACHINE_I386;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// NOTE: this only works if PDBs are already loaded!
|
||||||
|
SetLastError(0);
|
||||||
|
if (!StackWalk64(machine_type_, process, thd_, &s, ctx_, NULL,
|
||||||
|
SymFunctionTableAccess64, SymGetModuleBase64, NULL))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (s.AddrReturn.Offset == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
_stacktrace.push_back(reinterpret_cast<void *>(s.AddrPC.Offset));
|
||||||
|
|
||||||
|
if (size() >= depth)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t load_from(void *addr, size_t depth = 32) {
|
||||||
|
load_here(depth + 8);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _stacktrace.size(); ++i) {
|
||||||
|
if (_stacktrace[i] == addr) {
|
||||||
|
skip_n_firsts(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth));
|
||||||
|
return size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD machine_type_ = 0;
|
||||||
|
HANDLE thd_ = 0;
|
||||||
|
CONTEXT *ctx_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
class StackTrace : public StackTraceImpl<system_tag::current_tag> {};
|
class StackTrace : public StackTraceImpl<system_tag::current_tag> {};
|
||||||
|
|
||||||
@ -3068,6 +3219,132 @@ class TraceResolverImpl<system_tag::darwin_tag>
|
|||||||
|
|
||||||
#endif // BACKWARD_SYSTEM_DARWIN
|
#endif // BACKWARD_SYSTEM_DARWIN
|
||||||
|
|
||||||
|
#ifdef BACKWARD_SYSTEM_WINDOWS
|
||||||
|
|
||||||
|
// Load all symbol info
|
||||||
|
// Based on:
|
||||||
|
// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227
|
||||||
|
|
||||||
|
struct module_data {
|
||||||
|
std::string image_name;
|
||||||
|
std::string module_name;
|
||||||
|
void *base_address;
|
||||||
|
DWORD load_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
class get_mod_info {
|
||||||
|
HANDLE process;
|
||||||
|
static const int buffer_length = 4096;
|
||||||
|
|
||||||
|
public:
|
||||||
|
get_mod_info(HANDLE h) : process(h) {}
|
||||||
|
|
||||||
|
module_data operator()(HMODULE module) {
|
||||||
|
module_data ret;
|
||||||
|
char temp[buffer_length];
|
||||||
|
MODULEINFO mi;
|
||||||
|
|
||||||
|
GetModuleInformation(process, module, &mi, sizeof(mi));
|
||||||
|
ret.base_address = mi.lpBaseOfDll;
|
||||||
|
ret.load_size = mi.SizeOfImage;
|
||||||
|
|
||||||
|
GetModuleFileNameEx(process, module, temp, sizeof(temp));
|
||||||
|
ret.image_name = temp;
|
||||||
|
GetModuleBaseName(process, module, temp, sizeof(temp));
|
||||||
|
ret.module_name = temp;
|
||||||
|
std::vector<char> img(ret.image_name.begin(), ret.image_name.end());
|
||||||
|
std::vector<char> mod(ret.module_name.begin(), ret.module_name.end());
|
||||||
|
SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address,
|
||||||
|
ret.load_size);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> class TraceResolverImpl<system_tag::windows_tag> {
|
||||||
|
public:
|
||||||
|
TraceResolverImpl() {
|
||||||
|
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
|
||||||
|
std::vector<module_data> modules;
|
||||||
|
DWORD cbNeeded;
|
||||||
|
std::vector<HMODULE> module_handles(1);
|
||||||
|
SymInitialize(process, NULL, false);
|
||||||
|
DWORD symOptions = SymGetOptions();
|
||||||
|
symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME;
|
||||||
|
SymSetOptions(symOptions);
|
||||||
|
EnumProcessModules(process, &module_handles[0],
|
||||||
|
module_handles.size() * sizeof(HMODULE), &cbNeeded);
|
||||||
|
module_handles.resize(cbNeeded / sizeof(HMODULE));
|
||||||
|
EnumProcessModules(process, &module_handles[0],
|
||||||
|
module_handles.size() * sizeof(HMODULE), &cbNeeded);
|
||||||
|
std::transform(module_handles.begin(), module_handles.end(),
|
||||||
|
std::back_inserter(modules), get_mod_info(process));
|
||||||
|
void *base = modules[0].base_address;
|
||||||
|
IMAGE_NT_HEADERS *h = ImageNtHeader(base);
|
||||||
|
image_type = h->FileHeader.Machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ST> void load_stacktrace(ST &) {}
|
||||||
|
|
||||||
|
static const int max_sym_len = 255;
|
||||||
|
struct symbol_t {
|
||||||
|
SYMBOL_INFO sym;
|
||||||
|
char buffer[max_sym_len];
|
||||||
|
} sym;
|
||||||
|
|
||||||
|
DWORD64 displacement;
|
||||||
|
|
||||||
|
ResolvedTrace resolve(ResolvedTrace t) {
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
|
||||||
|
char name[256];
|
||||||
|
|
||||||
|
memset(&sym, sizeof(sym), 0);
|
||||||
|
sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||||
|
sym.sym.MaxNameLen = max_sym_len;
|
||||||
|
|
||||||
|
if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) {
|
||||||
|
// TODO: error handling everywhere
|
||||||
|
LPTSTR lpMsgBuf;
|
||||||
|
DWORD dw = GetLastError();
|
||||||
|
|
||||||
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(LPTSTR)&lpMsgBuf, 0, NULL);
|
||||||
|
|
||||||
|
printf(lpMsgBuf);
|
||||||
|
|
||||||
|
// abort();
|
||||||
|
}
|
||||||
|
UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE);
|
||||||
|
|
||||||
|
DWORD offset = 0;
|
||||||
|
IMAGEHLP_LINE line;
|
||||||
|
if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) {
|
||||||
|
t.object_filename = line.FileName;
|
||||||
|
t.source.filename = line.FileName;
|
||||||
|
t.source.line = line.LineNumber;
|
||||||
|
t.source.col = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.source.function = name;
|
||||||
|
t.object_filename = "";
|
||||||
|
t.object_function = name;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD machine_type() const { return image_type; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD image_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
class TraceResolver : public TraceResolverImpl<system_tag::current_tag> {};
|
class TraceResolver : public TraceResolverImpl<system_tag::current_tag> {};
|
||||||
|
|
||||||
/*************** CODE SNIPPET ***************/
|
/*************** CODE SNIPPET ***************/
|
||||||
@ -3379,6 +3656,8 @@ public:
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TraceResolver const &resolver() const { return _resolver; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TraceResolver _resolver;
|
TraceResolver _resolver;
|
||||||
SnippetFactory _snippets;
|
SnippetFactory _snippets;
|
||||||
@ -3623,6 +3902,179 @@ private:
|
|||||||
|
|
||||||
#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
|
#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN
|
||||||
|
|
||||||
|
#ifdef BACKWARD_SYSTEM_WINDOWS
|
||||||
|
|
||||||
|
class SignalHandling {
|
||||||
|
public:
|
||||||
|
SignalHandling(const std::vector<int> & = std::vector<int>())
|
||||||
|
: reporter_thread_([]() {
|
||||||
|
/* We handle crashes in a utility thread:
|
||||||
|
backward structures and some Windows functions called here
|
||||||
|
need stack space, which we do not have when we encounter a
|
||||||
|
stack overflow.
|
||||||
|
To support reporting stack traces during a stack overflow,
|
||||||
|
we create a utility thread at startup, which waits until a
|
||||||
|
crash happens or the program exits normally. */
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
cv().wait(lk, [] { return crashed() != crash_status::running; });
|
||||||
|
}
|
||||||
|
if (crashed() == crash_status::crashed) {
|
||||||
|
handle_stacktrace(skip_recs());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::ending;
|
||||||
|
}
|
||||||
|
cv().notify_one();
|
||||||
|
}) {
|
||||||
|
SetUnhandledExceptionFilter(crash_handler);
|
||||||
|
|
||||||
|
signal(SIGABRT, signal_handler);
|
||||||
|
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||||
|
|
||||||
|
set_terminate(&terminator);
|
||||||
|
set_unexpected(&terminator);
|
||||||
|
_set_purecall_handler(&terminator);
|
||||||
|
_set_invalid_parameter_handler(&invalid_parameter_handler);
|
||||||
|
}
|
||||||
|
bool loaded() const { return true; }
|
||||||
|
|
||||||
|
~SignalHandling() {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::normal_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv().notify_one();
|
||||||
|
|
||||||
|
reporter_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static CONTEXT *ctx() {
|
||||||
|
static CONTEXT data;
|
||||||
|
return &data;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class crash_status { running, crashed, normal_exit, ending };
|
||||||
|
|
||||||
|
static crash_status &crashed() {
|
||||||
|
static crash_status data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::mutex &mtx() {
|
||||||
|
static std::mutex data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::condition_variable &cv() {
|
||||||
|
static std::condition_variable data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HANDLE &thread_handle() {
|
||||||
|
static HANDLE handle;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread reporter_thread_;
|
||||||
|
|
||||||
|
// TODO: how not to hardcode these?
|
||||||
|
static const constexpr int signal_skip_recs =
|
||||||
|
#ifdef __clang__
|
||||||
|
// With clang, RtlCaptureContext also captures the stack frame of the
|
||||||
|
// current function Below that, there ar 3 internal Windows functions
|
||||||
|
4
|
||||||
|
#else
|
||||||
|
// With MSVC cl, RtlCaptureContext misses the stack frame of the current
|
||||||
|
// function The first entries during StackWalk are the 3 internal Windows
|
||||||
|
// functions
|
||||||
|
3
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
static int &skip_recs() {
|
||||||
|
static int data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void terminator() {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void signal_handler(int) {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __cdecl invalid_parameter_handler(const wchar_t *,
|
||||||
|
const wchar_t *,
|
||||||
|
const wchar_t *,
|
||||||
|
unsigned int,
|
||||||
|
uintptr_t) {
|
||||||
|
crash_handler(signal_skip_recs);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) {
|
||||||
|
// The exception info supplies a trace from exactly where the issue was,
|
||||||
|
// no need to skip records
|
||||||
|
crash_handler(0, info->ContextRecord);
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE static void crash_handler(int skip, CONTEXT *ct = nullptr) {
|
||||||
|
|
||||||
|
if (ct == nullptr) {
|
||||||
|
RtlCaptureContext(ctx());
|
||||||
|
} else {
|
||||||
|
memcpy(ctx(), ct, sizeof(CONTEXT));
|
||||||
|
}
|
||||||
|
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
|
||||||
|
GetCurrentProcess(), &thread_handle(), 0, FALSE,
|
||||||
|
DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
|
skip_recs() = skip;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
crashed() = crash_status::crashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
cv().notify_one();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mtx());
|
||||||
|
cv().wait(lk, [] { return crashed() != crash_status::crashed; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_stacktrace(int skip_frames = 0) {
|
||||||
|
// printer creates the TraceResolver, which can supply us a machine type
|
||||||
|
// for stack walking. Without this, StackTrace can only guess using some
|
||||||
|
// macros.
|
||||||
|
// StackTrace also requires that the PDBs are already loaded, which is done
|
||||||
|
// in the constructor of TraceResolver
|
||||||
|
Printer printer;
|
||||||
|
|
||||||
|
StackTrace st;
|
||||||
|
st.set_machine_type(printer.resolver().machine_type());
|
||||||
|
st.set_context(ctx());
|
||||||
|
st.set_thread_handle(thread_handle());
|
||||||
|
st.load_here(32 + skip_frames);
|
||||||
|
st.skip_n_firsts(skip_frames);
|
||||||
|
|
||||||
|
printer.address = true;
|
||||||
|
printer.print(st, std::cerr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BACKWARD_SYSTEM_WINDOWS
|
||||||
|
|
||||||
#ifdef BACKWARD_SYSTEM_UNKNOWN
|
#ifdef BACKWARD_SYSTEM_UNKNOWN
|
||||||
|
|
||||||
class SignalHandling {
|
class SignalHandling {
|
||||||
|
@ -24,15 +24,26 @@
|
|||||||
#include "test.hpp"
|
#include "test.hpp"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#define strcasecmp _stricmp
|
||||||
|
#else
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(__has_include) && __has_include(<error.h>)
|
#if defined(__has_include) && __has_include(<error.h>)
|
||||||
#include <error.h>
|
#include <error.h>
|
||||||
#else
|
#else
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#ifndef __APPLE__
|
#ifdef _WIN32
|
||||||
|
char argv0[MAX_PATH];
|
||||||
|
inline const char *getprogname() {
|
||||||
|
return GetModuleFileName(NULL, argv0, sizeof(argv0)) ? argv0 : NULL;
|
||||||
|
}
|
||||||
|
#elif !defined(__APPLE__)
|
||||||
// N.B. getprogname() is an Apple/BSD-ism.
|
// N.B. getprogname() is an Apple/BSD-ism.
|
||||||
// program_invocation_name is a GLIBC-ism, but it's also
|
// program_invocation_name is a GLIBC-ism, but it's also
|
||||||
// supported by libmusl.
|
// supported by libmusl.
|
||||||
@ -59,13 +70,71 @@ void error(int status, int errnum, const char *format, ...) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
test::test_registry_t test::test_registry;
|
|
||||||
using namespace test;
|
using namespace test;
|
||||||
|
|
||||||
bool run_test(TestBase &test) {
|
bool run_test(TestBase &test, bool use_child_process = true) {
|
||||||
|
if (!use_child_process) {
|
||||||
|
exit(static_cast<int>(test.run()));
|
||||||
|
}
|
||||||
|
|
||||||
printf("-- running test case: %s\n", test.name);
|
printf("-- running test case: %s\n", test.name);
|
||||||
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
|
test::TestStatus status = test::SUCCESS;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
char filename[256];
|
||||||
|
GetModuleFileName(NULL, filename, 256); // TODO: check for error
|
||||||
|
std::string cmd_line = filename;
|
||||||
|
cmd_line += " --nofork ";
|
||||||
|
cmd_line += test.name;
|
||||||
|
|
||||||
|
STARTUPINFO si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
if (!CreateProcessA(nullptr, const_cast<char *>(cmd_line.c_str()), nullptr,
|
||||||
|
nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
|
||||||
|
printf("unable to create process\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
|
|
||||||
|
DWORD exit_code;
|
||||||
|
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||||
|
switch (exit_code) {
|
||||||
|
case 3:
|
||||||
|
status = test::SIGNAL_ABORT;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
status = test::EXCEPTION_UNCAUGHT;
|
||||||
|
break;
|
||||||
|
case EXCEPTION_ACCESS_VIOLATION:
|
||||||
|
status = test::SIGNAL_SEGFAULT;
|
||||||
|
break;
|
||||||
|
case EXCEPTION_STACK_OVERFLOW:
|
||||||
|
status = test::SIGNAL_SEGFAULT;
|
||||||
|
break;
|
||||||
|
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||||
|
status = test::SIGNAL_DIVZERO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf("Exit code: %lu\n", exit_code);
|
||||||
|
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
if (test.expected_status == test::ASSERT_FAIL) {
|
||||||
|
// assert calls abort on windows
|
||||||
|
return (status & test::SIGNAL_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
pid_t child_pid = fork();
|
pid_t child_pid = fork();
|
||||||
if (child_pid == 0) {
|
if (child_pid == 0) {
|
||||||
exit(static_cast<int>(test.run()));
|
exit(static_cast<int>(test.run()));
|
||||||
@ -77,8 +146,6 @@ bool run_test(TestBase &test) {
|
|||||||
int child_status = 0;
|
int child_status = 0;
|
||||||
waitpid(child_pid, &child_status, 0);
|
waitpid(child_pid, &child_status, 0);
|
||||||
|
|
||||||
test::TestStatus status;
|
|
||||||
|
|
||||||
if (WIFEXITED(child_status)) {
|
if (WIFEXITED(child_status)) {
|
||||||
int exit_status = WEXITSTATUS(child_status);
|
int exit_status = WEXITSTATUS(child_status);
|
||||||
if (exit_status & ~test::STATUS_MASK) {
|
if (exit_status & ~test::STATUS_MASK) {
|
||||||
@ -103,10 +170,10 @@ bool run_test(TestBase &test) {
|
|||||||
default:
|
default:
|
||||||
status = test::SIGNAL_UNCAUGHT;
|
status = test::SIGNAL_UNCAUGHT;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
status = test::SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
if (test.expected_status == test::FAILED) {
|
if (test.expected_status == test::FAILED) {
|
||||||
return (status & test::FAILED);
|
return (status & test::FAILED);
|
||||||
}
|
}
|
||||||
@ -120,6 +187,25 @@ bool run_test(TestBase &test) {
|
|||||||
|
|
||||||
int main(int argc, const char *const argv[]) {
|
int main(int argc, const char *const argv[]) {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (argc == 3 && strcmp("--nofork", argv[1]) == 0) {
|
||||||
|
// Windows has no fork, so we simulate it
|
||||||
|
// we only execute one test, without forking
|
||||||
|
for (test_registry_t::iterator it = test_registry().begin();
|
||||||
|
it != test_registry().end(); ++it) {
|
||||||
|
TestBase &test = **it;
|
||||||
|
if (strcasecmp(argv[2], test.name) == 0) {
|
||||||
|
run_test(test, false);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
size_t success_cnt = 0;
|
size_t success_cnt = 0;
|
||||||
size_t total_cnt = 0;
|
size_t total_cnt = 0;
|
||||||
for (test_registry_t::iterator it = test_registry().begin();
|
for (test_registry_t::iterator it = test_registry().begin();
|
||||||
@ -145,9 +231,9 @@ int main(int argc, const char *const argv[]) {
|
|||||||
printf("** test case FAILED : %s\n", test.name);
|
printf("** test case FAILED : %s\n", test.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printf("-- tests passing: %lu/%lu", success_cnt, total_cnt);
|
printf("-- tests passing: %zu/%zu", success_cnt, total_cnt);
|
||||||
if (total_cnt) {
|
if (total_cnt) {
|
||||||
printf(" (%lu%%)\n", success_cnt * 100 / total_cnt);
|
printf(" (%zu%%)\n", success_cnt * 100 / total_cnt);
|
||||||
} else {
|
} else {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,19 @@
|
|||||||
#include "backward.hpp"
|
#include "backward.hpp"
|
||||||
#include "test/test.hpp"
|
#include "test/test.hpp"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace backward;
|
using namespace backward;
|
||||||
|
|
||||||
void collect_trace(StackTrace &st) { st.load_here(); }
|
void collect_trace(StackTrace &st) { st.load_here(); }
|
||||||
|
|
||||||
TEST(minitrace) {
|
TEST(minitrace) {
|
||||||
|
Printer printer;
|
||||||
|
|
||||||
StackTrace st;
|
StackTrace st;
|
||||||
collect_trace(st);
|
collect_trace(st);
|
||||||
|
|
||||||
Printer printer;
|
printer.print(st, std::cout);
|
||||||
printer.print(st, stdout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void d(StackTrace &st) { st.load_here(); }
|
void d(StackTrace &st) { st.load_here(); }
|
||||||
@ -46,9 +48,10 @@ void b(StackTrace &st) { return c(st); }
|
|||||||
NOINLINE void a(StackTrace &st) { return b(st); }
|
NOINLINE void a(StackTrace &st) { return b(st); }
|
||||||
|
|
||||||
TEST(smalltrace) {
|
TEST(smalltrace) {
|
||||||
|
Printer printer;
|
||||||
|
|
||||||
StackTrace st;
|
StackTrace st;
|
||||||
a(st);
|
a(st);
|
||||||
|
|
||||||
Printer printer;
|
printer.print(st, std::cout);
|
||||||
printer.print(st, stdout);
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,10 @@
|
|||||||
|
|
||||||
#include "test/test.hpp"
|
#include "test/test.hpp"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace backward;
|
using namespace backward;
|
||||||
|
|
||||||
@ -75,9 +78,11 @@ TEST_DIVZERO(divide_by_zero) {
|
|||||||
int bye_bye_stack(int i) { return bye_bye_stack(i + 1) + bye_bye_stack(i * 2); }
|
int bye_bye_stack(int i) { return bye_bye_stack(i + 1) + bye_bye_stack(i * 2); }
|
||||||
|
|
||||||
TEST_SEGFAULT(stackoverflow) {
|
TEST_SEGFAULT(stackoverflow) {
|
||||||
|
#ifndef _WIN32
|
||||||
struct rlimit limit;
|
struct rlimit limit;
|
||||||
limit.rlim_max = 8096;
|
limit.rlim_max = 8096;
|
||||||
setrlimit(RLIMIT_STACK, &limit);
|
setrlimit(RLIMIT_STACK, &limit);
|
||||||
|
#endif
|
||||||
int r = bye_bye_stack(42);
|
int r = bye_bye_stack(42);
|
||||||
std::cout << "r=" << r << std::endl;
|
std::cout << "r=" << r << std::endl;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user