131315593STrond Norbye/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
231315593STrond Norbye/*
331315593STrond Norbye *     Copyright 2018 Couchbase, Inc.
431315593STrond Norbye *
531315593STrond Norbye *   Licensed under the Apache License, Version 2.0 (the "License");
631315593STrond Norbye *   you may not use this file except in compliance with the License.
731315593STrond Norbye *   You may obtain a copy of the License at
831315593STrond Norbye *
931315593STrond Norbye *       http://www.apache.org/licenses/LICENSE-2.0
1031315593STrond Norbye *
1131315593STrond Norbye *   Unless required by applicable law or agreed to in writing, software
1231315593STrond Norbye *   distributed under the License is distributed on an "AS IS" BASIS,
1331315593STrond Norbye *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1431315593STrond Norbye *   See the License for the specific language governing permissions and
1531315593STrond Norbye *   limitations under the License.
1631315593STrond Norbye */
1731315593STrond Norbye
18cc769d48STrond Norbye#include "config.h"
1931315593STrond Norbye#include "breakpad.h"
20cc769d48STrond Norbye#include "terminate_handler.h"
2131315593STrond Norbye
22cc769d48STrond Norbye#include <logger/logger.h>
2331315593STrond Norbye
2431315593STrond Norbye#include <platform/backtrace.h>
25fd6bd4c1STrond Norbye#include <platform/dirutils.h>
26cc769d48STrond Norbye#include <platform/platform.h>
2731315593STrond Norbye
2831315593STrond Norbyeusing namespace google_breakpad;
2931315593STrond Norbye
3031315593STrond Norbye// Unique_ptr which holds the pointer to the installed
3131315593STrond Norbye// breakpad handler
3231315593STrond Norbyestatic std::unique_ptr<ExceptionHandler> handler;
3331315593STrond Norbye
34bed27248STrond Norbye#if (defined(WIN32) || defined(linux)) && defined(HAVE_BREAKPAD)
3531315593STrond Norbye// These methods is called from breakpad when creating
3631315593STrond Norbye// the dump. They're inside the #ifdef block to avoid
3731315593STrond Norbye// compilers to complain about static functions never
3831315593STrond Norbye// being used.
3931315593STrond Norbye
4031315593STrond Norbyestatic void write_to_logger(void* ctx, const char* frame) {
414dd9b0f4STrond Norbye    LOG_CRITICAL("    {}", frame);
4231315593STrond Norbye}
4331315593STrond Norbye
4431315593STrond Norbyestatic void dump_stack() {
454dd9b0f4STrond Norbye    LOG_CRITICAL("Stack backtrace of crashed thread:");
4631315593STrond Norbye    print_backtrace(write_to_logger, NULL);
474d6fdda3STrond Norbye    cb::logger::flush();
4831315593STrond Norbye}
4931315593STrond Norbye#endif
5031315593STrond Norbye
5131315593STrond Norbye// Unfortunately Breakpad use a different API on each platform,
5231315593STrond Norbye// so we need a bit of #ifdef's..
5331315593STrond Norbye
54bed27248STrond Norbye#if defined(WIN32) && defined(HAVE_BREAKPAD)
5531315593STrond Norbyestatic bool dumpCallback(const wchar_t* dump_path,
5631315593STrond Norbye                         const wchar_t* minidump_id,
5731315593STrond Norbye                         void* context,
5831315593STrond Norbye                         EXCEPTION_POINTERS* exinfo,
5931315593STrond Norbye                         MDRawAssertionInfo* assertion,
6031315593STrond Norbye                         bool succeeded) {
6131315593STrond Norbye    // Unfortnately the filenames is in wchar's and I got compiler errors
6231315593STrond Norbye    // from fmt when trying to print them by using {}. Let's just format
6331315593STrond Norbye    // it into a string first.
6431315593STrond Norbye    char file[512];
6531315593STrond Norbye    sprintf(file, "%S\\%S.dmp", dump_path, minidump_id);
6631315593STrond Norbye
674dd9b0f4STrond Norbye    LOG_CRITICAL(
68fd6bd4c1STrond Norbye            "Breakpad caught a crash (Couchbase version {}). Writing crash "
69fd6bd4c1STrond Norbye            "dump to {} before terminating.",
70cc769d48STrond Norbye            PRODUCT_VERSION,
7131315593STrond Norbye            file);
7231315593STrond Norbye    dump_stack();
7331315593STrond Norbye    return succeeded;
7431315593STrond Norbye}
75bed27248STrond Norbye#elif defined(linux) && defined(HAVE_BREAKPAD)
7631315593STrond Norbyestatic bool dumpCallback(const MinidumpDescriptor& descriptor,
7731315593STrond Norbye                         void* context,
7831315593STrond Norbye                         bool succeeded) {
794dd9b0f4STrond Norbye    LOG_CRITICAL(
80fd6bd4c1STrond Norbye            "Breakpad caught a crash (Couchbase version {}). Writing crash "
81fd6bd4c1STrond Norbye            "dump to {} before terminating.",
82cc769d48STrond Norbye            PRODUCT_VERSION,
8331315593STrond Norbye            descriptor.path());
8431315593STrond Norbye
8531315593STrond Norbye    dump_stack();
8631315593STrond Norbye    return succeeded;
8731315593STrond Norbye}
8831315593STrond Norbye#endif
8931315593STrond Norbye
9031315593STrond Norbyevoid create_handler(const std::string& minidump_dir) {
91bed27248STrond Norbye#if defined(WIN32) && defined(HAVE_BREAKPAD)
9231315593STrond Norbye    // Takes a wchar_t* on Windows. Isn't the Breakpad API nice and
9331315593STrond Norbye    // consistent? ;)
9431315593STrond Norbye    size_t len = minidump_dir.length() + 1;
9531315593STrond Norbye    std::wstring wc_minidump_dir(len, '\0');
9631315593STrond Norbye    size_t wlen = 0;
9731315593STrond Norbye    mbstowcs_s(
9831315593STrond Norbye            &wlen, &wc_minidump_dir[0], len, minidump_dir.c_str(), _TRUNCATE);
9931315593STrond Norbye
10031315593STrond Norbye    handler.reset(new ExceptionHandler(&wc_minidump_dir[0],
10131315593STrond Norbye                                       /*filter*/ NULL,
10231315593STrond Norbye                                       dumpCallback,
10331315593STrond Norbye                                       /*callback-context*/ NULL,
10431315593STrond Norbye                                       ExceptionHandler::HANDLER_ALL,
10531315593STrond Norbye                                       MiniDumpNormal,
10631315593STrond Norbye                                       /*pipe*/ (wchar_t*)NULL,
10731315593STrond Norbye                                       /*custom_info*/ NULL));
108bed27248STrond Norbye#elif defined(linux) && defined(HAVE_BREAKPAD)
10931315593STrond Norbye    MinidumpDescriptor descriptor(minidump_dir.c_str());
11031315593STrond Norbye    handler.reset(new ExceptionHandler(descriptor,
11131315593STrond Norbye                                       /*filter*/ NULL,
11231315593STrond Norbye                                       dumpCallback,
11331315593STrond Norbye                                       /*callback-context*/ NULL,
11431315593STrond Norbye                                       /*install_handler*/ true,
11531315593STrond Norbye                                       /*server_fd*/ -1));
11631315593STrond Norbye#else
11731315593STrond Norbye// Not supported on this plaform
11831315593STrond Norbye#endif
11931315593STrond Norbye}
12031315593STrond Norbye
12131315593STrond Norbyevoid cb::breakpad::initialize(const cb::breakpad::Settings& settings) {
12231315593STrond Norbye    // We cannot actually change any of breakpad's settings once created, only
12331315593STrond Norbye    // remove it and re-create with new settings.
12431315593STrond Norbye    destroy();
12531315593STrond Norbye
12631315593STrond Norbye    if (settings.enabled) {
12731315593STrond Norbye        create_handler(settings.minidump_dir);
12831315593STrond Norbye    }
12931315593STrond Norbye
13031315593STrond Norbye    if (handler) {
13131315593STrond Norbye        // Turn off the terminate handler's backtrace - otherwise we
13231315593STrond Norbye        // just print it twice.
13331315593STrond Norbye        set_terminate_handler_print_backtrace(false);
13431315593STrond Norbye
1354dd9b0f4STrond Norbye        LOG_INFO("Breakpad enabled. Minidumps will be written to '{}'",
1364dd9b0f4STrond Norbye                 settings.minidump_dir);
13731315593STrond Norbye    } else {
13831315593STrond Norbye        // If breakpad is off, then at least print the backtrace via
13931315593STrond Norbye        // terminate_handler.
14031315593STrond Norbye        set_terminate_handler_print_backtrace(true);
1414dd9b0f4STrond Norbye        LOG_INFO("Breakpad disabled");
14231315593STrond Norbye    }
14331315593STrond Norbye}
14431315593STrond Norbye
145fd6bd4c1STrond Norbyevoid cb::breakpad::initialize(const std::string& directory) {
146fd6bd4c1STrond Norbye    // We cannot actually change any of breakpad's settings once created, only
147fd6bd4c1STrond Norbye    // remove it and re-create with new settings.
148fd6bd4c1STrond Norbye    destroy();
149fd6bd4c1STrond Norbye
150fd6bd4c1STrond Norbye    if (directory.empty()) {
151fd6bd4c1STrond Norbye        // No directory provided
152fd6bd4c1STrond Norbye        return;
153fd6bd4c1STrond Norbye    }
154fd6bd4c1STrond Norbye
155fd6bd4c1STrond Norbye    create_handler(directory);
156fd6bd4c1STrond Norbye
157fd6bd4c1STrond Norbye    if (handler) {
158fd6bd4c1STrond Norbye        // Turn off the terminate handler's backtrace - otherwise we
159fd6bd4c1STrond Norbye        // just print it twice.
160fd6bd4c1STrond Norbye        set_terminate_handler_print_backtrace(false);
161fd6bd4c1STrond Norbye    } else {
162fd6bd4c1STrond Norbye        // do we want to notify the user that we don't have access?
163fd6bd4c1STrond Norbye    }
164fd6bd4c1STrond Norbye}
165fd6bd4c1STrond Norbye
16631315593STrond Norbyevoid cb::breakpad::destroy() {
16731315593STrond Norbye    if (handler) {
1684dd9b0f4STrond Norbye        LOG_INFO("Disabling Breakpad");
16931315593STrond Norbye        set_terminate_handler_print_backtrace(true);
17031315593STrond Norbye    }
17131315593STrond Norbye    handler.reset();
17231315593STrond Norbye}
173