[PSEH/x64] Fix our GCC-SEH-plugin hack

The change is needed, because the previous version emitted multiple ".seh_handlerdata" blocks and GAS isn't able to merge them into one, but will instead only include the first one. This is fixed by generating "asm defines" in the code and pass the line number to the "REACTOS seh" pragma, which is then used to create all handler data by referring to the predefined symbols, that include the line number.
Also the EstablisherFrame parameter passed to the filter/finally function is the original stack pointer, not the frame pointer! Take this into account by passing __builtin_frame_address(0) from the filter context to the global wrapper function, which includes the frame-offset, and use that to recalculate the frame-pointer from the passed in stack pointer.
This commit is contained in:
Timo Kreuzer 2024-03-15 21:17:23 +02:00
parent f4a9f9cde3
commit 67c28b723e
2 changed files with 104 additions and 50 deletions

View file

@ -6,15 +6,19 @@ __asm__(
".p2align 4, 0x90\n"
".seh_proc __seh2_global_filter_func\n"
"__seh2_global_filter_func:\n"
/* r8 is rbp - frame-offset. Calculate the negative frame-offset */
"\tsub %rbp, %rax\n"
"\tpush %rbp\n"
"\t.seh_pushreg %rbp\n"
"\tsub $32, %rsp\n"
"\t.seh_stackalloc 32\n"
"\t.seh_endprologue\n"
/* rdx is the original stack pointer, fix it up to be the frame pointer */
"\tsub %rax, %rdx\n"
/* Restore frame pointer. */
"\tmov %rdx, %rbp\n"
/* Actually execute the filter funclet */
"\tjmp *%rax\n"
"\tjmp *%r8\n"
"__seh2_global_filter_func_exit:\n"
"\t.p2align 4\n"
"\tadd $32, %rsp\n"
@ -22,8 +26,36 @@ __asm__(
"\tret\n"
"\t.seh_endproc");
#define STRINGIFY(a) #a
#define EMIT_PRAGMA_(params) \
_Pragma( STRINGIFY(params) )
#define EMIT_PRAGMA(type,line) \
EMIT_PRAGMA_(REACTOS seh(type,line))
#define _SEH3$_EMIT_DEFS_AND_PRAGMA__(Line, Type) \
/* Emit assembler constants with line number to be individual */ \
__asm__ __volatile__ goto ("\n" \
"\t__seh2$$begin_try__" #Line "=%l0\n" /* Begin of tried code */ \
"\t__seh2$$end_try__" #Line "=%l1 + 1\n" /* End of tried code */ \
"\t__seh2$$filter__" #Line "=%l2\n" /* Filter function */ \
"\t__seh2$$begin_except__" #Line "=%l3\n" /* Called on except */ \
: /* No output */ \
: /* No input */ \
: /* No clobber */ \
: __seh2$$begin_try__, \
__seh2$$end_try__, \
__seh2$$filter__, \
__seh2$$begin_except__); \
/* Call our home-made pragma */ \
EMIT_PRAGMA(Type,Line)
#define _SEH3$_EMIT_DEFS_AND_PRAGMA_(Line, Type) _SEH3$_EMIT_DEFS_AND_PRAGMA__(Line, Type)
#define _SEH3$_EMIT_DEFS_AND_PRAGMA(Type) _SEH3$_EMIT_DEFS_AND_PRAGMA_(__LINE__, Type)
#define _SEH2_TRY \
{ \
__label__ __seh2$$filter__; \
__label__ __seh2$$begin_except__; \
__label__ __seh2$$begin_try__; \
__label__ __seh2$$end_try__; \
/* \
@ -40,41 +72,25 @@ __seh2$$begin_try__:
#define _SEH2_EXCEPT(...) \
__seh2$$leave_scope__: __MINGW_ATTRIB_UNUSED; \
} \
__seh2$$end_try__: \
__seh2$$end_try__:(void)0; \
/* Call our home-made pragma */ \
_Pragma("REACTOS seh(except)") \
_SEH3$_EMIT_DEFS_AND_PRAGMA(__seh$$except); \
if (0) \
{ \
__label__ __seh2$$leave_scope__; \
__label__ __seh2$$filter__; \
__label__ __seh2$$begin_except__; \
LONG __MINGW_ATTRIB_UNUSED __seh2$$exception_code__ = 0; \
LONG __MINGW_ATTRIB_UNUSED __seh2$$exception_code__; \
/* Add our handlers to the list */ \
__asm__ __volatile__ goto ("\n" \
"\t.seh_handlerdata\n" \
"\t.rva %l0\n" /* Begin of tried code */ \
"\t.rva %l1 + 1\n" /* End of tried code */ \
"\t.rva %l2\n" /* Filter function */ \
"\t.rva %l3\n" /* Called on except */ \
"\t.seh_code\n" \
: /* No output */ \
: /* No input */ \
: /* No clobber */ \
: __seh2$$begin_try__, \
__seh2$$end_try__, \
__seh2$$filter__, \
__seh2$$begin_except__); \
if (0) \
{ \
/* Jump to the global filter. Tell it where the filter funclet lies */ \
__label__ __seh2$$filter_funclet__; \
__seh2$$filter__: \
__asm__ __volatile__ goto( \
"\tleaq %l0(%%rip), %%rax\n" \
"\tleaq %l1(%%rip), %%r8\n" \
"\tjmp __seh2_global_filter_func\n" \
: /* No output */ \
: /* No input */ \
: "%rax" \
: "a"(__builtin_frame_address(0)) \
: "%r8" \
: __seh2$$filter_funclet__); \
/* Actually declare our filter funclet */ \
struct _EXCEPTION_POINTERS* __seh2$$exception_ptr__; \
@ -105,38 +121,28 @@ __seh2$$end_try__:
__seh2$$leave_scope__: __MINGW_ATTRIB_UNUSED; \
} \
__seh2$$end_try__: \
__seh2$$begin_except__: __MINGW_ATTRIB_UNUSED; \
/* Call our home-made pragma */ \
_Pragma("REACTOS seh(finally)") \
_SEH3$_EMIT_DEFS_AND_PRAGMA(__seh$$finally); \
if (1) \
{ \
__label__ __seh2$$finally__; \
__label__ __seh2$$begin_finally__; \
__label__ __seh2$$leave_scope__; \
__asm__ __volatile__ goto("" : : : : __seh2$$finally__); \
int __seh2$$abnormal_termination__; \
/* Add our handlers to the list */ \
__asm__ __volatile__ goto ("\n" \
"\t.seh_handlerdata\n" \
"\t.rva %l0\n" /* Begin of tried code */ \
"\t.rva %l1 + 1\n" /* End of tried code */ \
"\t.rva %l2\n" /* Filter function */ \
"\t.long 0\n" /* Nothing for unwind code */ \
"\t.seh_code\n" \
: /* No output */ \
: /* No input */ \
: /* No clobber */ \
: __seh2$$begin_try__, \
__seh2$$end_try__, \
__seh2$$finally__); \
if (0) \
{ \
/* Jump to the global trampoline. Tell it where the unwind code really lies */ \
__seh2$$finally__: \
__seh2$$filter__: __MINGW_ATTRIB_UNUSED; \
__seh2$$finally__: __MINGW_ATTRIB_UNUSED; \
__asm__ __volatile__ goto( \
"\tleaq %l0(%%rip), %%rax\n" \
"\t\n" \
"\tleaq %l1(%%rip), %%r8\n" \
"\tjmp __seh2_global_filter_func\n" \
: /* No output */ \
: /* No input */ \
: /* No clobber */ \
: "a"(__builtin_frame_address(0)) \
: "%r8" \
: __seh2$$begin_finally__); \
} \
\

View file

@ -3,6 +3,7 @@
* LICENSE: BSD Zero Clause License (https://spdx.org/licenses/0BSD)
* PURPOSE: Helper pragma implementation for pseh library (amd64)
* COPYRIGHT: Copyright 2021 Jérôme Gardou
* Copyright 2024 Timo Kreuzer <timo.kreuzer@reactos.org>
*/
#include <gcc-plugin.h>
@ -16,6 +17,13 @@
#include <sstream>
#include <unordered_map>
#include <vector>
#include <cstdio>
#if 0 // To enable tracing
#define trace(...) fprintf(stderr, __VA_ARGS__)
#else
#define trace(...)
#endif
#define is_alpha(c) (((c)>64 && (c)<91) || ((c)>96 && (c)<123))
@ -31,6 +39,14 @@ int
VISIBLE
plugin_is_GPL_compatible = 1;
constexpr size_t k_header_statement_max_size = 20000;
struct seh_handler
{
bool is_except;
unsigned int line;
};
struct seh_function
{
bool unwind;
@ -38,6 +54,7 @@ struct seh_function
tree asm_header_text;
tree asm_header;
size_t count;
std::vector<seh_handler> handlers;
seh_function(struct function* fun)
: unwind(false)
@ -45,9 +62,13 @@ struct seh_function
, count(0)
{
/* Reserve space for our header statement */
char buf[256];
#if 0 // FIXME: crashes on older GCC
asm_header_text = build_string(k_header_statement_max_size, "");
#else
char buf[k_header_statement_max_size];
memset(buf, 0, sizeof(buf));
asm_header_text = build_string(sizeof(buf), buf);
#endif
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);
@ -74,8 +95,9 @@ static
void
handle_seh_pragma(cpp_reader* UNUSED parser)
{
tree x, arg;
tree x, arg, line;
std::stringstream label_decl;
bool is_except;
if (!cfun)
{
@ -84,21 +106,32 @@ handle_seh_pragma(cpp_reader* UNUSED parser)
}
if ((pragma_lex(&x) != CPP_OPEN_PAREN) ||
(pragma_lex(&arg) != CPP_NAME) ||
(pragma_lex(&arg) != CPP_NAME) || // except or finally
(pragma_lex(&x) != CPP_COMMA) ||
(pragma_lex(&line) != CPP_NUMBER) || // Line number
(pragma_lex(&x) != CPP_CLOSE_PAREN) ||
(pragma_lex(&x) != CPP_EOF))
(pragma_lex(&x) != CPP_EOF)
)
{
error("%<#pragma REACTOS seh%> needs one parameter%>");
error("%<#pragma REACTOS seh%> needs two parameters%>");
return;
}
trace(stderr, "Pragma: %s, %u\n", IDENTIFIER_POINTER(arg), TREE_INT_CST_LOW(line));
const char* op = IDENTIFIER_POINTER(arg);
seh_function* seh_fun = get_seh_function();
if (strcmp(op, "except") == 0)
if (strcmp(op, "__seh$$except") == 0)
{
is_except = true;
seh_fun->except = true;
else if (strcmp(op, "finally") == 0)
}
else if (strcmp(op, "__seh$$finally") == 0)
{
is_except = false;
seh_fun->unwind = true;
}
else
{
error("Wrong argument for %<#pragma REACTOS seh%>. Expected \"except\" or \"finally\"");
@ -106,6 +139,8 @@ handle_seh_pragma(cpp_reader* UNUSED parser)
}
seh_fun->count++;
seh_fun->handlers.push_back({is_except, (unsigned int)TREE_INT_CST_LOW(line)});
/* Make sure we use a frame pointer. REACTOS' PSEH depends on this */
cfun->machine->accesses_prev_frame = 1;
}
@ -142,12 +177,25 @@ finish_seh_function(void* event_data, void* UNUSED user_data)
asm_str << "\n";
asm_str << "\t.seh_handlerdata\n";
asm_str << "\t.long " << seh_fun->count << "\n";
asm_str << "\t.seh_code";
for (auto& handler : seh_fun->handlers)
{
asm_str << "\n\t.rva " << "__seh2$$begin_try__" << handler.line; /* Begin of tried code */
asm_str << "\n\t.rva " << "__seh2$$end_try__" << handler.line; /* End of tried code */
asm_str << "\n\t.rva " << "__seh2$$filter__" << handler.line; /* Filter function */
if (handler.is_except)
asm_str << "\n\t.rva " << "__seh2$$begin_except__" << handler.line; /* Called on except */
else
asm_str << "\n\t.long 0"; /* No unwind handler */
}
asm_str << "\n\t.seh_code\n";
strncpy(const_cast<char*>(TREE_STRING_POINTER(seh_fun->asm_header_text)),
asm_str.str().c_str(),
TREE_STRING_LENGTH(seh_fun->asm_header_text));
trace(stderr, "ASM: %s\n", asm_str.str().c_str());
delete seh_fun;
}