// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/minidump_uploader/rewrite_minidumps_as_mimes.h"
#include <utility>
#include "base/android/build_info.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "components/crash/android/anr_build_id_provider.h"
#include "components/crash/android/anr_skipped_reason.h"
#include "components/version_info/android/channel_getter.h"
#include "components/version_info/version_info.h"
#include "third_party/crashpad/crashpad/handler/minidump_to_upload_parameters.h"
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h"
#include "third_party/crashpad/crashpad/util/file/file_writer.h"
#include "third_party/crashpad/crashpad/util/net/http_body.h"
#include "third_party/crashpad/crashpad/util/net/http_multipart_builder.h"
#include "third_party/crashpad/crashpad/util/posix/signals.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/minidump_uploader/minidump_uploader_jni_headers/CrashReportMimeWriter_jni.h"
namespace minidump_uploader {
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ProcessedMinidumpCounts {
kOther = 0,
kBrowser = 1,
kRenderer = 2,
kGpu = 3,
kUtility = 4,
kMaxValue = kUtility
};
bool MimeifyReportWithKeyValuePairs(
const crashpad::CrashReportDatabase::UploadReport& report,
crashpad::HTTPMultipartBuilder* http_multipart_builder,
std::vector<std::string>* crashes_key_value_arr,
pid_t* pid) {
crashpad::FileReader* reader = report.Reader();
crashpad::FileOffset start_offset = reader->SeekGet();
if (start_offset < 0) {
return false;
}
// Ignore any errors that might occur when attempting to interpret the
// minidump file. This may result in its being uploaded with few or no
// parameters, but as long as there’s a dump file, the server can decide what
// to do with it.
std::map<std::string, std::string> parameters;
crashpad::ProcessSnapshotMinidump minidump_process_snapshot;
if (minidump_process_snapshot.Initialize(reader)) {
parameters =
BreakpadHTTPFormParametersFromMinidump(&minidump_process_snapshot);
}
if (!reader->SeekSet(start_offset)) {
return false;
}
static constexpr char kMinidumpKey[] = "upload_file_minidump";
static constexpr char kPtypeKey[] = "ptype";
for (const auto& kv : parameters) {
if (kv.first == kMinidumpKey) {
LOG(WARNING) << "reserved key " << kv.first << ", discarding value "
<< kv.second;
} else {
http_multipart_builder->SetFormData(kv.first, kv.second);
if (crashes_key_value_arr) {
crashes_key_value_arr->push_back(kv.first);
crashes_key_value_arr->push_back(kv.second);
}
if (kv.first == kPtypeKey) {
const crashpad::ExceptionSnapshot* exception =
minidump_process_snapshot.Exception();
if (exception != nullptr) {
const uint32_t signo = exception->Exception();
ProcessedMinidumpCounts count_type;
if (kv.second == "browser") {
count_type = ProcessedMinidumpCounts::kBrowser;
} else if (kv.second == "renderer") {
count_type = ProcessedMinidumpCounts::kRenderer;
} else if (kv.second == "gpu-process") {
count_type = ProcessedMinidumpCounts::kGpu;
} else if (kv.second == "utility") {
count_type = ProcessedMinidumpCounts::kUtility;
} else {
count_type = ProcessedMinidumpCounts::kOther;
}
if (signo !=
static_cast<uint32_t>(crashpad::Signals::kSimulatedSigno)) {
UMA_HISTOGRAM_ENUMERATION(
"Stability.Android.ProcessedRealMinidumps", count_type);
}
}
}
}
}
if (crashes_key_value_arr) {
crashes_key_value_arr->push_back(kMinidumpKey);
crashes_key_value_arr->push_back(report.uuid.ToString().c_str());
}
http_multipart_builder->SetFileAttachment(kMinidumpKey,
report.uuid.ToString() + ".dmp",
reader, "application/octet-stream");
*pid = minidump_process_snapshot.ProcessID();
return true;
}
bool MimeifyReportAndWriteToDirectory(
const crashpad::CrashReportDatabase::UploadReport& report,
const base::FilePath& dest_dir,
std::vector<std::string>* crashes_key_value_arr) {
crashpad::HTTPMultipartBuilder builder;
pid_t pid;
if (!MimeifyReportWithKeyValuePairs(report, &builder, crashes_key_value_arr,
&pid)) {
return false;
}
crashpad::FileWriter writer;
if (!writer.Open(dest_dir.Append(base::StringPrintf(
"%s.dmp%d", report.uuid.ToString().c_str(), pid)),
crashpad::FileWriteMode::kCreateOrFail,
crashpad::FilePermissions::kOwnerOnly)) {
return false;
}
return WriteBodyToFile(builder.GetBodyStream().get(), &writer);
}
} // namespace
bool MimeifyReport(const crashpad::CrashReportDatabase::UploadReport& report,
crashpad::HTTPMultipartBuilder* http_multipart_builder,
pid_t* pid) {
return MimeifyReportWithKeyValuePairs(report, http_multipart_builder, nullptr,
pid);
}
bool WriteBodyToFile(crashpad::HTTPBodyStream* body,
crashpad::FileWriterInterface* writer) {
uint8_t buffer[4096];
crashpad::FileOperationResult bytes_read;
while ((bytes_read = body->GetBytesBuffer(buffer, sizeof(buffer))) > 0) {
writer->Write(buffer, bytes_read);
}
return bytes_read == 0;
}
void RewriteMinidumpsAsMIMEs(const base::FilePath& src_dir,
const base::FilePath& dest_dir,
std::vector<std::string>* crashes_key_value_arr) {
std::unique_ptr<crashpad::CrashReportDatabase> db =
crashpad::CrashReportDatabase::InitializeWithoutCreating(src_dir);
if (!db) {
return;
}
std::vector<crashpad::CrashReportDatabase::Report> reports;
if (db->GetPendingReports(&reports) !=
crashpad::CrashReportDatabase::kNoError) {
return;
}
for (const auto& report : reports) {
std::unique_ptr<const crashpad::CrashReportDatabase::UploadReport>
upload_report;
switch (db->GetReportForUploading(report.uuid,
&upload_report,
/* report_metrics= */ false)) {
case crashpad::CrashReportDatabase::kBusyError:
case crashpad::CrashReportDatabase::kReportNotFound:
continue;
case crashpad::CrashReportDatabase::kNoError:
if (MimeifyReportAndWriteToDirectory(*upload_report.get(), dest_dir,
crashes_key_value_arr)) {
db->RecordUploadComplete(std::move(upload_report), std::string());
} else {
crashpad::Metrics::CrashUploadSkipped(
crashpad::Metrics::CrashSkippedReason::kPrepareForUploadFailed);
upload_report.reset();
}
db->DeleteReport(report.uuid);
continue;
case crashpad::CrashReportDatabase::kFileSystemError:
case crashpad::CrashReportDatabase::kDatabaseError:
crashpad::Metrics::CrashUploadSkipped(
crashpad::Metrics::CrashSkippedReason::kDatabaseError);
db->DeleteReport(report.uuid);
continue;
case crashpad::CrashReportDatabase::kCannotRequestUpload:
NOTREACHED_IN_MIGRATION();
db->DeleteReport(report.uuid);
continue;
}
}
}
static void reportAnrUploadFailure(AnrSkippedReason reason) {
UMA_HISTOGRAM_ENUMERATION("Crashpad.AnrUpload.Skipped", reason);
}
void WriteAnrAsMime(crashpad::FileReader* anr_reader,
crashpad::FileWriterInterface* writer,
const std::string& version_number,
const std::string& build_id,
const std::string& anr_file_name) {
crashpad::HTTPMultipartBuilder builder;
builder.SetFormData("version", version_number);
builder.SetFormData("product", "Chrome_Android");
std::string channel = std::string(
version_info::GetChannelString(version_info::android::GetChannel()));
if (channel == "stable") {
// Android reports require an empty string instead of "stable".
channel = "";
}
builder.SetFormData("channel", channel);
if (build_id.empty()) {
if (version_number == version_info::GetVersionNumber()) {
// We have an ANR where we didn't pre-set the build ID in the process
// state summary, but since we are currently on the same version we can
// just use our current one.
builder.SetFormData("elf_build_id", crash_reporter::GetElfBuildId());
}
} else {
builder.SetFormData("elf_build_id", build_id);
}
// We can't use crashpad::AnnotationList::Get() as it contains a number of
// fields which change on each Chrome restart.
base::android::BuildInfo* info = base::android::BuildInfo::GetInstance();
builder.SetFormData("android_build_id", info->android_build_id());
builder.SetFormData("android_build_fp", info->android_build_fp());
builder.SetFormData("sdk", base::StringPrintf("%d", info->sdk_int()));
builder.SetFormData("device", info->device());
builder.SetFormData("model", info->model());
builder.SetFormData("brand", info->brand());
builder.SetFormData("board", info->board());
builder.SetFormData("installer_package_name", info->installer_package_name());
builder.SetFormData("abi_name", info->abi_name());
builder.SetFormData("custom_themes", info->custom_themes());
builder.SetFormData("resources_version", info->resources_version());
builder.SetFormData("gms_core_version", info->gms_version_code());
// The package name and version are used for deobfuscation, but will
// only be accurate for the same version of chrome.
if (version_number == version_info::GetVersionNumber()) {
builder.SetFormData("package", std::string(info->package_name()) + " v" +
info->package_version_code() + " (" +
info->package_version_name() + ")");
}
if (anr_reader != nullptr) {
builder.SetFileAttachment("anr_data", anr_file_name, anr_reader,
"application/octet-stream");
}
if (!WriteBodyToFile(builder.GetBodyStream().get(), writer)) {
reportAnrUploadFailure(AnrSkippedReason::kFilesystemWriteFailure);
}
}
static void JNI_CrashReportMimeWriter_RewriteAnrsAsMIMEs(
JNIEnv* env,
const base::android::JavaParamRef<jobjectArray>& j_anrs,
const base::android::JavaParamRef<jstring>& j_dest_dir) {
std::vector<std::string> anr_strings;
base::android::AppendJavaStringArrayToStringVector(env, j_anrs, &anr_strings);
std::string dest_dir;
base::android::ConvertJavaStringToUTF8(env, j_dest_dir, &dest_dir);
for (size_t i = 0; i < anr_strings.size(); i += 3) {
std::string anr_proto_file_path = anr_strings.at(i);
std::string chrome_version = anr_strings.at(i + 1);
std::string build_id = anr_strings.at(i + 2);
crashpad::FileWriter writer;
crashpad::FileReader reader;
crashpad::UUID uuid;
uuid.InitializeWithNew();
std::string anr_file_name = uuid.ToString() + "_ANR.dmp";
if (!reader.Open(base::FilePath(anr_proto_file_path))) {
reportAnrUploadFailure(AnrSkippedReason::kFilesystemReadFailure);
continue;
}
if (!writer.Open(base::FilePath(dest_dir).Append(anr_file_name),
crashpad::FileWriteMode::kCreateOrFail,
crashpad::FilePermissions::kOwnerOnly)) {
reportAnrUploadFailure(AnrSkippedReason::kFilesystemWriteFailure);
continue;
}
WriteAnrAsMime(&reader, &writer, chrome_version, build_id, anr_file_name);
}
}
static void JNI_CrashReportMimeWriter_RewriteMinidumpsAsMIMEs(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& j_src_dir,
const base::android::JavaParamRef<jstring>& j_dest_dir) {
std::string src_dir, dest_dir;
base::android::ConvertJavaStringToUTF8(env, j_src_dir, &src_dir);
base::android::ConvertJavaStringToUTF8(env, j_dest_dir, &dest_dir);
RewriteMinidumpsAsMIMEs(base::FilePath(src_dir), base::FilePath(dest_dir),
nullptr);
}
static base::android::ScopedJavaLocalRef<jobjectArray>
JNI_CrashReportMimeWriter_RewriteMinidumpsAsMIMEsAndGetCrashKeys(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& j_src_dir,
const base::android::JavaParamRef<jstring>& j_dest_dir) {
std::string src_dir, dest_dir;
base::android::ConvertJavaStringToUTF8(env, j_src_dir, &src_dir);
base::android::ConvertJavaStringToUTF8(env, j_dest_dir, &dest_dir);
std::vector<std::string> key_value_arr;
RewriteMinidumpsAsMIMEs(base::FilePath(src_dir), base::FilePath(dest_dir),
&key_value_arr);
return base::android::ToJavaArrayOfStrings(env, key_value_arr);
}
} // namespace minidump_uploader