diff --git a/.github/workflows/distro-ci.yml b/.github/workflows/distro-ci.yml index 75c6c769..56848d43 100644 --- a/.github/workflows/distro-ci.yml +++ b/.github/workflows/distro-ci.yml @@ -38,8 +38,9 @@ jobs: set -e DISTRO=$( cat /etc/*-release | tr [:upper:] [:lower:] | grep -Poi '(debian|ubuntu|fedora|gentoo|alpine)' | uniq ) if [ "$DISTRO" == "gentoo" ]; then source /etc/profile; fi - git clone https://github.com/HyperWinX/HyperCPU.git --recursive && cd HyperCPU + git clone https://github.com/HyperWinX/HyperCPU.git && cd HyperCPU git switch "${{ github.head_ref }}" + git submodule update --init --recursive cd dist/pog && git pull origin master && cd ../.. cmake -S. -Bbuild -DHCPU_COMPILER=clang -DHCPU_LTO=ON -DHCPU_SANITIZERS=OFF -DCMAKE_BUILD_TYPE=Release cmake --build build --target run-all-tests-github -j8 diff --git a/.gitmodules b/.gitmodules index f769b4dc..1ca87879 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,9 @@ [submodule "dist/benchmark"] path = dist/benchmark url = https://github.com/google/benchmark +[submodule "dist/libbacktrace"] + path = dist/libbacktrace + url = https://github.com/ianlancetaylor/libbacktrace +[submodule "dist/libunwind"] + path = dist/libunwind + url = https://github.com/libunwind/libunwind diff --git a/CMakeLists.txt b/CMakeLists.txt index 45a811a0..67707944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Generating source files list") include(cmake/SourceListGenerator.cmake) - add_subdirectory(dist/argparse) add_subdirectory(dist/pog) add_subdirectory(bench) diff --git a/cmake/Configuration.cmake b/cmake/Configuration.cmake index 4d0e4941..16e95fa8 100644 --- a/cmake/Configuration.cmake +++ b/cmake/Configuration.cmake @@ -37,6 +37,14 @@ function(set_compile_flags) add_link_options(-fsanitize=address,leak) message(STATUS "Enabling sanitizers") endif() + + find_library(LIBUNWIND unwind) + set(LIBUNWIND ${LIBUNWIND} PARENT_SCOPE) + if (LIBUNWIND) + message(STATUS "Found libunwind") + add_compile_options(-DHCPU_ENABLE_LIBUNWIND) + add_link_options(-L${ROOT_DIR}/dist/libbacktrace -lunwind -lbacktrace) + endif() endfunction() function(detect_compilers) diff --git a/cmake/TargetAndFolderAssoc.cmake b/cmake/TargetAndFolderAssoc.cmake index c2fc8e60..ee27762c 100644 --- a/cmake/TargetAndFolderAssoc.cmake +++ b/cmake/TargetAndFolderAssoc.cmake @@ -6,6 +6,8 @@ set(__TARGETS_LIST assembler-core assembler-main + backtrace-provider + # Testing modular_testing @@ -25,6 +27,9 @@ set(__DIRECTORIES_LIST # assembler-main target ${ROOT_DIR}/src/Assembler/Main +# backtrace-provider target + ${ROOT_DIR}/src/BacktraceProvider + # modulartesting_src target ${ROOT_DIR}/test/Modular diff --git a/cmake/Variables.cmake b/cmake/Variables.cmake index a6f3b6b3..1d185b4f 100644 --- a/cmake/Variables.cmake +++ b/cmake/Variables.cmake @@ -1,7 +1,2 @@ -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(LTO_FLAG "-flto=thin") -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(LTO_FLAG "-flto") -endif() set(DEBUG_COMPILE_FLAGS -Wall -Wextra -Werror -Wno-pointer-arith -O0 -ggdb3 -Wno-unused-const-variable -Wno-missing-field-initializers -Wno-stringop-overflow -Wno-unknown-warning-option -D__HCPU_DEBUG) set(FAST_COMPILE_FLAGS -Wall -Wextra -Werror -Wno-pointer-arith -O3 -Wno-unused-const-variable -Wno-missing-field-initializers -Wno-stringop-overflow -Wno-unknown-warning-option) diff --git a/dist/libbacktrace b/dist/libbacktrace new file mode 160000 index 00000000..79392187 --- /dev/null +++ b/dist/libbacktrace @@ -0,0 +1 @@ +Subproject commit 793921876c981ce49759114d7bb89bb89b2d3a2d diff --git a/src/Assembler/Main/Main.cpp b/src/Assembler/Main/Main.cpp index d2fcba4a..5a8e6954 100644 --- a/src/Assembler/Main/Main.cpp +++ b/src/Assembler/Main/Main.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -13,6 +14,9 @@ #include +#ifdef HCPU_ENABLE_LIBUNWIND +#include +#endif constexpr const inline auto loglevel_assoc = mapbox::eternal::map({ {"debug", HyperCPU::LogLevel::DEBUG}, @@ -22,6 +26,12 @@ constexpr const inline auto loglevel_assoc = mapbox::eternal::map + +#include +#include +#include +#include + +#define UNW_LOCAL_ONLY +#include +#include + +#include +#include + +#include +#include + +extern "C" { + void SignalHandler(int signal) { + switch(signal) { + case SIGSEGV: + catched_signal_type = "SIGSEGV"; + break; + case SIGFPE: + catched_signal_type = "SIGFPE"; + break; + } + global_bt_controller.Run(); + } + + void bt_create_error_callback(void*, const char* msg, int err) { + fmt::println("Error {} occurred when generating the stack trace: {}", err, msg); + } + + void bt_error_callback(void*, [[maybe_unused]] const char* msg, [[maybe_unused]] int err) { + fmt::println("{}[!] Error while getting the stacktrace!{}", B_RED, RESET); + } + + int bt_callback(void*, uintptr_t, const char* filename, int lineno, const char* function) { + if (global_bt_controller.HasFinished() || global_bt_controller.iteration < 3) { + ++global_bt_controller.iteration; + return 0; + } + + // Demangle function name + const char* func_name = function; + int status; + unw_word_t pc, sp; + std::unique_ptr demangled{abi::__cxa_demangle(function, nullptr, nullptr, &status), &std::free}; + if (!status) { + func_name = demangled.get(); + } + + // Null pointer protection + if (!func_name) { + func_name = ""; + } else if (!std::strcmp(func_name, "main")) { + global_bt_controller.SetFinished(); + } + if (!filename) { + filename = ""; + } + + // Extract PC and SP + if (unw_get_reg(&global_bt_controller.cursor, UNW_REG_IP, &pc)) { + fmt::println("{}[!] Unwinding stack failed: couldn't get PC!{}", B_RED, RESET); + std::exit(1); + } + + if (unw_get_reg(&global_bt_controller.cursor, UNW_REG_SP, &sp)) { + fmt::println("{}[!] Unwinding stack failed: couldn't get SP!{}", B_RED, RESET); + std::exit(1); + } + + fmt::println("{}frame #{} (PC: {:#x}, SP: {:#x}){}", B_YELLOW, global_bt_controller.iteration, pc, sp, RESET); + fmt::println("{}{}:{}, function: {}{}", B_YELLOW, filename, lineno, func_name, RESET); + + unw_step(&global_bt_controller.cursor); + + ++global_bt_controller.iteration; + return 0; + } +} + +BacktraceController global_bt_controller; +std::string_view catched_signal_type; + +void BacktraceController::Run() { + fmt::println("\n{}[!] HyperCPU encountered a {}!{}", B_RED, catched_signal_type, RESET); + if (unw_getcontext(&context) < 0) { + fmt::println("{}[!] Unwinding stack failed: couldn't initialize the context!{}", B_RED, RESET); + std::exit(1); + } + + if (unw_init_local(&cursor, &context) < 0) { + fmt::println("{}[!] Unwinding stack failed: couldn't initialize the context!{}", B_RED, RESET); + std::exit(1); + } + fmt::println("{}[>] Libunwind context initialized successfully, generating stack trace...{}\n", B_GREEN, RESET); + + unw_step(&cursor); + + backtrace_full((backtrace_state*)bt_state, 0, bt_callback, bt_error_callback, nullptr); + + std::abort(); +} + +void BacktraceController::SetFinished() { + finished = true; +} + +bool BacktraceController::HasFinished() { + return finished; +} + +#endif \ No newline at end of file diff --git a/src/BacktraceProvider/BacktraceProvider.hpp b/src/BacktraceProvider/BacktraceProvider.hpp new file mode 100644 index 00000000..4b0d8d55 --- /dev/null +++ b/src/BacktraceProvider/BacktraceProvider.hpp @@ -0,0 +1,40 @@ +#ifdef HCPU_ENABLE_LIBUNWIND +#include + +#include +#include + +extern "C" { + void bt_create_error_callback(void*, const char* msg, int err); +} + +class BacktraceController { +public: + BacktraceController() = default; + BacktraceController(char* name) : iteration(0), finished(false) { + bt_state = backtrace_create_state(name, 0, bt_create_error_callback, nullptr); + } + + void Run(); + void SetFinished(); + bool HasFinished(); + + int iteration; // to slice entries that are not needed + + // libunwind + unw_cursor_t cursor; + unw_context_t context; +private: + // libbacktrace + void* bt_state; + + // internals + bool finished; +}; + +extern BacktraceController global_bt_controller; +extern std::string_view catched_signal_type; + +extern "C" void SignalHandler(int); + +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5bac8b39..1a099227 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,26 +1,53 @@ +include(ExternalProject) + set(GENERIC_INCLUDE_DIR - ${HyperCPU_SOURCE_DIR}/src - ${HyperCPU_SOURCE_DIR}/dist/argparse/include - ${HyperCPU_SOURCE_DIR}/dist/pog/include - ${HyperCPU_SOURCE_DIR}/dist/eternal/include - ${HyperCPU_SOURCE_DIR}/dist/HPool) + ${ROOT_DIR}/src + ${ROOT_DIR}/dist/argparse/include + ${ROOT_DIR}/dist/pog/include + ${ROOT_DIR}/dist/eternal/include + ${ROOT_DIR}/dist/HPool) + +ExternalProject_Add( + libbacktrace + SOURCE_DIR + ${ROOT_DIR}/dist/libbacktrace + BUILD_IN_SOURCE + 0 + CONFIGURE_COMMAND + cd ${ROOT_DIR}/dist/libbacktrace && ./configure + BUILD_COMMAND + cd ${ROOT_DIR}/dist/libbacktrace && make -j$(nproc) + INSTALL_COMMAND + "" + BUILD_BYPRODUCTS + ${ROOT_DIR}/dist/libbacktrace/libbacktrace.la +) add_library(emulator-core STATIC ${SOURCES_emulator-core}) -target_include_directories(emulator-core PUBLIC ${GENERIC_INCLUDE_DIR} ${HyperCPU_SOURCE_DIR}/src/Emulator) +target_include_directories(emulator-core PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Emulator) target_link_libraries(emulator-core ${LD_FLAGS} fmt) add_library(assembler-core STATIC ${SOURCES_assembler-core}) -target_include_directories(assembler-core PUBLIC ${GENERIC_INCLUDE_DIR} ${HyperCPU_SOURCE_DIR}/src/Assembler) +target_include_directories(assembler-core PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Assembler) target_link_libraries(assembler-core pog) +add_library(backtrace-provider STATIC ${SOURCES_backtrace-provider}) +target_include_directories(backtrace-provider PUBLIC ${GENERIC_INCLUDE_DIR}) +add_dependencies(backtrace-provider libbacktrace) + add_executable(hcasm ${SOURCES_assembler-main}) -target_include_directories(hcasm PUBLIC ${GENERIC_INCLUDE_DIR} ${HyperCPU_SOURCE_DIR}/src/Assembler) +target_include_directories(hcasm PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Assembler) target_link_libraries(hcasm assembler-core pog) add_executable(hcemul ${SOURCES_emulator-main}) -target_include_directories(hcemul PUBLIC ${GENERIC_INCLUDE_DIR} ${HyperCPU_SOURCE_DIR}/src/Emulator) +target_include_directories(hcemul PUBLIC ${GENERIC_INCLUDE_DIR} ${ROOT_DIR}/src/Emulator) target_link_libraries(hcemul emulator-core assembler-core) +if (LIBUNWIND) + target_link_libraries(hcasm backtrace-provider -L${ROOT_DIR}/dist/libbacktrace backtrace) + target_link_libraries(hcemul backtrace-provider -L${ROOT_DIR}/dist/libbacktrace backtrace) +endif() + add_custom_target(default DEPENDS hcasm diff --git a/src/Emulator/Main/Main.cpp b/src/Emulator/Main/Main.cpp index e5f2e8a4..1b824434 100644 --- a/src/Emulator/Main/Main.cpp +++ b/src/Emulator/Main/Main.cpp @@ -1,7 +1,8 @@ #include #include -#include +#include + #include #include #include @@ -12,9 +13,19 @@ #include +#ifdef HCPU_ENABLE_LIBUNWIND +#include +#endif + HyperCPU::GenericHeader ParseHeader(std::ifstream& binary); int main(int argc, char** argv) { +#ifdef HCPU_ENABLE_LIBUNWIND + global_bt_controller = BacktraceController(argv[0]); + + std::signal(SIGSEGV, SignalHandler); + std::signal(SIGFPE, SignalHandler); +#endif HyperCPU::Logger logger{HyperCPU::LogLevel::ERROR}; argparse::ArgumentParser program("hcemul", HCPU_VERSION); program.add_argument("binary") diff --git a/src/Logger/Colors.hpp b/src/Logger/Colors.hpp index bb43ca68..3d75e0a9 100644 --- a/src/Logger/Colors.hpp +++ b/src/Logger/Colors.hpp @@ -2,6 +2,7 @@ static constexpr char RESET[] = "\e[0m"; static constexpr char B_RED[] = "\e[1;31m"; +static constexpr char B_GREEN[] = "\e[1;32m"; static constexpr char B_YELLOW[] = "\e[1;33m"; static constexpr char RED[] = "\e[0;31m"; static constexpr char YELLOW[] = "\e[0;33m"; \ No newline at end of file diff --git a/test.cpp b/test.cpp new file mode 100644 index 00000000..e69de29b diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5bb2c3b4..77e5ec8a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,15 +1,15 @@ include(../cmake/Variables.cmake) set(TESTS_INCLUDE_DIR - ${HyperCPU_SOURCE_DIR}/dist/eternal/include - ${HyperCPU_SOURCE_DIR}/src/Emulator - ${HyperCPU_SOURCE_DIR}/test) + ${ROOT_DIR}/dist/eternal/include + ${ROOT_DIR}/src/Emulator + ${ROOT_DIR}/test) -add_executable(modular_testing ${HyperCPU_SOURCE_DIR}/test/main.cpp ${SOURCES_modular_testing}) +add_executable(modular_testing ${ROOT_DIR}/test/main.cpp ${SOURCES_modular_testing}) target_link_libraries(modular_testing emulator-core assembler-core gtest pthread) target_include_directories(modular_testing PUBLIC ${TESTS_INCLUDE_DIR}) -add_executable(integration_testing ${HyperCPU_SOURCE_DIR}/test/main.cpp ${SOURCES_integration_testing}) +add_executable(integration_testing ${ROOT_DIR}/test/main.cpp ${SOURCES_integration_testing}) target_link_libraries(integration_testing emulator-core assembler-core gtest pthread) target_include_directories(integration_testing PUBLIC ${TESTS_INCLUDE_DIR})