xref: /reactos/sdk/tools/gcc_plugin_seh/main.cpp (revision 67c28b72)
1 /*
2  * PROJECT:     ReactOS SDK
3  * LICENSE:     BSD Zero Clause License (https://spdx.org/licenses/0BSD)
4  * PURPOSE:     Helper pragma implementation for pseh library (amd64)
5  * COPYRIGHT:   Copyright 2021 Jérôme Gardou
6  *              Copyright 2024 Timo Kreuzer <timo.kreuzer@reactos.org>
7  */
8 
9 #include <gcc-plugin.h>
10 #include <plugin-version.h>
11 #include <function.h>
12 #include <tree.h>
13 #include <c-family/c-pragma.h>
14 #include <c-family/c-common.h>
15 
16 #include <iostream>
17 #include <sstream>
18 #include <unordered_map>
19 #include <vector>
20 #include <cstdio>
21 
22 #if 0 // To enable tracing
23 #define trace(...) fprintf(stderr, __VA_ARGS__)
24 #else
25 #define trace(...)
26 #endif
27 
28 #define is_alpha(c) (((c)>64 && (c)<91) || ((c)>96 && (c)<123))
29 
30 #if defined(_WIN32) || defined(WIN32)
31 #define VISIBLE __declspec(dllexport)
32 #else
33 #define VISIBLE __attribute__((__visibility__("default")))
34 #endif
35 
36 #define UNUSED __attribute__((__unused__))
37 
38 int
39 VISIBLE
40 plugin_is_GPL_compatible = 1;
41 
42 constexpr size_t k_header_statement_max_size = 20000;
43 
44 struct seh_handler
45 {
46     bool is_except;
47     unsigned int line;
48 };
49 
50 struct seh_function
51 {
52     bool unwind;
53     bool except;
54     tree asm_header_text;
55     tree asm_header;
56     size_t count;
57     std::vector<seh_handler> handlers;
58 
seh_functionseh_function59     seh_function(struct function* fun)
60         : unwind(false)
61         , except(false)
62         , count(0)
63     {
64         /* Reserve space for our header statement */
65 #if 0 // FIXME: crashes on older GCC
66         asm_header_text = build_string(k_header_statement_max_size, "");
67 #else
68         char buf[k_header_statement_max_size];
69         memset(buf, 0, sizeof(buf));
70         asm_header_text = build_string(sizeof(buf), buf);
71 #endif
72         asm_header = build_stmt(fun->function_start_locus, ASM_EXPR, asm_header_text, NULL_TREE, NULL_TREE, NULL_TREE, NULL_TREE);
73         ASM_VOLATILE_P(asm_header) = 1;
74         add_stmt(asm_header);
75     }
76 };
77 
78 static std::unordered_map<struct function*, struct seh_function*> func_seh_map = {};
79 
80 static
81 struct seh_function*
get_seh_function()82 get_seh_function()
83 {
84     auto search = func_seh_map.find(cfun);
85     if (search != func_seh_map.end())
86         return search->second;
87 
88     auto seh_fun = new seh_function(cfun);
89     func_seh_map.insert({cfun, seh_fun});
90 
91     return seh_fun;
92 }
93 
94 static
95 void
handle_seh_pragma(cpp_reader * UNUSED parser)96 handle_seh_pragma(cpp_reader* UNUSED parser)
97 {
98     tree x, arg, line;
99     std::stringstream label_decl;
100     bool is_except;
101 
102     if (!cfun)
103     {
104         error("%<#pragma REACTOS seh%> is not allowed outside functions");
105         return;
106     }
107 
108     if ((pragma_lex(&x) != CPP_OPEN_PAREN) ||
109         (pragma_lex(&arg) != CPP_NAME) || // except or finally
110         (pragma_lex(&x) != CPP_COMMA) ||
111         (pragma_lex(&line) != CPP_NUMBER) || // Line number
112         (pragma_lex(&x) != CPP_CLOSE_PAREN) ||
113         (pragma_lex(&x) != CPP_EOF)
114         )
115     {
116         error("%<#pragma REACTOS seh%> needs two parameters%>");
117         return;
118     }
119 
120     trace(stderr, "Pragma: %s, %u\n", IDENTIFIER_POINTER(arg), TREE_INT_CST_LOW(line));
121 
122     const char* op = IDENTIFIER_POINTER(arg);
123 
124     seh_function* seh_fun = get_seh_function();
125     if (strcmp(op, "__seh$$except") == 0)
126     {
127         is_except = true;
128         seh_fun->except = true;
129     }
130     else if (strcmp(op, "__seh$$finally") == 0)
131     {
132         is_except = false;
133         seh_fun->unwind = true;
134     }
135     else
136     {
137         error("Wrong argument for %<#pragma REACTOS seh%>. Expected \"except\" or \"finally\"");
138         return;
139     }
140     seh_fun->count++;
141 
142     seh_fun->handlers.push_back({is_except, (unsigned int)TREE_INT_CST_LOW(line)});
143 
144     /* Make sure we use a frame pointer. REACTOS' PSEH depends on this */
145     cfun->machine->accesses_prev_frame = 1;
146 }
147 
148 static
149 void
finish_seh_function(void * event_data,void * UNUSED user_data)150 finish_seh_function(void* event_data, void* UNUSED user_data)
151 {
152     tree fndef = (tree)event_data;
153     struct function* fun = DECL_STRUCT_FUNCTION(fndef);
154 
155     auto search = func_seh_map.find(fun);
156     if (search == func_seh_map.end())
157         return;
158 
159     /* Get our SEH details and remove us from the map */
160     seh_function* seh_fun = search->second;
161     func_seh_map.erase(search);
162 
163     if (DECL_FUNCTION_PERSONALITY(fndef) != nullptr)
164     {
165         error("Function %s has a personality. Are you mixing SEH with C++ exceptions ?",
166               IDENTIFIER_POINTER(fndef));
167         return;
168     }
169 
170     /* Update asm statement */
171     std::stringstream asm_str;
172     asm_str << ".seh_handler __C_specific_handler";
173     if (seh_fun->unwind)
174         asm_str << ", @unwind";
175     if (seh_fun->except)
176         asm_str << ", @except";
177     asm_str << "\n";
178     asm_str << "\t.seh_handlerdata\n";
179     asm_str << "\t.long " << seh_fun->count << "\n";
180 
181     for (auto& handler : seh_fun->handlers)
182     {
183         asm_str << "\n\t.rva " << "__seh2$$begin_try__" << handler.line; /* Begin of tried code */
184         asm_str << "\n\t.rva " << "__seh2$$end_try__" << handler.line; /* End of tried code */
185         asm_str << "\n\t.rva " << "__seh2$$filter__" << handler.line; /* Filter function */
186         if (handler.is_except)
187             asm_str << "\n\t.rva " << "__seh2$$begin_except__" << handler.line; /* Called on except */
188         else
189             asm_str << "\n\t.long 0"; /* No unwind handler */
190     }
191     asm_str << "\n\t.seh_code\n";
192 
193     strncpy(const_cast<char*>(TREE_STRING_POINTER(seh_fun->asm_header_text)),
194             asm_str.str().c_str(),
195             TREE_STRING_LENGTH(seh_fun->asm_header_text));
196 
197     trace(stderr, "ASM: %s\n", asm_str.str().c_str());
198 
199     delete seh_fun;
200 }
201 
202 static
203 void
register_seh_pragmas(void * UNUSED event_data,void * UNUSED user_data)204 register_seh_pragmas(void* UNUSED event_data, void* UNUSED user_data)
205 {
206     c_register_pragma("REACTOS", "seh", handle_seh_pragma);
207 }
208 
209 /* Return 0 on success or error code on failure */
210 extern "C"
211 VISIBLE
plugin_init(struct plugin_name_args * info,struct plugin_gcc_version * version)212 int plugin_init(struct plugin_name_args   *info,  /* Argument infor */
213                 struct plugin_gcc_version *version)   /* Version of GCC */
214 {
215     if (!plugin_default_version_check (version, &gcc_version))
216     {
217         std::cerr << "This GCC plugin is for version " << GCCPLUGIN_VERSION_MAJOR << "." << GCCPLUGIN_VERSION_MINOR << "\n";
218         return 1;
219     }
220 
221     register_callback(info->base_name, PLUGIN_PRAGMAS, register_seh_pragmas, NULL);
222     register_callback(info->base_name, PLUGIN_FINISH_PARSE_FUNCTION, finish_seh_function, NULL);
223 
224     return 0;
225 }
226