From 37bc01f42b6d940c763b0d346f44971c7abe08d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gardou?= Date: Thu, 22 Apr 2021 10:37:50 +0200 Subject: [PATCH] [CMAKE] Introduce a GCC plugin for helping with amd64 SEH implementation \#pragma REACTOS SEH(except) \#pragma REACTOS SEH(finally) What it does is counting the number of SEH __try blocks and emit the proper assembly statements at function prologue It also checks for mixing C++ & SEH exception handling, which wouldn't work --- sdk/cmake/host-tools.cmake | 23 ++- sdk/tools/CMakeLists.txt | 8 ++ sdk/tools/gcc_plugin_seh/CMakeLists.txt | 4 + sdk/tools/gcc_plugin_seh/main.cpp | 177 ++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 sdk/tools/gcc_plugin_seh/CMakeLists.txt create mode 100644 sdk/tools/gcc_plugin_seh/main.cpp diff --git a/sdk/cmake/host-tools.cmake b/sdk/cmake/host-tools.cmake index a738ff64c09..4633b1097d1 100644 --- a/sdk/cmake/host-tools.cmake +++ b/sdk/cmake/host-tools.cmake @@ -6,6 +6,13 @@ function(setup_host_tools) if(NOT MSVC) list(APPEND HOST_TOOLS rsym pefixup) endif() + if ((ARCH STREQUAL "amd64") AND (CMAKE_C_COMPILER_ID STREQUAL "GNU")) + execute_process( + COMMAND ${CMAKE_C_COMPILER} --print-file-name=plugin + OUTPUT_VARIABLE GCC_PLUGIN_DIR) + string(STRIP ${GCC_PLUGIN_DIR} GCC_PLUGIN_DIR) + list(APPEND HOST_MODULES gcc_plugin_seh) + endif() list(TRANSFORM HOST_TOOLS PREPEND "${REACTOS_BINARY_DIR}/host-tools/bin/" OUTPUT_VARIABLE HOST_TOOLS_OUTPUT) if (CMAKE_HOST_WIN32) list(TRANSFORM HOST_TOOLS_OUTPUT APPEND ".exe") @@ -13,13 +20,21 @@ function(setup_host_tools) set(HOST_EXTRA_DIR "$(ConfigurationName)/") endif() set(HOST_EXE_SUFFIX ".exe") + set(HOST_MODULE_SUFFIX ".dll") + else() + set(HOST_MODULE_SUFFIX ".so") endif() ExternalProject_Add(host-tools SOURCE_DIR ${REACTOS_SOURCE_DIR} PREFIX ${REACTOS_BINARY_DIR}/host-tools BINARY_DIR ${REACTOS_BINARY_DIR}/host-tools/bin - CMAKE_ARGS -UCMAKE_TOOLCHAIN_FILE -DARCH:STRING=${ARCH} -DCMAKE_INSTALL_PREFIX=${REACTOS_BINARY_DIR}/host-tools -DTOOLS_FOLDER=${REACTOS_BINARY_DIR}/host-tools/bin + CMAKE_ARGS + -UCMAKE_TOOLCHAIN_FILE + -DARCH:STRING=${ARCH} + -DCMAKE_INSTALL_PREFIX=${REACTOS_BINARY_DIR}/host-tools + -DTOOLS_FOLDER=${REACTOS_BINARY_DIR}/host-tools/bin + -DGCC_PLUGIN_DIR=${GCC_PLUGIN_DIR} BUILD_ALWAYS TRUE INSTALL_COMMAND ${CMAKE_COMMAND} -E true BUILD_BYPRODUCTS ${HOST_TOOLS_OUTPUT} @@ -32,4 +47,10 @@ function(setup_host_tools) set_target_properties(native-${_tool} PROPERTIES IMPORTED_LOCATION ${INSTALL_DIR}/bin/${HOST_EXTRA_DIR}${_tool}${HOST_EXE_SUFFIX}) add_dependencies(native-${_tool} host-tools ${INSTALL_DIR}/bin/${HOST_EXTRA_DIR}${_tool}${HOST_EXE_SUFFIX}) endforeach() + + foreach(_module ${HOST_MODULES}) + add_library(native-${_module} MODULE IMPORTED) + set_target_properties(native-${_module} PROPERTIES IMPORTED_LOCATION ${INSTALL_DIR}/bin/${HOST_EXTRA_DIR}${_module}${HOST_MODULE_SUFFIX}) + add_dependencies(native-${_module} host-tools ${INSTALL_DIR}/bin/${HOST_EXTRA_DIR}${_module}${HOST_MODULE_SUFFIX}) + endforeach() endfunction() diff --git a/sdk/tools/CMakeLists.txt b/sdk/tools/CMakeLists.txt index 3f836f8e9bf..956a6dc7060 100644 --- a/sdk/tools/CMakeLists.txt +++ b/sdk/tools/CMakeLists.txt @@ -4,6 +4,11 @@ function(add_host_tool _tool) set_target_properties(${_tool} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TOOLS_FOLDER}) endfunction() +function(add_host_module _module) + add_library(${_module} MODULE ${ARGN}) + set_target_properties(${_module} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${TOOLS_FOLDER}) +endfunction() + if(MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -DHAVE_IO_H=1) add_compile_options("$<$:/EHsc>") @@ -38,6 +43,9 @@ add_subdirectory(xml2sdb) if(NOT MSVC) add_subdirectory(log2lines) add_subdirectory(rsym) +if (ARCH STREQUAL "amd64") + add_subdirectory(gcc_plugin_seh) +endif() add_host_tool(pefixup pefixup.c) if (ARCH STREQUAL "amd64") diff --git a/sdk/tools/gcc_plugin_seh/CMakeLists.txt b/sdk/tools/gcc_plugin_seh/CMakeLists.txt new file mode 100644 index 00000000000..b96571f7fbe --- /dev/null +++ b/sdk/tools/gcc_plugin_seh/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_host_module(gcc_plugin_seh main.cpp) +target_include_directories(gcc_plugin_seh PRIVATE ${GCC_PLUGIN_DIR}/include) +set_target_properties(gcc_plugin_seh PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/sdk/tools/gcc_plugin_seh/main.cpp b/sdk/tools/gcc_plugin_seh/main.cpp new file mode 100644 index 00000000000..22df978e0c6 --- /dev/null +++ b/sdk/tools/gcc_plugin_seh/main.cpp @@ -0,0 +1,177 @@ +/* + * PROJECT: ReactOS SDK + * LICENSE: BSD Zero Clause License (https://spdx.org/licenses/0BSD.html) + * PURPOSE: Helper pragma implementation for pseh library (amd64) + * COPYRIGHT: Copyright 2021 Jérôme Gardou + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define is_alpha(c) (((c)>64 && (c)<91) || ((c)>96 && (c)<123)) + +#if defined(_WIN32) || defined(WIN32) +#define VISIBLE __decspec(dllexport) +#else +#define VISIBLE __attribute__((__visibility__("default"))) +#endif + +#define UNUSED __attribute__((__unused__)) + +int +VISIBLE +plugin_is_GPL_compatible = 1; + +struct seh_function +{ + bool unwind; + bool except; + tree asm_header_text; + tree asm_header; + size_t count; + + seh_function(struct function* fun) + : unwind(false) + , except(false) + , count(0) + { + /* Reserve space for our header statement */ + char buf[256]; + memset(buf, 0, sizeof(buf)); + asm_header_text = build_string(sizeof(buf), buf); + asm_header = build_stmt(fun->function_start_locus, ASM_EXPR, asm_header_text, NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE); + ASM_VOLATILE_P(asm_header) = 1; + add_stmt(asm_header); + } +}; + +static std::unordered_map func_seh_map = {}; + +static +struct seh_function* +get_seh_function() +{ + auto search = func_seh_map.find(cfun); + if (search != func_seh_map.end()) + return search->second; + + auto seh_fun = new seh_function(cfun); + func_seh_map.insert({cfun, seh_fun}); + + return seh_fun; +} + +static +void +handle_seh_pragma(cpp_reader* UNUSED parser) +{ + tree x, arg; + std::stringstream label_decl; + + if (!cfun) + { + error("%<#pragma REACTOS seh%> is not allowed outside functions"); + return; + } + + if ((pragma_lex(&x) != CPP_OPEN_PAREN) || + (pragma_lex(&arg) != CPP_NAME) || + (pragma_lex(&x) != CPP_CLOSE_PAREN) || + (pragma_lex(&x) != CPP_EOF)) + { + error("%<#pragma REACTOS seh%> needs one parameter%>"); + return; + } + + const char* op = IDENTIFIER_POINTER(arg); + + seh_function* seh_fun = get_seh_function(); + if (strcmp(op, "except") == 0) + seh_fun->except = true; + else if (strcmp(op, "finally") == 0) + seh_fun->unwind = true; + else + { + error("Wrong argument for %<#pragma REACTOS seh%>. Expected \"except\" or \"finally\""); + return; + } + seh_fun->count++; + + /* Make sure we use a frame pointer. REACTOS' PSEH depends on this */ + cfun->machine->accesses_prev_frame = 1; +} + +static +void +finish_seh_function(void* event_data, void* UNUSED user_data) +{ + tree fndef = (tree)event_data; + struct function* fun = DECL_STRUCT_FUNCTION(fndef); + + auto search = func_seh_map.find(fun); + if (search == func_seh_map.end()) + return; + + /* Get our SEH details and remove us from the map */ + seh_function* seh_fun = search->second; + func_seh_map.erase(search); + + if (DECL_FUNCTION_PERSONALITY(fndef) != nullptr) + { + error("Function %s has a personality. Are you mixing SEH with C++ exceptions ?", + IDENTIFIER_POINTER(fndef)); + return; + } + + /* Update asm statement */ + std::stringstream asm_str; + asm_str << ".seh_handler __C_specific_handler"; + if (seh_fun->unwind) + asm_str << ", @unwind"; + if (seh_fun->except) + asm_str << ", @except"; + asm_str << "\n"; + asm_str << "\t.seh_handlerdata\n"; + asm_str << "\t.long " << seh_fun->count << "\n"; + asm_str << "\t.seh_code"; + + strncpy(const_cast(TREE_STRING_POINTER(seh_fun->asm_header_text)), + asm_str.str().c_str(), + TREE_STRING_LENGTH(seh_fun->asm_header_text)); + + delete seh_fun; +} + +static +void +register_seh_pragmas(void* UNUSED event_data, void* UNUSED user_data) +{ + c_register_pragma("REACTOS", "seh", handle_seh_pragma); +} + +/* Return 0 on success or error code on failure */ +extern "C" +VISIBLE +int plugin_init(struct plugin_name_args *info, /* Argument infor */ + struct plugin_gcc_version *version) /* Version of GCC */ +{ + if (!plugin_default_version_check (version, &gcc_version)) + { + std::cerr << "This GCC plugin is for version " << GCCPLUGIN_VERSION_MAJOR << "." << GCCPLUGIN_VERSION_MINOR << "\n"; + return 1; + } + + register_callback(info->base_name, PLUGIN_PRAGMAS, register_seh_pragmas, NULL); + register_callback(info->base_name, PLUGIN_FINISH_PARSE_FUNCTION, finish_seh_function, NULL); + + return 0; +}