blob: b5720975f1ad70713a27746cfb36382e77fe040a [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crash-reporter/chrome_collector.h"
#include <fstream>
#include <glib.h>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <pcrecpp.h>
#include <string>
#include <vector>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "chromeos/process.h"
#include "chromeos/syslog_logging.h"
#include "chromeos/dbus/dbus.h"
#include "chromeos/dbus/service_constants.h"
const char kDefaultMinidumpName[] = "upload_file_minidump";
const char kTarPath[] = "/bin/tar";
// From //net/crash/collector/collector.h
const int kDefaultMaxUploadBytes = 1024 * 1024;
const char kHeader[] = "kernel_task_states";
const char kEnter[] = "enter";
const char kLeave[] = "leave";
const char kSyslogPath[] = "/var/log/messages";
const char kFirstTwoColumnsRE[] = "\\S+\\s+\\S+\\s+";
const char kIdent[] = "crash_reporter";
const char kSpacesRE[] = "\\s+";
const char kKernelIdentRE[] = "kernel:";
const char kKernelTimestampRE[] = "\\[\\s*\\d+\\.\\d+\\]";
const char kTaskNameRE[] = "(.{15})";
// kernel's TASK_STATE_TO_CHAR_STR + '?'
const char kTaskStateRE[] = "([RSDTtZXxKW?])";
const char kHexNumRE[] = "([[:xdigit:]]+)";
const char kNumRE[] = "([[:digit:]]+)";
const char kRunning32[] = "running ";
const char kRunning64[] = " running task ";
// This regex matches symbol+offset as printed by printk(%pB) or (%pS).
// See https://www.kernel.org/doc/Documentation/printk-formats.txt
// As noted in http://sourceware.org/binutils/docs-2.23.1/as/Symbol-Names.html
// symbols may contain periods and dollars, like "scm_recv.isra.18+0x61/0xdf".
// Strangely, occasionally there is an additional string following the symbol,
// like "qcusbnet_get+0x3e4/0x1e46 [gobi]".
// Those lines will be missing in the output, as parsing them is troublesome.
// Sometimes the address is printed and not the symbol. Those are ignored too.
const char kSymbolAndOffsetRE[] =
"([[:alpha:]._$][[:alnum:]._$]*)\\+0x([[:xdigit:]]+)/0x([[:xdigit:]]+)";
using base::FilePath;
using base::StringPrintf;
namespace {
// Extract a string delimited by the given character, from the given offset
// into a source string. Returns false if the string is zero-sized or no
// delimiter was found.
bool GetDelimitedString(const std::string &str, char ch, size_t offset,
std::string *substr) {
size_t at = str.find_first_of(ch, offset);
if (at == std::string::npos || at == offset)
return false;
*substr = str.substr(offset, at - offset);
return true;
}
bool GetDriErrorState(const chromeos::dbus::Proxy &proxy,
const FilePath &error_state_path) {
chromeos::glib::ScopedError error;
gchar *error_state = NULL;
if (!dbus_g_proxy_call(proxy.gproxy(), debugd::kGetLog,
&chromeos::Resetter(&error).lvalue(),
G_TYPE_STRING, "i915_error_state", G_TYPE_INVALID,
G_TYPE_STRING, &error_state, G_TYPE_INVALID)) {
LOG(ERROR) << "Error performing D-Bus proxy call "
<< "'" << debugd::kGetLog << "'"
<< ": " << (error ? error->message : "");
g_free(error_state);
return false;
}
std::string error_state_str(error_state);
g_free(error_state);
if (error_state_str == "<empty>")
return false;
const char kBase64Header[] = "<base64>: ";
const size_t kBase64HeaderLength = sizeof(kBase64Header) - 1;
if (error_state_str.compare(0, kBase64HeaderLength, kBase64Header)) {
LOG(ERROR) << "i915_error_state is missing base64 header";
return false;
}
gsize len;
guchar *decoded_error_state =
g_base64_decode(error_state_str.c_str() + kBase64HeaderLength, &len);
int written;
written = file_util::WriteFile(error_state_path,
reinterpret_cast<const char*>(decoded_error_state), len);
g_free(decoded_error_state);
if (written < 0 || (gsize)written != len) {
LOG(ERROR) << "Could not write file " << error_state_path.value();
base::DeleteFile(error_state_path, false);
return false;
}
return true;
}
bool LogKernelTaskStates(const chromeos::dbus::Proxy &proxy,
const std::string &task_states_id) {
// Wrap task states with unique id - header
LOG(INFO) << kHeader << " " << task_states_id.c_str() << " " << kEnter;
// Trigger asynchronous logging of task states
chromeos::glib::ScopedError error;
if (!dbus_g_proxy_call(proxy.gproxy(), debugd::kLogKernelTaskStates,
&chromeos::Resetter(&error).lvalue(),
G_TYPE_INVALID, G_TYPE_INVALID)) {
LOG(ERROR) << "Error performing D-Bus proxy call "
<< "'" << debugd::kLogKernelTaskStates << "'"
<< ": " << (error ? error->message : "");
return false;
}
// Give kernel a second to finish the asynchronous logging
sleep(1);
// Wrap task states with unique id - footer
LOG(INFO) << kHeader << " " << task_states_id.c_str() << " " << kLeave;
return true;
}
bool GetKernelTaskStates(const chromeos::dbus::Proxy &proxy,
std::vector<std::string> *output) {
std::string task_states_id = StringPrintf("%016"PRIX64, base::RandUint64());
if (!LogKernelTaskStates(proxy, task_states_id))
return false;
std::ifstream syslog_stream(kSyslogPath, std::ios::in);
if (!syslog_stream.is_open()) {
LOG(ERROR) << "Couldn't open syslog for reading";
return false;
}
std::string ident_re = StringPrintf("%s\\[%d\\]:\\s+", kIdent, getpid());
// 2013-07-05T11:52:55.530503-07:00 localhost crash_reporter[9349]:
// kernel_task_states 33F114E753768B70 enter
pcrecpp::RE enter_re(kFirstTwoColumnsRE + ident_re + kHeader + " " +
task_states_id + " " + kEnter);
// 2013-07-05T11:52:56.556177-07:00 localhost crash_reporter[9349]:
// kernel_task_states 33F114E753768B70 leave
pcrecpp::RE leave_re(kFirstTwoColumnsRE + ident_re + kHeader + " " +
task_states_id + " " + kLeave);
// 2013-06-21T21:18:14.855071+00:00 localhost kernel: [362142.027766]
// NSS SSL ThreadW S 804f8e28 0 5995 1016 0x00000000
pcrecpp::RE task_header_re(std::string(kFirstTwoColumnsRE) +
kKernelIdentRE + kSpacesRE +
kKernelTimestampRE + kSpacesRE +
kTaskNameRE + kSpacesRE +
kTaskStateRE + kSpacesRE +
kHexNumRE + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
"0x" + kHexNumRE);
// 2013-06-21T21:18:14.815190+00:00 localhost kernel: [362142.016915]
// rsyslogd R running 0 340 1 0x00000000
pcrecpp::RE task_header_run32_re(std::string(kFirstTwoColumnsRE) +
kKernelIdentRE + kSpacesRE +
kKernelTimestampRE + kSpacesRE +
kTaskNameRE + kSpacesRE +
kTaskStateRE + kSpacesRE +
kRunning32 + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
"0x" + kHexNumRE);
// 2013-06-21T14:11:18.718810-07:00 localhost kernel: [ 656.217932]
// kworker/2:1 R running task 0 54 2 0x00000000
pcrecpp::RE task_header_run64_re(std::string(kFirstTwoColumnsRE) +
kKernelIdentRE + kSpacesRE +
kKernelTimestampRE + kSpacesRE +
kTaskNameRE + kSpacesRE +
kTaskStateRE + kSpacesRE +
kRunning64 + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
kNumRE + kSpacesRE +
"0x" + kHexNumRE);
std::string address_re = std::string("\\[<") + kHexNumRE + ">\\]";
std::string braced_sym_re = std::string("\\(") + kSymbolAndOffsetRE + "\\)";
// 2013-07-05T11:53:20.141355-07:00 localhost kernel: [ 169.476749]
// [<8002fb74>] (do_wait+0x1a8/0x248) from [<80030c48>] (sys_wait4+0xbc/0xf8)
pcrecpp::RE arm_stack_re(std::string(kFirstTwoColumnsRE) +
kKernelIdentRE + kSpacesRE +
kKernelTimestampRE + kSpacesRE +
address_re + kSpacesRE +
braced_sym_re + kSpacesRE +
"from" + kSpacesRE +
address_re + kSpacesRE +
braced_sym_re);
// 2013-06-21T14:12:34.457315-07:00 localhost kernel: [ 731.965907]
// [<ffffffff814aa602>] ? system_call_fastpath+0x16/0x1b
pcrecpp::RE x86_stack_re(std::string(kFirstTwoColumnsRE) +
kKernelIdentRE + kSpacesRE +
kKernelTimestampRE + kSpacesRE +
address_re + kSpacesRE +
"(\\?\\s+)?" +
kSymbolAndOffsetRE);
bool enter_matched = false;
bool leave_matched = false;
uint64 prev_address = 0;
std::string line;
while (std::getline(syslog_stream, line)) {
if (leave_re.FullMatch(line)) {
if (!enter_matched) {
LOG(ERROR) << "Task states header not found before footer";
return false;
}
leave_matched = true;
break; // Stop processing input after footer
}
if (enter_re.FullMatch(line)) {
if (enter_matched) {
LOG(ERROR) << "Task states header encountered twice";
return false;
}
enter_matched = true;
continue;
}
if(!enter_matched)
continue; // Skip lines before header
std::string task_name, task_state;
uint64 address1, address2;
unsigned long free_stack;
pid_t pid, ppid;
unsigned long flags;
std::string sym1, sym2;
uint64 offset1a, offset1b, offset2a, offset2b;
std::string question;
if (task_header_re.FullMatch(line, &task_name, &task_state,
pcrecpp::Hex(&address1), &free_stack, &pid,
&ppid, pcrecpp::Hex(&flags))) {
prev_address = 0;
output->push_back(StringPrintf("%s %s %016"PRIx64" %5lu %5d %6d 0x%08lx",
task_name.c_str(), task_state.c_str(),
address1, free_stack, pid, ppid, flags));
} else if (task_header_run32_re.FullMatch(line, &task_name, &task_state,
&free_stack, &pid, &ppid,
pcrecpp::Hex(&flags)) ||
task_header_run64_re.FullMatch(line, &task_name, &task_state,
&free_stack, &pid, &ppid,
pcrecpp::Hex(&flags))) {
prev_address = 0;
output->push_back(StringPrintf("%s %s %s %5lu %5d %6d 0x%08lx",
task_name.c_str(), task_state.c_str(),
kRunning64, free_stack, pid, ppid, flags));
} else if (arm_stack_re.FullMatch(line, pcrecpp::Hex(&address1), &sym1,
pcrecpp::Hex(&offset1a),
pcrecpp::Hex(&offset1b),
pcrecpp::Hex(&address2), &sym2,
pcrecpp::Hex(&offset2a),
pcrecpp::Hex(&offset2b))) {
if (prev_address != address1) {
output->push_back(StringPrintf("%s+0x%"PRIx64"/0x%"PRIx64,
sym1.c_str(), offset1a, offset1b));
}
output->push_back(StringPrintf("%s+0x%"PRIx64"/0x%"PRIx64,
sym2.c_str(), offset2a, offset2b));
prev_address = address2;
} else if (x86_stack_re.FullMatch(line, pcrecpp::Hex(&address1), &question,
&sym1, pcrecpp::Hex(&offset1a),
pcrecpp::Hex(&offset1b))) {
output->push_back(StringPrintf("%s%s+0x%"PRIx64"/0x%"PRIx64,
question.c_str(), sym1.c_str(),
offset1a, offset1b));
}
}
syslog_stream.close();
if(!enter_matched || !leave_matched) {
LOG(ERROR) << "Task states header or footer not found";
return false;
}
return true;
}
bool WriteKernelTaskStates(const chromeos::dbus::Proxy &proxy,
const FilePath &task_states_path) {
std::ofstream task_states_stream(task_states_path.value().c_str(),
std::ios::out);
if (!task_states_stream.is_open()) {
LOG(ERROR) << "Could not write file " << task_states_path.value();
return false;
}
std::vector<std::string> task_sates;
if (!GetKernelTaskStates(proxy, &task_sates)) {
base::DeleteFile(task_states_path, false);
return false;
}
for (std::vector<std::string>::iterator it = task_sates.begin();
it != task_sates.end(); ++it) {
task_states_stream << *it << std::endl;
}
task_states_stream.close();
return true;
}
bool GetAdditionalLogs(const FilePath &log_path) {
chromeos::dbus::BusConnection dbus = chromeos::dbus::GetSystemBusConnection();
if (!dbus.HasConnection()) {
LOG(ERROR) << "Error connecting to system D-Bus";
return false;
}
chromeos::dbus::Proxy proxy(dbus,
debugd::kDebugdServiceName,
debugd::kDebugdServicePath,
debugd::kDebugdInterface);
if (!proxy) {
LOG(ERROR) << "Error creating D-Bus proxy to interface "
<< "'" << debugd::kDebugdServiceName << "'";
return false;
}
FilePath error_state_path =
log_path.DirName().Append("i915_error_state.log.xz");
bool has_dri_error_state = false;
if (GetDriErrorState(proxy, error_state_path))
has_dri_error_state = true;
FilePath task_states_path = log_path.DirName().Append("task_states.log");
bool has_task_states = false;
if (WriteKernelTaskStates(proxy, task_states_path)) {
has_task_states = true;
}
if (!has_dri_error_state && !has_task_states)
return false;
chromeos::ProcessImpl tar_process;
tar_process.AddArg(kTarPath);
tar_process.AddArg("cfJ");
tar_process.AddArg(log_path.value());
tar_process.AddStringOption("-C", log_path.DirName().value());
if (has_dri_error_state)
tar_process.AddArg(error_state_path.BaseName().value());
if (has_task_states)
tar_process.AddArg(task_states_path.BaseName().value());
int res = tar_process.Run();
base::DeleteFile(error_state_path, false);
base::DeleteFile(task_states_path, false);
if (res || !base::PathExists(log_path)) {
LOG(ERROR) << "Could not tar file " << log_path.value();
return false;
}
return true;
}
} //namespace
ChromeCollector::ChromeCollector() {}
ChromeCollector::~ChromeCollector() {}
bool ChromeCollector::HandleCrash(const std::string &file_path,
const std::string &pid_string,
const std::string &uid_string,
const std::string &exe_name) {
if (!is_feedback_allowed_function_())
return true;
if (exe_name.find('/') != std::string::npos) {
LOG(ERROR) << "exe_name contains illegal characters: " << exe_name;
return false;
}
FilePath dir;
uid_t uid = atoi(uid_string.c_str());
pid_t pid = atoi(pid_string.c_str());
if (!GetCreatedCrashDirectoryByEuid(uid, &dir, NULL)) {
LOG(ERROR) << "Can't create crash directory for uid " << uid;
return false;
}
std::string dump_basename = FormatDumpBasename(exe_name, time(NULL), pid);
FilePath meta_path = GetCrashPath(dir, dump_basename, "meta");
FilePath minidump_path = GetCrashPath(dir, dump_basename, "dmp");
FilePath log_path = GetCrashPath(dir, dump_basename, "log.tar.xz");
std::string data;
if (!base::ReadFileToString(FilePath(file_path), &data)) {
LOG(ERROR) << "Can't read crash log: " << file_path.c_str();
return false;
}
if (!ParseCrashLog(data, dir, minidump_path, dump_basename)) {
LOG(ERROR) << "Failed to parse Chrome's crash log";
return false;
}
if (GetAdditionalLogs(log_path)) {
int64 minidump_size = 0;
int64 log_size = 0;
if (base::GetFileSize(minidump_path, &minidump_size) &&
base::GetFileSize(log_path, &log_size) &&
minidump_size > 0 && log_size > 0 &&
minidump_size + log_size < kDefaultMaxUploadBytes) {
AddCrashMetaData("log", log_path.value());
} else {
LOG(INFO) << "Skipping logs upload to prevent discarding minidump "
"because of report size limit < " << minidump_size + log_size;
base::DeleteFile(log_path, false);
}
}
// We're done.
WriteCrashMetaData(meta_path, exe_name, minidump_path.value());
return true;
}
bool ChromeCollector::ParseCrashLog(const std::string &data,
const FilePath &dir,
const FilePath &minidump,
const std::string &basename) {
size_t at = 0;
while (at < data.size()) {
// Look for a : followed by a decimal number, followed by another :
// followed by N bytes of data.
std::string name, size_string;
if (!GetDelimitedString(data, ':', at, &name)) {
LOG(ERROR) << "Can't find : after name @ offset " << at;
break;
}
at += name.size() + 1; // Skip the name & : delimiter.
if (!GetDelimitedString(data, ':', at, &size_string)) {
LOG(ERROR) << "Can't find : after size @ offset " << at;
break;
}
at += size_string.size() + 1; // Skip the size & : delimiter.
size_t size;
if (!base::StringToSizeT(size_string, &size)) {
LOG(ERROR) << "String not convertible to integer: " << size_string;
break;
}
// Data would run past the end, did we get a truncated file?
if (at + size > data.size()) {
LOG(ERROR) << "Overrun, expected " << size << " bytes of data, got "
<< (data.size() - at);
break;
}
if (name.find("filename") != std::string::npos) {
// File.
// Name will be in a semi-MIME format of
// <descriptive name>"; filename="<name>"
// Descriptive name will be upload_file_minidump for the dump.
std::string desc, filename;
pcrecpp::RE re("(.*)\" *; *filename=\"(.*)\"");
if (!re.FullMatch(name.c_str(), &desc, &filename)) {
LOG(ERROR) << "Filename was not in expected format: " << name;
break;
}
if (desc.compare(kDefaultMinidumpName) == 0) {
// The minidump.
WriteNewFile(minidump, data.c_str() + at, size);
} else {
// Some other file.
FilePath path = GetCrashPath(dir, basename + "-" + filename, "other");
if (WriteNewFile(path, data.c_str() + at, size) >= 0) {
AddCrashMetaUploadFile(desc, path.value());
}
}
} else {
// Other attribute.
std::string value_str;
value_str.reserve(size);
// Since metadata is one line/value the values must be escaped properly.
for (size_t i = at; i < at + size; i++) {
switch (data[i]) {
case '"':
case '\\':
value_str.push_back('\\');
value_str.push_back(data[i]);
break;
case '\r':
value_str += "\\r";
break;
case '\n':
value_str += "\\n";
break;
case '\t':
value_str += "\\t";
break;
case '\0':
value_str += "\\0";
break;
default:
value_str.push_back(data[i]);
break;
}
}
AddCrashMetaUploadData(name, value_str);
}
at += size;
}
return at == data.size();
}