1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2018 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include "config.h"
19 #include "breakpad.h"
20 #include "terminate_handler.h"
21 
22 #include <logger/logger.h>
23 
24 #include <platform/backtrace.h>
25 #include <platform/dirutils.h>
26 #include <platform/platform.h>
27 
28 using namespace google_breakpad;
29 
30 // Unique_ptr which holds the pointer to the installed
31 // breakpad handler
32 static std::unique_ptr<ExceptionHandler> handler;
33 
34 #if (defined(WIN32) || defined(linux)) && defined(HAVE_BREAKPAD)
35 // These methods is called from breakpad when creating
36 // the dump. They're inside the #ifdef block to avoid
37 // compilers to complain about static functions never
38 // being used.
39 
write_to_logger(void* ctx, const char* frame)40 static void write_to_logger(void* ctx, const char* frame) {
41     LOG_CRITICAL("    {}", frame);
42 }
43 
dump_stack()44 static void dump_stack() {
45     LOG_CRITICAL("Stack backtrace of crashed thread:");
46     print_backtrace(write_to_logger, NULL);
47     cb::logger::flush();
48 }
49 #endif
50 
51 // Unfortunately Breakpad use a different API on each platform,
52 // so we need a bit of #ifdef's..
53 
54 #if defined(WIN32) && defined(HAVE_BREAKPAD)
dumpCallback(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded)55 static bool dumpCallback(const wchar_t* dump_path,
56                          const wchar_t* minidump_id,
57                          void* context,
58                          EXCEPTION_POINTERS* exinfo,
59                          MDRawAssertionInfo* assertion,
60                          bool succeeded) {
61     // Unfortnately the filenames is in wchar's and I got compiler errors
62     // from fmt when trying to print them by using {}. Let's just format
63     // it into a string first.
64     char file[512];
65     sprintf(file, "%S\\%S.dmp", dump_path, minidump_id);
66 
67     LOG_CRITICAL(
68             "Breakpad caught a crash (Couchbase version {}). Writing crash "
69             "dump to {} before terminating.",
70             PRODUCT_VERSION,
71             file);
72     dump_stack();
73     return succeeded;
74 }
75 #elif defined(linux) && defined(HAVE_BREAKPAD)
dumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded)76 static bool dumpCallback(const MinidumpDescriptor& descriptor,
77                          void* context,
78                          bool succeeded) {
79     LOG_CRITICAL(
80             "Breakpad caught a crash (Couchbase version {}). Writing crash "
81             "dump to {} before terminating.",
82             PRODUCT_VERSION,
83             descriptor.path());
84 
85     dump_stack();
86     return succeeded;
87 }
88 #endif
89 
create_handler(const std::string& minidump_dir)90 void create_handler(const std::string& minidump_dir) {
91 #if defined(WIN32) && defined(HAVE_BREAKPAD)
92     // Takes a wchar_t* on Windows. Isn't the Breakpad API nice and
93     // consistent? ;)
94     size_t len = minidump_dir.length() + 1;
95     std::wstring wc_minidump_dir(len, '\0');
96     size_t wlen = 0;
97     mbstowcs_s(
98             &wlen, &wc_minidump_dir[0], len, minidump_dir.c_str(), _TRUNCATE);
99 
100     handler.reset(new ExceptionHandler(&wc_minidump_dir[0],
101                                        /*filter*/ NULL,
102                                        dumpCallback,
103                                        /*callback-context*/ NULL,
104                                        ExceptionHandler::HANDLER_ALL,
105                                        MiniDumpNormal,
106                                        /*pipe*/ (wchar_t*)NULL,
107                                        /*custom_info*/ NULL));
108 #elif defined(linux) && defined(HAVE_BREAKPAD)
109     MinidumpDescriptor descriptor(minidump_dir.c_str());
110     handler.reset(new ExceptionHandler(descriptor,
111                                        /*filter*/ NULL,
112                                        dumpCallback,
113                                        /*callback-context*/ NULL,
114                                        /*install_handler*/ true,
115                                        /*server_fd*/ -1));
116 #else
117 // Not supported on this plaform
118 #endif
119 }
120 
initialize(const cb::breakpad::Settings& settings)121 void cb::breakpad::initialize(const cb::breakpad::Settings& settings) {
122     // We cannot actually change any of breakpad's settings once created, only
123     // remove it and re-create with new settings.
124     destroy();
125 
126     if (settings.enabled) {
127         create_handler(settings.minidump_dir);
128     }
129 
130     if (handler) {
131         // Turn off the terminate handler's backtrace - otherwise we
132         // just print it twice.
133         set_terminate_handler_print_backtrace(false);
134 
135         LOG_INFO("Breakpad enabled. Minidumps will be written to '{}'",
136                  settings.minidump_dir);
137     } else {
138         // If breakpad is off, then at least print the backtrace via
139         // terminate_handler.
140         set_terminate_handler_print_backtrace(true);
141         LOG_INFO("Breakpad disabled");
142     }
143 }
144 
initialize(const std::string& directory)145 void cb::breakpad::initialize(const std::string& directory) {
146     // We cannot actually change any of breakpad's settings once created, only
147     // remove it and re-create with new settings.
148     destroy();
149 
150     if (directory.empty()) {
151         // No directory provided
152         return;
153     }
154 
155     create_handler(directory);
156 
157     if (handler) {
158         // Turn off the terminate handler's backtrace - otherwise we
159         // just print it twice.
160         set_terminate_handler_print_backtrace(false);
161     } else {
162         // do we want to notify the user that we don't have access?
163     }
164 }
165 
destroy()166 void cb::breakpad::destroy() {
167     if (handler) {
168         LOG_INFO("Disabling Breakpad");
169         set_terminate_handler_print_backtrace(true);
170     }
171     handler.reset();
172 }
173