Windows (Clang9, MSVC2017) implementation

Remaining issues:
* some warinings
This commit is contained in:
Zsolt Parragi 2019-08-23 16:48:59 +02:00
parent ec14c48e3e
commit 7cbf4c25e9
5 changed files with 563 additions and 17 deletions

View File

@ -102,7 +102,7 @@ if(BACKWARD_TESTS)
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})
endmacro()

View File

@ -31,7 +31,7 @@
#if defined(BACKWARD_CXX11)
#elif defined(BACKWARD_CXX98)
#else
#if __cplusplus >= 201103L
#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)
#define BACKWARD_CXX11
#define BACKWARD_ATLEAST_CXX11
#define BACKWARD_ATLEAST_CXX98
@ -55,11 +55,14 @@
#if defined(BACKWARD_SYSTEM_LINUX)
#elif defined(BACKWARD_SYSTEM_DARWIN)
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
#elif defined(BACKWARD_SYSTEM_WINDOWS)
#else
#if defined(__linux) || defined(__linux__)
#define BACKWARD_SYSTEM_LINUX
#elif defined(__APPLE__)
#define BACKWARD_SYSTEM_DARWIN
#elif defined(_WIN32)
#define BACKWARD_SYSTEM_WINDOWS
#else
#define BACKWARD_SYSTEM_UNKNOWN
#endif
@ -301,6 +304,49 @@
#endif
#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
#include <unwind.h>
@ -357,12 +403,15 @@ namespace system_tag {
struct linux_tag; // seems that I cannot call that "linux" because the name
// is already defined... so I am adding _tag everywhere.
struct darwin_tag;
struct windows_tag;
struct unknown_tag;
#if defined(BACKWARD_SYSTEM_LINUX)
typedef linux_tag current_tag;
#elif defined(BACKWARD_SYSTEM_DARWIN)
typedef darwin_tag current_tag;
#elif defined(BACKWARD_SYSTEM_WINDOWS)
typedef windows_tag current_tag;
#elif defined(BACKWARD_SYSTEM_UNKNOWN)
typedef unknown_tag current_tag;
#else
@ -396,6 +445,13 @@ typedef backtrace_symbol current;
#else
#error "You shall not pass, until you know what you want."
#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
} // namespace trace_resolver_tag
@ -747,7 +803,7 @@ private:
};
};
#else // BACKWARD_HAS_UNWIND == 0
#elif defined(BACKWARD_HAS_BACKTRACE)
template <>
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> {};
@ -3068,6 +3219,132 @@ class TraceResolverImpl<system_tag::darwin_tag>
#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> {};
/*************** CODE SNIPPET ***************/
@ -3379,6 +3656,8 @@ public:
return os;
}
TraceResolver const &resolver() const { return _resolver; }
private:
TraceResolver _resolver;
SnippetFactory _snippets;
@ -3623,6 +3902,179 @@ private:
#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
class SignalHandling {

View File

@ -24,15 +24,26 @@
#include "test.hpp"
#include <cstdio>
#include <cstdlib>
#ifdef _WIN32
#include <Windows.h>
#define strcasecmp _stricmp
#else
#include <sys/wait.h>
#include <unistd.h>
#endif
#if defined(__has_include) && __has_include(<error.h>)
#include <error.h>
#else
#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.
// program_invocation_name is a GLIBC-ism, but it's also
// supported by libmusl.
@ -59,13 +70,71 @@ void error(int status, int errnum, const char *format, ...) {
}
#endif
test::test_registry_t test::test_registry;
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);
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();
if (child_pid == 0) {
exit(static_cast<int>(test.run()));
@ -77,8 +146,6 @@ bool run_test(TestBase &test) {
int child_status = 0;
waitpid(child_pid, &child_status, 0);
test::TestStatus status;
if (WIFEXITED(child_status)) {
int exit_status = WEXITSTATUS(child_status);
if (exit_status & ~test::STATUS_MASK) {
@ -103,10 +170,10 @@ bool run_test(TestBase &test) {
default:
status = test::SIGNAL_UNCAUGHT;
}
} else {
status = test::SUCCESS;
}
#endif
if (test.expected_status == test::FAILED) {
return (status & test::FAILED);
}
@ -120,6 +187,25 @@ bool run_test(TestBase &test) {
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 total_cnt = 0;
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("-- tests passing: %lu/%lu", success_cnt, total_cnt);
printf("-- tests passing: %zu/%zu", success_cnt, total_cnt);
if (total_cnt) {
printf(" (%lu%%)\n", success_cnt * 100 / total_cnt);
printf(" (%zu%%)\n", success_cnt * 100 / total_cnt);
} else {
printf("\n");
}

View File

@ -24,17 +24,19 @@
#include "backward.hpp"
#include "test/test.hpp"
#include <cstdio>
#include <iostream>
using namespace backward;
void collect_trace(StackTrace &st) { st.load_here(); }
TEST(minitrace) {
Printer printer;
StackTrace st;
collect_trace(st);
Printer printer;
printer.print(st, stdout);
printer.print(st, std::cout);
}
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); }
TEST(smalltrace) {
Printer printer;
StackTrace st;
a(st);
Printer printer;
printer.print(st, stdout);
printer.print(st, std::cout);
}

View File

@ -25,7 +25,10 @@
#include "test/test.hpp"
#include <cstdio>
#ifndef _WIN32
#include <sys/resource.h>
#endif
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); }
TEST_SEGFAULT(stackoverflow) {
#ifndef _WIN32
struct rlimit limit;
limit.rlim_max = 8096;
setrlimit(RLIMIT_STACK, &limit);
#endif
int r = bye_bye_stack(42);
std::cout << "r=" << r << std::endl;
}