1 // Copyright Antony Polukhin, 2016-2020.
2 //
3 // Distributed under the Boost Software License, Version 1.0. (See
4 // accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6
7 #include <boost/array.hpp>
8 BOOST_NOINLINE void foo(int i);
9 BOOST_NOINLINE void bar(int i);
10
bar(int i)11 BOOST_NOINLINE void bar(int i) {
12 boost::array<int, 5> a = {{-1, -231, -123, -23, -32}};
13 if (i >= 0) {
14 foo(a[i]);
15 } else {
16 std::terminate();
17 }
18 }
19
foo(int i)20 BOOST_NOINLINE void foo(int i) {
21 bar(--i);
22 }
23
24 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
25
26 //[getting_started_terminate_handlers
27
28 #include <signal.h> // ::signal, ::raise
29 #include <boost/stacktrace.hpp>
30
my_signal_handler(int signum)31 void my_signal_handler(int signum) {
32 ::signal(signum, SIG_DFL);
33 boost::stacktrace::safe_dump_to("./backtrace.dump");
34 ::raise(SIGABRT);
35 }
36 //]
37
setup_handlers()38 void setup_handlers() {
39 //[getting_started_setup_handlers
40 ::signal(SIGSEGV, &my_signal_handler);
41 ::signal(SIGABRT, &my_signal_handler);
42 //]
43 }
44
45 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
46
47 BOOST_CONSTEXPR_OR_CONST std::size_t shared_memory_size = 4096 * 8;
48
49 //[getting_started_terminate_handlers_shmem
50 #include <boost/stacktrace.hpp>
51 #include <boost/interprocess/shared_memory_object.hpp>
52 #include <boost/interprocess/mapped_region.hpp>
53
54 boost::interprocess::shared_memory_object g_shm; // inited at program start
55 boost::interprocess::mapped_region g_region; // inited at program start
56
57
my_signal_handler2(int signum)58 void my_signal_handler2(int signum) {
59 ::signal(signum, SIG_DFL);
60 void** f = static_cast<void**>(g_region.get_address());
61 *f = reinterpret_cast<void*>(1); // Setting flag that shared memory now constains stacktrace.
62 boost::stacktrace::safe_dump_to(f + 1, g_region.get_size() - sizeof(void*));
63
64 ::raise(SIGABRT);
65 }
66 //]
67
68 #include <iostream> // std::cerr
69 #include <fstream> // std::ifstream
70 #include <boost/filesystem/path.hpp>
71 #include <boost/filesystem/operations.hpp>
72
73
copy_and_run(const char * exec_name,char param,bool not_null)74 inline void copy_and_run(const char* exec_name, char param, bool not_null) {
75 std::cout << "Running with param " << param << std::endl;
76 boost::filesystem::path command = exec_name;
77 command = command.parent_path() / (command.stem().string() + param + command.extension().string());
78 boost::filesystem::copy_file(exec_name, command, boost::filesystem::copy_option::overwrite_if_exists);
79
80 boost::filesystem::path command_args = command;
81 command_args += ' ';
82 command_args += param;
83 const int ret = std::system(command_args.string().c_str());
84
85 std::cout << "End Running with param " << param << "; ret code is " << ret << std::endl;
86 boost::system::error_code ignore;
87 boost::filesystem::remove(command, ignore);
88 if (not_null && !ret) {
89 std::exit(97);
90 } else if (!not_null && ret) {
91 std::exit(ret);
92 }
93 }
94
run_1(const char * [])95 int run_1(const char* /*argv*/[]) {
96 setup_handlers();
97 foo(5);
98 return 11;
99 }
100
run_2(const char * argv[])101 int run_2(const char* argv[]) {
102 if (!boost::filesystem::exists("./backtrace.dump")) {
103 if (std::string(argv[0]).find("noop") == std::string::npos) {
104 return 21;
105 }
106
107 boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(std::cin);
108 if (st) {
109 return 22;
110 }
111 return 0;
112 }
113
114 //[getting_started_on_program_restart
115 if (boost::filesystem::exists("./backtrace.dump")) {
116 // there is a backtrace
117 std::ifstream ifs("./backtrace.dump");
118
119 boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(ifs);
120 std::cout << "Previous run crashed:\n" << st << std::endl; /*<-*/
121
122 if (!st) {
123 return 23;
124 } /*->*/
125
126 // cleaning up
127 ifs.close();
128 boost::filesystem::remove("./backtrace.dump");
129 }
130 //]
131
132 return 0;
133 }
134
135
run_3(const char * [])136 int run_3(const char* /*argv*/[]) {
137 using namespace boost::interprocess;
138 {
139 shared_memory_object shm_obj(open_or_create, "shared_memory", read_write);
140 shm_obj.swap(g_shm);
141 }
142 g_shm.truncate(shared_memory_size);
143
144 {
145 mapped_region m(g_shm, read_write, 0, shared_memory_size);
146 m.swap(g_region);
147 }
148 void** f = static_cast<void**>(g_region.get_address());
149 *f = 0;
150
151 ::signal(SIGSEGV, &my_signal_handler2);
152 ::signal(SIGABRT, &my_signal_handler2);
153 foo(5);
154 return 31;
155 }
156
run_4(const char * argv[])157 int run_4(const char* argv[]) {
158 using namespace boost::interprocess;
159 {
160 shared_memory_object shm_obj(open_only, "shared_memory", read_write);
161 shm_obj.swap(g_shm);
162 }
163
164 {
165 mapped_region m(g_shm, read_write, 0, shared_memory_size);
166 m.swap(g_region);
167 }
168
169 //[getting_started_on_program_restart_shmem
170 void** f = static_cast<void**>(g_region.get_address());
171 if (*f) { // Checking if memory constains stacktrace.
172 boost::stacktrace::stacktrace st
173 = boost::stacktrace::stacktrace::from_dump(f + 1, g_region.get_size() - sizeof(bool));
174
175 std::cout << "Previous run crashed and left trace in shared memory:\n" << st << std::endl;
176 *f = 0; /*<-*/
177 shared_memory_object::remove("shared_memory");
178 if (std::string(argv[0]).find("noop") == std::string::npos) {
179 if (!st) {
180 return 43;
181 }
182 } else {
183 if (st) {
184 return 44;
185 }
186 }
187 } else {
188 return 42; /*->*/
189 }
190 //]
191
192
193 return 0;
194 }
195
196 #include <sstream>
197
test_inplace()198 int test_inplace() {
199 const bool is_noop = !boost::stacktrace::stacktrace();
200
201 {
202 // This is very dependent on compiler and link flags. No sane way to make it work, because:
203 // * BOOST_NOINLINE could be ignored by MSVC compiler if link-time optimization is enabled.
204 // * BOOST_FORCEINLINE could be ignored by GCC depending on the std::vector default constructor length.
205 const std::size_t frames_ss1 = boost::stacktrace::safe_dump_to("./backtrace2.dump");
206 boost::stacktrace::stacktrace ss2;
207 std::ifstream ifs("./backtrace2.dump");
208 boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(ifs);
209 ifs.close();
210 boost::filesystem::remove("./backtrace2.dump");
211
212 if (ss1.size() + 1 != frames_ss1 || ss2.size() != ss1.size()) {
213 std::cerr << "51: Stacktraces differ. Dumped size == " << frames_ss1 << ".\n" << ss1 << "\n vs \n" << ss2 << '\n';
214 } else if (ss1.size() > 1 && ss1[1].name() != ss2[1].name()) {
215 std::cerr << "52: Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n';
216 }
217 }
218
219 {
220 // This is very dependent on compiler and link flags. No sane way to make it work, because:
221 // * BOOST_NOINLINE could be ignored by MSVC compiler if link-time optimization is enabled.
222 // * BOOST_FORCEINLINE could be ignored by GCC depending on the std::vector default constructor length.
223 void* data[1024];
224 const std::size_t frames_ss1 = boost::stacktrace::safe_dump_to(data, sizeof(data));
225 boost::stacktrace::stacktrace ss2;
226 boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(data, sizeof(data));
227
228 if (ss1.size() + 1 != frames_ss1 || ss1.size() != ss2.size()) {
229 std::cerr << "53: Stacktraces differ. Dumped size == " << frames_ss1 << ".\n" << ss1 << "\n vs \n" << ss2 << '\n';
230 } else if (ss1.size() > 1 && ss1[1].name() != ss2[1].name()) {
231 std::cerr << "54: Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n';
232 }
233 }
234
235 {
236 void* data[1024];
237 boost::stacktrace::safe_dump_to(1024, data, sizeof(data));
238 if (boost::stacktrace::stacktrace::from_dump(data, sizeof(data))) {
239 std::cerr << "Stacktrace not empty!\n";
240 return 55;
241 }
242 }
243
244 {
245 void* data[1024];
246 boost::stacktrace::safe_dump_to(1, data, sizeof(data));
247 if (!is_noop && !boost::stacktrace::stacktrace::from_dump(data, sizeof(data))) {
248 std::cerr << "Stacktrace empty!\n";
249 return 56;
250 }
251 const std::size_t size_1_skipped = boost::stacktrace::stacktrace::from_dump(data, sizeof(data)).size();
252 boost::stacktrace::safe_dump_to(0, data, sizeof(data));
253 const std::size_t size_0_skipped = boost::stacktrace::stacktrace::from_dump(data, sizeof(data)).size();
254
255 if (!is_noop && (size_1_skipped + 1 != size_0_skipped)) {
256 std::cerr << "failed to skip 1 frame!\n";
257 return 57;
258 }
259 }
260
261 {
262 boost::stacktrace::safe_dump_to(0, 1, "./backtrace3.dump");
263 std::ifstream ifs("./backtrace3.dump");
264 boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(ifs);
265 ifs.close();
266
267 boost::stacktrace::safe_dump_to(1, 1, "./backtrace3.dump");
268 ifs.open("./backtrace3.dump");
269 boost::stacktrace::stacktrace ss2 = boost::stacktrace::stacktrace::from_dump(ifs);
270 ifs.close();
271
272 boost::filesystem::remove("./backtrace3.dump");
273
274 #ifdef BOOST_WINDOWS
275 // `ss2` could be empty on some combinations of Windows+MSVC.
276 if (!ss2) {
277 return 0;
278 }
279 #endif
280
281 if (ss1.size() != ss2.size()) {
282 std::cerr << "Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n';
283 return 58;
284 }
285
286 if (!is_noop && ss1.size() != 1) {
287 std::cerr << "Stacktraces does not have size 1:\n" << ss1 << '\n';
288 return 59;
289 }
290
291 if (ss1 && ss1[0].address() == ss2[0].address()) {
292 std::cerr << "Stacktraces must differ:\n" << ss1 << "\n vs \n" << ss2 << '\n';
293 return 60;
294 }
295 }
296
297 return 0;
298 }
299
300
main(int argc,const char * argv[])301 int main(int argc, const char* argv[]) {
302 if (argc < 2) {
303 #ifndef BOOST_WINDOWS
304 // We are copying files to make sure that stacktrace printing works independently from executable name
305 copy_and_run(argv[0], '1', true);
306 copy_and_run(argv[0], '2', false);
307
308 // There are some issues with async-safety of shared memory writes on Windows.
309 copy_and_run(argv[0], '3', true);
310 copy_and_run(argv[0], '4', false);
311 #endif
312
313 return test_inplace();
314 }
315
316 switch (argv[1][0]) {
317 case '1': return run_1(argv);
318 case '2': return run_2(argv);
319 case '3': return run_3(argv);
320 case '4': return run_4(argv);
321 }
322
323 return 404;
324 }
325
326
327