Add baseline mingw support (#13)

This commit is contained in:
Jeremy Rifkin 2023-07-21 22:33:56 -04:00 committed by GitHub
parent 7c9c4bc5be
commit d7a5eb54fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 368 additions and 32 deletions

View File

@ -43,3 +43,13 @@ jobs:
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py --clang-only
build-windows-mingw:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py --mingw-only

View File

@ -45,3 +45,13 @@ jobs:
run: |
pip3 install colorama
python3 ci/test-all-configs.py --clang-only
build-windows-mingw:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --mingw-only

View File

@ -133,7 +133,7 @@ if(
if(HAS_STACKTRACE AND NOT WIN32) # Our trace is better than msvc's <stacktrace>
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using C++23 <stacktrace> for the full trace")
elseif(HAS_BACKTRACE)
elseif(HAS_BACKTRACE AND NOT WIN32) # Mingw libbacktrace doesn't seem to be working
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace")
endif()
@ -163,6 +163,17 @@ if(
set(CPPTRACE_UNWIND_WITH_NOTHING On)
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
endif()
elseif(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # mingw
if(HAS_UNWIND)
set(CPPTRACE_UNWIND_WITH_UNWIND On)
message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding")
elseif(HAS_EXECINFO)
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
endif()
elseif(WIN32)
set(CPPTRACE_UNWIND_WITH_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
@ -189,11 +200,14 @@ if(
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDL ON)
elseif(UNIX)
if(HAS_BACKTRACE)
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
set(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for symbols")
else()
message(FATAL_ERROR "Cpptrace auto config: No symbol back-end could be automatically configured. To compile anyway set CPPTRACE_GET_SYMBOLS_WITH_NOTHING.")
endif()
elseif(WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # mingw
set(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE On)
message(STATUS "Cpptrace auto config: Using addr2line for symbols")
elseif(WIN32)
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
@ -260,7 +274,9 @@ endif()
if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
target_link_libraries(cpptrace PRIVATE dl)
if(UNIX)
target_link_libraries(cpptrace PRIVATE dl)
endif()
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)

View File

@ -54,7 +54,7 @@ def build(matrix):
if succeeded:
run_command("make", "-j")
else:
succeeded = run_command(
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
@ -63,9 +63,15 @@ def build(matrix):
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On"
)
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
run_command("msbuild", "cpptrace.sln")
if matrix["compiler"] == "g++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
@ -103,10 +109,14 @@ def build_full_or_auto(matrix):
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
print(args)
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
run_command("msbuild", "cpptrace.sln")
if matrix["compiler"] == "g++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
@ -188,13 +198,19 @@ def main():
"--msvc-only",
action="store_true"
)
parser.add_argument(
"--mingw-only",
action="store_true"
)
args = parser.parse_args()
compilers = ["cl", "clang++"]
compilers = ["cl", "clang++", "g++"]
if args.clang_only:
compilers = ["clang++"]
if args.msvc_only:
compilers = ["cl"]
if args.mingw_only:
compilers = ["g++"]
matrix = {
"compiler": compilers,
@ -202,10 +218,12 @@ def main():
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_WINAPI",
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
@ -217,7 +235,27 @@ def main():
{
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
"compiler": "cl"
}
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "cl"
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "cl"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"compiler": "g++"
},
]
run_matrix(matrix, exclude, build)
matrix = {

View File

@ -161,7 +161,7 @@ def build(matrix):
if succeeded:
return run_command("make", "-j")
else:
succeeded = run_command(
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
@ -172,9 +172,15 @@ def build(matrix):
f"-D{matrix['demangle']}=On",
"-DCPPTRACE_BUILD_TEST=On",
"-DBUILD_SHARED_LIBS=On"
)
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
return run_command("msbuild", "cpptrace.sln")
if matrix["compiler"] == "g++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
def build_full_or_auto(matrix):
if platform.system() != "Windows":
@ -205,10 +211,14 @@ def build_full_or_auto(matrix):
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
print(args)
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
return run_command("msbuild", "cpptrace.sln")
if matrix["compiler"] == "g++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
def test(matrix):
if platform.system() != "Windows":
@ -217,10 +227,16 @@ def test(matrix):
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
if matrix["compiler"] == "g++":
run_test(
f".\\test.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
def test_full_or_auto(matrix):
if platform.system() != "Windows":
@ -229,10 +245,16 @@ def test_full_or_auto(matrix):
(matrix["compiler"],)
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
(matrix["compiler"],)
)
if matrix["compiler"] == "g++":
run_test(
f".\\test.exe",
(matrix["compiler"],)
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
(matrix["compiler"],)
)
def build_and_test(matrix):
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
@ -341,13 +363,19 @@ def main():
"--msvc-only",
action="store_true"
)
parser.add_argument(
"--mingw-only",
action="store_true"
)
args = parser.parse_args()
compilers = ["cl", "clang++"]
compilers = ["cl", "clang++", "g++"]
if args.clang_only:
compilers = ["clang++"]
if args.msvc_only:
compilers = ["cl"]
if args.mingw_only:
compilers = ["g++"]
matrix = {
"compiler": compilers,
@ -355,10 +383,12 @@ def main():
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_WINAPI",
"CPPTRACE_UNWIND_WITH_UNWIND",
#"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
#"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
@ -370,6 +400,26 @@ def main():
{
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
"compiler": "cl"
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "cl"
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "cl"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"compiler": "g++"
}
]
run_matrix(matrix, exclude, build_and_test)

View File

@ -19,9 +19,44 @@
#include <ios>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#define IS_WINDOWS 0
#define IS_LINUX 0
#define IS_APPLE 0
#if defined(_WIN32)
#undef IS_WINDOWS
#define IS_WINDOWS 1
#elif defined(__linux)
#undef IS_LINUX
#define IS_LINUX 1
#elif defined(__APPLE__)
#undef IS_APPLE
#define IS_APPLE 1
#else
#error "Unexpected platform"
#endif
#define IS_CLANG 0
#define IS_GCC 0
#define IS_MSVC 0
#if defined(__clang__)
#undef IS_CLANG
#define IS_CLANG 1
#elif defined(__GNUC__) || defined(__GNUG__)
#undef IS_GCC
#define IS_GCC 1
#elif defined(_MSC_VER)
#undef IS_MSVC
#define IS_MSVC 1
#else
#error "Unsupported compiler"
#endif
// Lightweight std::source_location.
struct source_location {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
@ -128,6 +163,61 @@ static std::string to_hex(uintptr_t addr) {
return std::move(sstream).str();
}
CPPTRACE_MAYBE_UNUSED
static bool is_little_endian() {
uint16_t num = 0x1;
auto* ptr = (uint8_t*)&num;
return ptr[0] == 1;
}
// Modified from
// https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c
template<typename T, size_t N>
struct byte_swapper;
template<typename T>
struct byte_swapper<T, 1> {
T operator()(T val) {
return val;
}
};
template<typename T>
struct byte_swapper<T, 2> {
T operator()(T val) {
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct byte_swapper<T, 4> {
T operator()(T val) {
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<typename T>
struct byte_swapper<T, 8> {
T operator()(T val) {
return ((((val) & 0xff00000000000000ull) >> 56) |
(((val) & 0x00ff000000000000ull) >> 40) |
(((val) & 0x0000ff0000000000ull) >> 24) |
(((val) & 0x000000ff00000000ull) >> 8 ) |
(((val) & 0x00000000ff000000ull) << 8 ) |
(((val) & 0x0000000000ff0000ull) << 24) |
(((val) & 0x000000000000ff00ull) << 40) |
(((val) & 0x00000000000000ffull) << 56));
}
};
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap(T value) {
return byte_swapper<T, sizeof(T)>{}(value);
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -12,11 +12,15 @@
#include <functional>
#include <vector>
#include <unistd.h>
#include <dlfcn.h>
// NOLINTNEXTLINE(misc-include-cleaner)
#include <sys/types.h>
#include <sys/wait.h>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
// NOLINTNEXTLINE(misc-include-cleaner)
#include <sys/types.h>
#include <sys/wait.h>
#elif IS_WINDOWS
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
@ -27,6 +31,7 @@ namespace cpptrace {
uintptr_t raw_address = 0;
};
#if IS_LINUX || IS_APPLE
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
std::vector<dlframe> backtrace_frames(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
@ -41,7 +46,7 @@ namespace cpptrace {
// but we don't really need dli_saddr
frame.obj_path = info.dli_fname;
frame.obj_base = reinterpret_cast<uintptr_t>(info.dli_fbase);
frame.symbol = info.dli_sname ?: "?";
frame.symbol = info.dli_sname ?: "";
}
frames.push_back(frame);
}
@ -113,6 +118,118 @@ namespace cpptrace {
return output;
}
uintptr_t get_module_image_base(const dlframe &entry) {
(void)entry;
return 0;
}
#elif IS_WINDOWS
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
std::vector<dlframe> backtrace_frames(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
HMODULE handle;
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
static_cast<const char*>(addr),
&handle
)) {
fflush(stderr);
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
///fprintf(stderr, "path: %s base: %p\n", path, handle);
frame.obj_path = path;
frame.obj_base = reinterpret_cast<uintptr_t>(handle);
frame.symbol = "";
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
frames.push_back(frame);
}
return frames;
}
bool has_addr2line() {
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
FILE* p = popen("addr2line --version", "r");
return pclose(p) == 0;
}
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
FILE* p = popen(("addr2line -e " + executable + " -fCp " + addresses).c_str(), "r");
std::string output;
constexpr int buffer_size = 4096;
char buffer[buffer_size];
size_t count = 0;
while((count = fread(buffer, 1, buffer_size, p)) > 0) {
output.insert(output.end(), buffer, buffer + count);
}
pclose(p);
///fprintf(stderr, "%s\n", output.c_str());
return output;
}
// TODO: Refactor into backtrace_frames...
// TODO: Memoize
uintptr_t get_module_image_base(const dlframe &entry) {
// PE header values are little endian
bool do_swap = !is_little_endian();
FILE* file = fopen(entry.obj_path.c_str(), "rb");
char magic[2];
internal_verify(fread(magic, 1, 2, file) == 2); // file + 0x0
internal_verify(memcmp(magic, "MZ", 2) == 0);
DWORD e_lfanew;
internal_verify(fseek(file, 0x3c, SEEK_SET) == 0);
internal_verify(fread(&e_lfanew, sizeof(DWORD), 1, file) == 1); // file + 0x3c
if(do_swap) e_lfanew = byteswap(e_lfanew);
long nt_header_offset = e_lfanew;
char signature[4];
internal_verify(fseek(file, nt_header_offset, SEEK_SET) == 0);
internal_verify(fread(signature, 1, 4, file) == 4); // NT header + 0x0
internal_verify(memcmp(signature, "PE\0\0", 4) == 0);
//WORD machine;
//internal_verify(fseek(file, nt_header_offset + 4, SEEK_SET) == 0); // file header + 0x0
//internal_verify(fread(&machine, sizeof(WORD), 1, file) == 1);
WORD size_of_optional_header;
internal_verify(fseek(file, nt_header_offset + 4 + 0x10, SEEK_SET) == 0); // file header + 0x10
internal_verify(fread(&size_of_optional_header, sizeof(DWORD), 1, file) == 1);
if(do_swap) size_of_optional_header = byteswap(size_of_optional_header);
internal_verify(size_of_optional_header != 0);
WORD optional_header_magic;
internal_verify(fseek(file, nt_header_offset + 0x18, SEEK_SET) == 0); // optional header + 0x0
internal_verify(fread(&optional_header_magic, sizeof(DWORD), 1, file) == 1);
if(do_swap) optional_header_magic = byteswap(optional_header_magic);
internal_verify(optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC);
uintptr_t image_base;
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
// 32 bit
DWORD base;
internal_verify(fseek(file, nt_header_offset + 0x18 + 0x1c, SEEK_SET) == 0); // optional header + 0x1c
internal_verify(fread(&base, sizeof(DWORD), 1, file) == 1);
if(do_swap) base = byteswap(base);
image_base = base;
} else {
// 64 bit
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
unsigned __int64 base;
internal_verify(fseek(file, nt_header_offset + 0x18 + 0x18, SEEK_SET) == 0); // optional header + 0x18
internal_verify(fread(&base, sizeof(unsigned __int64), 1, file) == 1);
if(do_swap) base = byteswap(base);
image_base = base;
}
fclose(file);
return image_base;
}
#endif
struct symbolizer::impl {
using target_vec = std::vector<std::pair<std::string, std::reference_wrapper<stacktrace_frame>>>;
@ -124,8 +241,9 @@ namespace cpptrace {
std::unordered_map<std::string, target_vec> entries;
for(std::size_t i = 0; i < dlframes.size(); i++) {
const auto& entry = dlframes[i];
///fprintf(stderr, "%s %s\n", to_hex(entry.raw_address).c_str(), to_hex(entry.raw_address - entry.obj_base + base).c_str());
entries[entry.obj_path].emplace_back(
to_hex(entry.raw_address - entry.obj_base),
to_hex(entry.raw_address - entry.obj_base + get_module_image_base(entry)),
trace[i]
);
// Set what is known for now, and resolutions from addr2line should overwrite
@ -182,7 +300,11 @@ namespace cpptrace {
std::string address_input;
for(const auto& pair : entries_vec) {
address_input += pair.first;
address_input += '\n';
#if !IS_WINDOWS
address_input += '\n';
#else
address_input += ' ';
#endif
}
auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
internal_verify(output.size() == entries_vec.size());