blob: f09439f70ca067c924c166c6e6b773c4a08f9287 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "util/minidump.h"
#include <assert.h>
#include <boost/filesystem.hpp>
#include <client/linux/handler/exception_handler.h>
#include <common/linux/linux_libc_support.h>
#include <google_breakpad/common/minidump_format.h>
#include <third_party/lss/linux_syscall_support.h>
#include <ctime>
#include <fstream>
#include <glob.h>
#include <iomanip>
#include <map>
#include <signal.h>
#include <sstream>
#include "common/logging.h"
#include "common/version.h"
#include "util/filesystem-util.h"
#include "util/time.h"
using namespace std;
using boost::filesystem::create_directories;
using boost::filesystem::is_regular_file;
using boost::filesystem::path;
using boost::filesystem::remove;
DECLARE_string(log_dir);
DECLARE_bool(enable_minidumps);
DECLARE_string(minidump_path);
DECLARE_int32(max_minidumps);
DECLARE_int32(minidump_size_limit_hint_kb);
namespace impala {
/// Breakpad ExceptionHandler. It registers its own signal handlers to write minidump
/// files during process crashes, but also can be used to write minidumps directly.
static google_breakpad::ExceptionHandler* minidump_exception_handler = NULL;
/// Test helper. True if minidumps should be enabled.
static bool minidumps_enabled = true;
/// Called by the exception handler before minidump is produced. Minidump is only written
/// if this returns true.
static bool FilterCallback(void* context) {
return minidumps_enabled;
}
/// Callback for breakpad. It is called by breakpad whenever a minidump file has been
/// written and should not be called directly. It logs the event before breakpad crashes
/// the process. Due to the process being in a failed state we write to stdout/stderr and
/// let the surrounding redirection make sure the output gets logged. The calls might
/// still fail in unknown scenarios as the process is in a broken state. However we don't
/// rely on them as the minidump file has been written already.
static bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
// See if a file was written successfully.
if (succeeded) {
// Write message to stdout/stderr, which will usually be captured in the INFO/ERROR
// log.
const char msg[] = "Wrote minidump to ";
const int msg_len = sizeof(msg) / sizeof(msg[0]) - 1;
const char* path = descriptor.path();
// We use breakpad's reimplementation of strlen to avoid calling into libc.
const int path_len = my_strlen(path);
// We use the linux syscall support methods from chromium here as per the
// recommendation of the breakpad docs to avoid calling into other shared libraries.
sys_write(STDOUT_FILENO, msg, msg_len);
sys_write(STDOUT_FILENO, path, path_len);
sys_write(STDOUT_FILENO, "\n", 1);
sys_write(STDERR_FILENO, msg, msg_len);
sys_write(STDERR_FILENO, path, path_len);
sys_write(STDERR_FILENO, "\n", 1);
}
// Return the value received in the call as described in the minidump documentation. If
// this values is true, then no other handlers will be called. Breakpad will still crash
// the process.
return succeeded;
}
/// Signal handler to write a minidump file outside of crashes.
static void HandleSignal(int signum, siginfo_t* info, void* context) {
const char* msg = "Caught signal: SIGUSR1\n";
sys_write(STDOUT_FILENO, msg, strlen(msg));
minidump_exception_handler->WriteMinidump(FLAGS_minidump_path, DumpCallback, NULL);
}
/// Register our signal handler to write minidumps on SIGUSR1. Will make us ignore the
/// signal if 'minidumps_enabled' is false.
static void SetupSigUSR1Handler(bool minidumps_enabled) {
struct sigaction sig_action;
memset(&sig_action, 0, sizeof(sig_action));
sigemptyset(&sig_action.sa_mask);
if (minidumps_enabled) {
DCHECK(minidump_exception_handler != NULL);
sig_action.sa_sigaction = &HandleSignal;
sig_action.sa_flags = SA_SIGINFO;
} else {
sig_action.sa_handler = SIG_IGN;
}
if (sigaction(SIGUSR1, &sig_action, nullptr) == -1) {
stringstream error_msg;
error_msg << "Failed to register action for SIGUSR1: " << GetStrErrMsg();
CLEAN_EXIT_WITH_ERROR(error_msg.str());
}
}
void CheckAndRotateMinidumps(int max_minidumps) {
// Disable rotation if 0 or wrong input
if (max_minidumps <= 0) return;
// Search for minidumps. There could be multiple minidumps for a single second.
multimap<int, path> timestamp_to_path;
// Minidump filenames are created by breakpad in the following format, for example:
// 7b57915b-ee6a-dbc5-21e59491-5c60a2cf.dmp.
string pattern = FLAGS_minidump_path + "/*.dmp";
glob_t result;
glob(pattern.c_str(), GLOB_TILDE, NULL, &result);
for (size_t i = 0; i < result.gl_pathc; ++i) {
const path minidump_path(result.gl_pathv[i]);
boost::system::error_code err;
bool is_file = is_regular_file(minidump_path, err);
// is_regular_file() calls stat() eventually, which can return errors, e.g. if the
// file permissions prevented access or the path was wrong (see 'man 2 stat' for
// details). In these cases we assume that the issue is out of our control and err on
// the safe side by keeping the minidump around, hoping it will aid in debugging the
// issue. The alternative, removing a ~2MB file, will probably not help much anyways.
if (err) {
LOG(WARNING) << "Failed to stat() file " << minidump_path << ": " << err;
continue;
}
if (is_file) {
ifstream stream(minidump_path.c_str(), std::ios::in | std::ios::binary);
if (!stream.good()) {
// Error opening file, probably broken, remove it.
LOG(WARNING) << "Failed to open file " << minidump_path << ". Removing it.";
stream.close();
// Best effort, ignore error.
remove(minidump_path.c_str(), err);
continue;
}
// Read minidump header from file.
MDRawHeader header;
constexpr int header_size = sizeof(header);
stream.read((char *)(&header), header_size);
// Check for minidump header signature and version. We don't need to check for
// endianness issues here since the file was written on the same machine. Ignore the
// higher 16 bit of the version as per a comment in the breakpad sources.
if (stream.gcount() != header_size || header.signature != MD_HEADER_SIGNATURE ||
(header.version & 0x0000ffff) != MD_HEADER_VERSION) {
LOG(WARNING) << "Found file in minidump folder, but it does not look like a "
<< "minidump file: " << minidump_path.string() << ". Removing it.";
remove(minidump_path, err);
if (err) {
LOG(ERROR) << "Failed to delete file: " << minidump_path << "(error was: "
<< err << ")";
}
continue;
}
int timestamp = header.time_date_stamp;
timestamp_to_path.emplace(timestamp, minidump_path);
}
}
globfree(&result);
// Remove oldest entries until max_minidumps are left.
if (timestamp_to_path.size() <= max_minidumps) return;
int files_to_delete = timestamp_to_path.size() - max_minidumps;
DCHECK_GT(files_to_delete, 0);
auto to_delete = timestamp_to_path.begin();
for (int i = 0; i < files_to_delete; ++i, ++to_delete) {
boost::system::error_code err;
remove(to_delete->second, err);
if (!err) {
LOG(INFO) << "Removed old minidump file : " << to_delete->second;
} else {
LOG(ERROR) << "Failed to delete old minidump file: " << to_delete->second <<
"(error was: " << err << ")";
}
}
}
Status RegisterMinidump(const char* cmd_line_path) {
// Registration must only be called once.
static bool registered = false;
DCHECK(!registered);
registered = true;
if (!FLAGS_enable_minidumps || FLAGS_minidump_path.empty()) {
SetupSigUSR1Handler(false);
return Status::OK();
}
if (path(FLAGS_minidump_path).is_relative()) {
path log_dir(FLAGS_log_dir);
FLAGS_minidump_path = (log_dir / FLAGS_minidump_path).string();
}
// Add the daemon name to the path where minidumps will be written. This makes
// identification easier and prevents name collisions between the files.
path daemon = path(cmd_line_path).filename();
FLAGS_minidump_path = (FLAGS_minidump_path / daemon).string();
// Create the directory if it is not there. The minidump doesn't get written if there is
// no directory.
boost::system::error_code err;
create_directories(FLAGS_minidump_path, err);
if (err) {
stringstream ss;
ss << "Could not create minidump folder " << FLAGS_minidump_path << ". Error "
<< "was: " << err;
return Status(ss.str());
}
google_breakpad::MinidumpDescriptor desc(FLAGS_minidump_path.c_str());
// Limit filesize if configured.
if (FLAGS_minidump_size_limit_hint_kb > 0) {
size_t size_limit = 1024 * static_cast<int64_t>(FLAGS_minidump_size_limit_hint_kb);
LOG(INFO) << "Setting minidump size limit to " << size_limit << ".";
desc.set_size_limit(size_limit);
}
// Intentionally leaked. We want this to have the lifetime of the process.
DCHECK(minidump_exception_handler == NULL);
minidump_exception_handler = new google_breakpad::ExceptionHandler(
desc, FilterCallback, DumpCallback, NULL, true, -1);
// Setup signal handler for SIGUSR1.
SetupSigUSR1Handler(true);
return Status::OK();
}
bool EnableMinidumpsForTest(bool enabled) {
bool old_value = minidumps_enabled;
minidumps_enabled = enabled;
return old_value;
}
} // end ns impala