win,linux: implement attachments support

Implemented the AddAttachment(), InitializeAttachments(), CleanDatabase() functions
on Windows.

Added attachment=FILE_NAME option to the handler, and
"attachments" argument for Windows and Linux to StartHandler function.
On crash it will create the corresponding attachments in the database
and copy content of the specified files to the database.

Bug: b/157144387

Change-Id: Ia238de39028e07112a7b971b5b7d5e71a5864f53
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2248099
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
IrinaShkviro 2020-06-24 09:03:47 +02:00 committed by Commit Bot
parent 7409adbff3
commit 4145699874
19 changed files with 432 additions and 26 deletions

View File

@ -33,7 +33,8 @@ std::vector<std::string> BuildHandlerArgvStrings(
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments) {
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments) {
std::vector<std::string> argv_strings(1, handler.value());
for (const auto& argument : arguments) {
@ -58,6 +59,11 @@ std::vector<std::string> BuildHandlerArgvStrings(
FormatArgumentString("annotation", kv.first + '=' + kv.second));
}
for (const auto& attachment : attachments) {
argv_strings.push_back(
FormatArgumentString("attachment", attachment.value()));
}
return argv_strings;
}

View File

@ -35,7 +35,8 @@ std::vector<std::string> BuildHandlerArgvStrings(
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments);
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments = {});
//! \brief Flattens a string vector into a const char* vector suitable for use
//! in an exec() call.

View File

@ -126,7 +126,7 @@ class CrashReportDatabase {
//! \brief Adds an attachment to the report.
//!
//! \note This function is not yet implemented on macOS or Windows.
//! \note This function is not yet implemented on macOS.
//!
//! \param[in] name The key and name for the attachment, which will be
//! included in the http upload. The attachment will not appear in the
@ -170,7 +170,7 @@ class CrashReportDatabase {
//! \brief Obtains a mapping of names to file readers for any attachments
//! for the report.
//!
//! This is not implemented on macOS or Windows.
//! This is not implemented on macOS.
std::map<std::string, FileReader*> GetAttachments() const {
return attachment_map_;
}
@ -396,8 +396,7 @@ class CrashReportDatabase {
//! \brief Cleans the database of expired lockfiles, metadata without report
//! files, and report files without metadata.
//!
//! This method does nothing on the macOS and Windows implementations of the
//! database.
//! This method does nothing on the macOS implementations of the database.
//!
//! \param[in] lockfile_ttl The number of seconds at which lockfiles or new
//! report files are considered expired.

View File

@ -29,6 +29,8 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "client/settings.h"
#include "util/file/directory_reader.h"
#include "util/file/filesystem.h"
#include "util/misc/implicit_cast.h"
#include "util/misc/initialization_state_dcheck.h"
#include "util/misc/metrics.h"
@ -47,6 +49,8 @@ constexpr wchar_t kCrashReportFileExtension[] = L"dmp";
constexpr uint32_t kMetadataFileHeaderMagic = 'CPAD';
constexpr uint32_t kMetadataFileVersion = 1;
constexpr base::FilePath::CharType kAttachmentsDirectory[] = L"attachments";
using OperationStatus = CrashReportDatabase::OperationStatus;
// Helpers ---------------------------------------------------------------------
@ -82,6 +86,11 @@ std::string ReadRestOfFileAsString(FileHandle file) {
: std::string();
}
bool UUIDFromReportPath(const base::FilePath& path, UUID* uuid) {
return uuid->InitializeFromString(
path.RemoveFinalExtension().BaseName().value());
}
// Helper structures, and conversions ------------------------------------------
// The format of the on disk metadata file is a MetadataFileHeader, followed by
@ -263,6 +272,12 @@ class Metadata {
OperationStatus DeleteReport(const UUID& uuid,
base::FilePath* report_path);
//! \brief Removes reports from the metadata database, that don't have
//! corresponding report files.
//!
//! \return number of metadata entries removed
int CleanDatabase();
private:
Metadata(FileHandle handle, const base::FilePath& report_dir);
@ -399,6 +414,20 @@ OperationStatus Metadata::DeleteReport(const UUID& uuid,
return CrashReportDatabase::kNoError;
}
int Metadata::CleanDatabase() {
int removed = 0;
for (auto report_iter = reports_.begin(); report_iter != reports_.end();) {
if (!IsRegularFile(report_iter->file_path)) {
report_iter = reports_.erase(report_iter);
++removed;
dirty_ = true;
} else {
++report_iter;
}
}
return removed;
}
Metadata::Metadata(FileHandle handle, const base::FilePath& report_dir)
: handle_(handle), report_dir_(report_dir), dirty_(false), reports_() {
}
@ -611,6 +640,13 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
Metrics::CrashSkippedReason reason) override;
OperationStatus DeleteReport(const UUID& uuid) override;
OperationStatus RequestUpload(const UUID& uuid) override;
int CleanDatabase(time_t lockfile_ttl) override;
// Build a filepath for the root attachments directory.
base::FilePath AttachmentsRootPath();
// Build a filepath for the directory for the report to hold attachments.
base::FilePath AttachmentsPath(const UUID& uuid);
private:
// CrashReportDatabase:
@ -618,6 +654,13 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
bool successful,
const std::string& id) override;
// Cleans any attachments that have no associated report.
void CleanOrphanedAttachments();
// Attempt to remove any attachments associated with the given report UUID.
// There may not be any, so failing is not an error.
void RemoveAttachmentsByUUID(const UUID& uuid);
std::unique_ptr<Metadata> AcquireMetadata();
base::FilePath base_dir_;
@ -627,14 +670,66 @@ class CrashReportDatabaseWin : public CrashReportDatabase {
DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseWin);
};
base::FilePath CrashReportDatabaseWin::AttachmentsRootPath() {
return base_dir_.Append(kAttachmentsDirectory);
}
base::FilePath CrashReportDatabaseWin::AttachmentsPath(const UUID& uuid) {
const std::wstring uuid_string = uuid.ToString16();
return base_dir_.Append(kAttachmentsDirectory).Append(uuid_string);
}
FileWriter* CrashReportDatabase::NewReport::AddAttachment(
const std::string& name) {
// Attachments aren't implemented in the Windows database yet.
return nullptr;
auto database_win = static_cast<CrashReportDatabaseWin*>(database_);
base::FilePath attachments_root_dir = database_win->AttachmentsRootPath();
base::FilePath attachments_dir = database_win->AttachmentsPath(uuid_);
if (!LoggingCreateDirectory(
attachments_root_dir, FilePermissions::kOwnerOnly, true) ||
!LoggingCreateDirectory(
attachments_dir, FilePermissions::kOwnerOnly, true)) {
return nullptr;
}
base::FilePath path = attachments_dir.Append(base::UTF8ToUTF16(name));
auto writer = std::make_unique<FileWriter>();
if (!writer->Open(
path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly)) {
LOG(ERROR) << "could not open " << path.value().c_str();
return nullptr;
}
attachment_writers_.emplace_back(std::move(writer));
attachment_removers_.emplace_back(ScopedRemoveFile(path));
return attachment_writers_.back().get();
}
void CrashReportDatabase::UploadReport::InitializeAttachments() {
// Attachments aren't implemented in the Windows database yet.
base::FilePath attachments_dir =
static_cast<CrashReportDatabaseWin*>(database_)->AttachmentsPath(uuid);
if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
return;
}
DirectoryReader dir_reader;
if (!dir_reader.Open(attachments_dir)) {
return;
}
base::FilePath filename;
DirectoryReader::Result dir_result;
while ((dir_result = dir_reader.NextFile(&filename)) ==
DirectoryReader::Result::kSuccess) {
const base::FilePath filepath(attachments_dir.Append(filename));
std::unique_ptr<FileReader> file_reader(std::make_unique<FileReader>());
if (!file_reader->Open(filepath)) {
LOG(ERROR) << "attachment " << filepath.value().c_str()
<< " couldn't be opened, skipping";
continue;
}
attachment_readers_.emplace_back(std::move(file_reader));
attachment_map_[base::UTF16ToUTF8(filename.value())] =
attachment_readers_.back().get();
}
}
CrashReportDatabaseWin::CrashReportDatabaseWin(const base::FilePath& path)
@ -700,6 +795,14 @@ OperationStatus CrashReportDatabaseWin::FinishedWritingCrashReport(
ignore_result(report->file_remover_.release());
// Close all the attachments and disarm their removers too.
for (auto& writer : report->attachment_writers_) {
writer->Close();
}
for (auto& remover : report->attachment_removers_) {
ignore_result(remover.release());
}
*uuid = report->ReportID();
Metrics::CrashReportPending(Metrics::PendingReportReason::kNewlyCreated);
@ -826,9 +929,33 @@ OperationStatus CrashReportDatabaseWin::DeleteReport(const UUID& uuid) {
<< base::UTF16ToUTF8(report_path.value());
return kFileSystemError;
}
RemoveAttachmentsByUUID(uuid);
return kNoError;
}
void CrashReportDatabaseWin::RemoveAttachmentsByUUID(const UUID& uuid) {
base::FilePath attachments_dir = AttachmentsPath(uuid);
if (!IsDirectory(attachments_dir, /*allow_symlinks=*/false)) {
return;
}
DirectoryReader reader;
if (!reader.Open(attachments_dir)) {
return;
}
base::FilePath filename;
DirectoryReader::Result result;
while ((result = reader.NextFile(&filename)) ==
DirectoryReader::Result::kSuccess) {
const base::FilePath filepath(attachments_dir.Append(filename));
LoggingRemoveFile(filepath);
}
LoggingRemoveDirectory(attachments_dir);
}
OperationStatus CrashReportDatabaseWin::SkipReportUpload(
const UUID& uuid,
Metrics::CrashSkippedReason reason) {
@ -897,6 +1024,86 @@ OperationStatus CrashReportDatabaseWin::RequestUpload(const UUID& uuid) {
return kNoError;
}
int CrashReportDatabaseWin::CleanDatabase(time_t lockfile_ttl) {
int removed = 0;
const base::FilePath dir_path(base_dir_.Append(kReportsDirectory));
DirectoryReader reader;
if (!reader.Open(dir_path)) {
return removed;
}
base::FilePath filename;
DirectoryReader::Result result;
time_t now = time(nullptr);
std::unique_ptr<Metadata> metadata(AcquireMetadata());
// Remove old reports without metadata.
while ((result = reader.NextFile(&filename)) ==
DirectoryReader::Result::kSuccess) {
timespec filetime;
const base::FilePath report_path(dir_path.Append(filename));
if (!FileModificationTime(report_path, &filetime) ||
filetime.tv_sec > now - lockfile_ttl) {
continue;
}
const ReportDisk* report_disk;
UUID uuid;
bool is_uuid = UUIDFromReportPath(report_path, &uuid);
// ignore files whose base name is not uuid
if (!is_uuid) {
continue;
}
OperationStatus os = metadata->FindSingleReport(uuid, &report_disk);
if (os == OperationStatus::kReportNotFound) {
if (LoggingRemoveFile(report_path)) {
++removed;
RemoveAttachmentsByUUID(uuid);
}
continue;
}
}
// Remove any metadata records without report files.
removed += metadata->CleanDatabase();
CleanOrphanedAttachments();
return removed;
}
void CrashReportDatabaseWin::CleanOrphanedAttachments() {
base::FilePath root_attachments_dir(base_dir_.Append(kAttachmentsDirectory));
DirectoryReader reader;
if (!reader.Open(root_attachments_dir)) {
return;
}
base::FilePath filename;
DirectoryReader::Result result;
base::FilePath reports_dir = base_dir_.Append(kReportsDirectory);
while ((result = reader.NextFile(&filename)) ==
DirectoryReader::Result::kSuccess) {
const base::FilePath path(root_attachments_dir.Append(filename));
if (IsDirectory(path, false)) {
UUID uuid;
if (!uuid.InitializeFromString(filename.value())) {
LOG(ERROR) << "unexpected attachment dir name "
<< filename.value().c_str();
continue;
}
// Remove attachments if corresponding report doen't exist.
base::FilePath report_path =
reports_dir.Append(uuid.ToString16() + kCrashReportFileExtension);
if (!IsRegularFile(report_path)) {
RemoveAttachmentsByUUID(uuid);
}
}
}
}
// static
std::unique_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
const base::FilePath& path) {

View File

@ -107,6 +107,8 @@ class CrashpadClient {
//! a background thread. Optionally, WaitForHandlerStart() can be used at
//! a suitable time to retreive the result of background startup. This
//! option is only used on Windows.
//! \param[in] attachments Vector that stores file paths that should be
//! captured with each report at the time of the crash.
//!
//! \return `true` on success, `false` on failure with a message logged.
bool StartHandler(const base::FilePath& handler,
@ -116,7 +118,8 @@ class CrashpadClient {
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start);
bool asynchronous_start,
const std::vector<base::FilePath>& attachments = {});
#if defined(OS_ANDROID) || defined(OS_LINUX) || DOXYGEN
//! \brief Retrieve the socket and process ID for the handler.
@ -340,7 +343,8 @@ class CrashpadClient {
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments);
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments = {});
//! \brief Starts a handler process with an initial client.
//!

View File

@ -39,7 +39,9 @@ bool CrashpadClient::StartHandler(
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start) {
bool asynchronous_start,
const std::vector<base::FilePath>& attachments) {
DCHECK(attachments.empty()); // Attachments are not implemented on Fuchsia yet.
DCHECK_EQ(restartable, false); // Not used on Fuchsia.
DCHECK_EQ(asynchronous_start, false); // Not used on Fuchsia.

View File

@ -372,7 +372,8 @@ bool CrashpadClient::StartHandler(
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start) {
bool asynchronous_start,
const std::vector<base::FilePath>& attachments) {
DCHECK(!asynchronous_start);
ScopedFileHandle client_sock, handler_sock;
@ -382,7 +383,7 @@ bool CrashpadClient::StartHandler(
}
std::vector<std::string> argv = BuildHandlerArgvStrings(
handler, database, metrics_dir, url, annotations, arguments);
handler, database, metrics_dir, url, annotations, arguments, attachments);
argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get()));
argv.push_back("--shared-client-connection");
@ -507,9 +508,10 @@ bool CrashpadClient::StartHandlerAtCrash(
const base::FilePath& metrics_dir,
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments) {
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments) {
std::vector<std::string> argv = BuildHandlerArgvStrings(
handler, database, metrics_dir, url, annotations, arguments);
handler, database, metrics_dir, url, annotations, arguments, attachments);
auto signal_handler = LaunchAtCrashHandler::Get();
return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_);

View File

@ -89,14 +89,16 @@ bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) {
bool InstallHandler(CrashpadClient* client,
bool start_at_crash,
const base::FilePath& handler_path,
const base::FilePath& database_path) {
const base::FilePath& database_path,
const std::vector<base::FilePath>& attachments) {
return start_at_crash
? client->StartHandlerAtCrash(handler_path,
database_path,
base::FilePath(),
"",
std::map<std::string, std::string>(),
std::vector<std::string>())
std::vector<std::string>(),
attachments)
: client->StartHandler(handler_path,
database_path,
base::FilePath(),
@ -104,16 +106,28 @@ bool InstallHandler(CrashpadClient* client,
std::map<std::string, std::string>(),
std::vector<std::string>(),
false,
false);
false,
attachments);
}
constexpr char kTestAnnotationName[] = "name_of_annotation";
constexpr char kTestAnnotationValue[] = "value_of_annotation";
constexpr char kTestAttachmentName[] = "test_attachment";
constexpr char kTestAttachmentContent[] = "attachment_content";
#if defined(OS_ANDROID)
constexpr char kTestAbortMessage[] = "test abort message";
#endif
void ValidateAttachment(const CrashReportDatabase::UploadReport* report) {
auto attachments = report->GetAttachments();
ASSERT_EQ(attachments.size(), 1u);
char buf[sizeof(kTestAttachmentContent)];
attachments.at(kTestAttachmentName)->Read(buf, sizeof(buf));
ASSERT_EQ(memcmp(kTestAttachmentContent, buf, sizeof(kTestAttachmentContent)),
0);
}
void ValidateDump(const CrashReportDatabase::UploadReport* report) {
ProcessSnapshotMinidump minidump_snapshot;
ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader()));
@ -129,6 +143,7 @@ void ValidateDump(const CrashReportDatabase::UploadReport* report) {
EXPECT_EQ(kTestAbortMessage, abort_message->second);
}
#endif
ValidateAttachment(report);
for (const ModuleSnapshot* module : minidump_snapshot.Modules()) {
for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
@ -169,11 +184,15 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
static StringAnnotation<32> test_annotation(kTestAnnotationName);
test_annotation.Set(kTestAnnotationValue);
const std::vector<base::FilePath> attachments = {
base::FilePath(kTestAttachmentName)};
crashpad::CrashpadClient client;
if (!InstallHandler(&client,
options.start_handler_at_crash,
handler_path,
base::FilePath(temp_dir))) {
base::FilePath(temp_dir),
attachments)) {
return EXIT_FAILURE;
}
@ -209,6 +228,16 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
private:
void MultiprocessParent() override {
FileWriter writer;
base::FilePath test_attachment_path = base::FilePath(kTestAttachmentName);
bool is_created = writer.Open(test_attachment_path,
FileWriteMode::kCreateOrFail,
FilePermissions::kOwnerOnly);
ASSERT_TRUE(is_created);
ScopedRemoveFile attachment_remover(test_attachment_path);
writer.Write(kTestAttachmentContent, sizeof(kTestAttachmentContent));
writer.Close();
ScopedTempDir temp_dir;
VMSize temp_dir_length = temp_dir.path().value().size();
ASSERT_TRUE(LoggingWriteFile(

View File

@ -446,7 +446,11 @@ bool CrashpadClient::StartHandler(
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start) {
bool asynchronous_start,
const std::vector<base::FilePath>& attachments) {
// Attachments are not implemented on MacOS yet.
DCHECK(attachments.empty());
// The “restartable” behavior can only be selected on OS X 10.10 and later. In
// previous OS versions, if the initial client were to crash while attempting
// to restart the handler, it would become an unkillable process.

View File

@ -310,6 +310,7 @@ struct BackgroundHandlerStartThreadData {
const std::string& url,
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
const std::vector<base::FilePath>& attachments,
const std::wstring& ipc_pipe,
ScopedFileHANDLE ipc_pipe_handle)
: handler(handler),
@ -318,6 +319,7 @@ struct BackgroundHandlerStartThreadData {
url(url),
annotations(annotations),
arguments(arguments),
attachments(attachments),
ipc_pipe(ipc_pipe),
ipc_pipe_handle(std::move(ipc_pipe_handle)) {}
@ -327,6 +329,7 @@ struct BackgroundHandlerStartThreadData {
std::string url;
std::map<std::string, std::string> annotations;
std::vector<std::string> arguments;
std::vector<base::FilePath> attachments;
std::wstring ipc_pipe;
ScopedFileHANDLE ipc_pipe_handle;
};
@ -382,6 +385,11 @@ bool StartHandlerProcess(
base::UTF8ToUTF16(kv.first + '=' + kv.second)),
&command_line);
}
for (const base::FilePath& attachment : data->attachments) {
AppendCommandLineArgument(
FormatArgumentString("attachment", attachment.value()),
&command_line);
}
ScopedKernelHANDLE this_process(
OpenProcess(kXPProcessAllAccess, true, GetCurrentProcessId()));
@ -599,7 +607,8 @@ bool CrashpadClient::StartHandler(
const std::map<std::string, std::string>& annotations,
const std::vector<std::string>& arguments,
bool restartable,
bool asynchronous_start) {
bool asynchronous_start,
const std::vector<base::FilePath>& attachments) {
DCHECK(ipc_pipe_.empty());
// Both the pipe and the signalling events have to be created on the main
@ -629,6 +638,7 @@ bool CrashpadClient::StartHandler(
url,
annotations,
arguments,
attachments,
ipc_pipe_,
std::move(ipc_pipe_handle));

View File

@ -101,6 +101,10 @@ void Usage(const base::FilePath& me) {
"Crashpad's exception handler server.\n"
"\n"
" --annotation=KEY=VALUE set a process annotation in each crash report\n"
#if defined(OS_WIN) || defined(OS_LINUX)
" --attachment=FILE_PATH attach specified file to each crash report\n"
" at the time of the crash\n"
#endif // OS_WIN || OS_LINUX
" --database=PATH store the crash report database at PATH\n"
#if defined(OS_MACOSX)
" --handshake-fd=FD establish communication with the client over FD\n"
@ -211,6 +215,9 @@ struct Options {
base::FilePath minidump_dir_for_tests;
bool always_allow_feedback = false;
#endif // OS_CHROMEOS
#if defined(OS_WIN) || defined (OS_LINUX)
std::vector<base::FilePath> attachments;
#endif // OS_WIN || OS_LINUX
};
// Splits |key_value| on '=' and inserts the resulting key and value into |map|.
@ -516,6 +523,9 @@ int HandlerMain(int argc,
// Long options without short equivalents.
kOptionLastChar = 255,
kOptionAnnotation,
#if defined(OS_WIN) || defined(OS_LINUX)
kOptionAttachment,
#endif // OS_WIN || OS_LINUX
kOptionDatabase,
#if defined(OS_MACOSX)
kOptionHandshakeFD,
@ -568,6 +578,9 @@ int HandlerMain(int argc,
static constexpr option long_options[] = {
{"annotation", required_argument, nullptr, kOptionAnnotation},
#if defined(OS_WIN) || defined(OS_LINUX)
{"attachment", required_argument, nullptr, kOptionAttachment},
#endif // OS_WIN || OS_LINUX
{"database", required_argument, nullptr, kOptionDatabase},
#if defined(OS_MACOSX)
{"handshake-fd", required_argument, nullptr, kOptionHandshakeFD},
@ -677,6 +690,13 @@ int HandlerMain(int argc,
}
break;
}
#if defined(OS_WIN) || defined(OS_LINUX)
case kOptionAttachment: {
options.attachments.push_back(base::FilePath(
ToolSupport::CommandLineArgumentToFilePathStringType(optarg)));
break;
}
#endif // OS_WIN || OS_LINUX
case kOptionDatabase: {
options.database = base::FilePath(
ToolSupport::CommandLineArgumentToFilePathStringType(optarg));
@ -988,6 +1008,9 @@ int HandlerMain(int argc,
database.get(),
static_cast<CrashReportUploadThread*>(upload_thread.Get()),
&options.annotations,
#if defined(OS_WIN) || defined(OS_LINUX)
&options.attachments,
#endif // OS_WIN || OS_LINUX
#if defined(OS_ANDROID)
options.write_minidump_to_database,
options.write_minidump_to_log,

View File

@ -23,6 +23,7 @@
#include "minidump/minidump_file_writer.h"
#include "snapshot/linux/process_snapshot_linux.h"
#include "snapshot/sanitized/process_snapshot_sanitized.h"
#include "util/file/file_helper.h"
#include "util/file/file_reader.h"
#include "util/file/output_stream_file_writer.h"
#include "util/linux/direct_ptrace_connection.h"
@ -62,12 +63,14 @@ CrashReportExceptionHandler::CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::vector<base::FilePath>* attachments,
bool write_minidump_to_database,
bool write_minidump_to_log,
const UserStreamDataSources* user_stream_data_sources)
: database_(database),
upload_thread_(upload_thread),
process_annotations_(process_annotations),
attachments_(attachments),
write_minidump_to_database_(write_minidump_to_database),
write_minidump_to_log_(write_minidump_to_log),
user_stream_data_sources_(user_stream_data_sources) {
@ -200,6 +203,25 @@ bool CrashReportExceptionHandler::WriteMinidumpToDatabase(
}
}
for (const auto& attachment : (*attachments_)) {
FileReader file_reader;
if (!file_reader.Open(attachment)) {
LOG(ERROR) << "attachment " << attachment.value().c_str()
<< " couldn't be opened, skipping";
continue;
}
base::FilePath filename = attachment.BaseName();
FileWriter* file_writer = new_report->AddAttachment(filename.value());
if (file_writer == nullptr) {
LOG(ERROR) << "attachment " << filename.value().c_str()
<< " couldn't be created, skipping";
continue;
}
CopyFileContent(&file_reader, file_writer);
}
UUID uuid;
database_status =
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);

View File

@ -53,6 +53,8 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] attachments A vector of file paths that should be captured with
//! each report at the time of the crash.
//! \param[in] write_minidump_to_database Whether the minidump shall be
//! written to database.
//! \param[in] write_minidump_to_log Whether the minidump shall be written to
@ -65,6 +67,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::vector<base::FilePath>* attachments,
bool write_minidump_to_database,
bool write_minidump_to_log,
const UserStreamDataSources* user_stream_data_sources);
@ -106,6 +109,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate {
CrashReportDatabase* database_; // weak
CrashReportUploadThread* upload_thread_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak
const std::vector<base::FilePath>* attachments_; // weak
bool write_minidump_to_database_;
bool write_minidump_to_log_;
const UserStreamDataSources* user_stream_data_sources_; // weak

View File

@ -17,12 +17,14 @@
#include <type_traits>
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "client/crash_report_database.h"
#include "client/settings.h"
#include "handler/crash_report_upload_thread.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/minidump_user_extension_stream_data_source.h"
#include "snapshot/win/process_snapshot_win.h"
#include "util/file/file_helper.h"
#include "util/file/file_writer.h"
#include "util/misc/metrics.h"
#include "util/win/registration_protocol_win.h"
@ -35,17 +37,17 @@ CrashReportExceptionHandler::CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::vector<base::FilePath>* attachments,
const UserStreamDataSources* user_stream_data_sources)
: database_(database),
upload_thread_(upload_thread),
process_annotations_(process_annotations),
attachments_(attachments),
user_stream_data_sources_(user_stream_data_sources) {}
CrashReportExceptionHandler::~CrashReportExceptionHandler() {
}
CrashReportExceptionHandler::~CrashReportExceptionHandler() {}
void CrashReportExceptionHandler::ExceptionHandlerServerStarted() {
}
void CrashReportExceptionHandler::ExceptionHandlerServerStarted() {}
unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
HANDLE process,
@ -114,6 +116,26 @@ unsigned int CrashReportExceptionHandler::ExceptionHandlerServerException(
return termination_code;
}
for (const auto& attachment : (*attachments_)) {
FileReader file_reader;
if (!file_reader.Open(attachment)) {
LOG(ERROR) << "attachment " << attachment.value().c_str()
<< " couldn't be opened, skipping";
continue;
}
base::FilePath filename = attachment.BaseName();
FileWriter* file_writer =
new_report->AddAttachment(base::UTF16ToUTF8(filename.value()));
if (file_writer == nullptr) {
LOG(ERROR) << "attachment " << filename.value().c_str()
<< " couldn't be created, skipping";
continue;
}
CopyFileContent(&file_reader, file_writer);
}
UUID uuid;
database_status =
database_->FinishedWritingCrashReport(std::move(new_report), &uuid);

View File

@ -50,6 +50,8 @@ class CrashReportExceptionHandler final
//! To interoperate with Breakpad servers, the recommended practice is to
//! specify values for the `"prod"` and `"ver"` keys as process
//! annotations.
//! \param[in] attachments A vector of file paths that should be captured with
//! each report at the time of the crash.
//! \param[in] user_stream_data_sources Data sources to be used to extend
//! crash reports. For each crash report that is written, the data sources
//! are called in turn. These data sources may contribute additional
@ -58,6 +60,7 @@ class CrashReportExceptionHandler final
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const std::vector<base::FilePath>* attachments,
const UserStreamDataSources* user_stream_data_sources);
~CrashReportExceptionHandler();
@ -76,6 +79,7 @@ class CrashReportExceptionHandler final
CrashReportDatabase* database_; // weak
CrashReportUploadThread* upload_thread_; // weak
const std::map<std::string, std::string>* process_annotations_; // weak
const std::vector<base::FilePath>* attachments_; // weak
const UserStreamDataSources* user_stream_data_sources_; // weak
DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);

View File

@ -149,6 +149,8 @@ static_library("util") {
"file/delimited_file_reader.cc",
"file/delimited_file_reader.h",
"file/directory_reader.h",
"file/file_helper.cc",
"file/file_helper.h",
"file/file_io.cc",
"file/file_io.h",
"file/file_reader.cc",

34
util/file/file_helper.cc Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2020 The Crashpad Authors. All rights reserved.
//
// Licensed 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/file/file_helper.h"
namespace crashpad {
void CopyFileContent(FileReaderInterface* file_reader,
FileWriterInterface* file_writer) {
char buf[4096];
FileOperationResult read_result;
do {
read_result = file_reader->Read(buf, sizeof(buf));
if (read_result < 0) {
break;
}
if (read_result > 0 && !file_writer->Write(buf, read_result)) {
break;
}
} while (read_result > 0);
}
} // namespace crashpad

29
util/file/file_helper.h Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2020 The Crashpad Authors. All rights reserved.
//
// Licensed 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.
#ifndef CRASHPAD_UTIL_FILE_FILE_HELPER_H_
#define CRASHPAD_UTIL_FILE_FILE_HELPER_H_
#include "util/file/file_reader.h"
#include "util/file/file_writer.h"
namespace crashpad {
//! \brief Copy the file content from file_reader to file_writer
void CopyFileContent(FileReaderInterface* file_reader,
FileWriterInterface* file_writer);
} // namespace crashpad
#endif // CRASHPAD_UTIL_FILE_FILE_HELPER_H_

View File

@ -37,6 +37,8 @@
'file/directory_reader.h',
'file/directory_reader_posix.cc',
'file/directory_reader_win.cc',
'file/file_helper.cc',
'file/file_helper.h',
'file/file_io.cc',
'file/file_io.h',
'file/file_io_posix.cc',