[microdump] Add build fingerprint and product info metadata.

This is to add build fingerprint and product name/version to
microdumps. Conversely to what happens in the case of minidumps
with MIME fields, due to the nature of minidumps, extra metadata
cannot be reliably injected after the dump is completed.
This CL adds the plumbing to inject two optional fields plus the
corresponding tests.

BUG=chromium:410294
R=thestig@chromium.org

Review URL: https://codereview.chromium.org/1125153008

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1456 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
primiano@chromium.org 2015-05-15 08:43:01 +00:00
parent f1ca06a079
commit 90cbb27528
6 changed files with 163 additions and 50 deletions

View file

@ -570,10 +570,13 @@ void ExceptionHandler::WaitForContinueSignal() {
bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context,
size_t context_size) { size_t context_size) {
if (minidump_descriptor_.IsMicrodumpOnConsole()) { if (minidump_descriptor_.IsMicrodumpOnConsole()) {
return google_breakpad::WriteMicrodump(crashing_process, return google_breakpad::WriteMicrodump(
context, crashing_process,
context_size, context,
mapping_list_); context_size,
mapping_list_,
minidump_descriptor_.microdump_build_fingerprint(),
minidump_descriptor_.microdump_product_info());
} }
if (minidump_descriptor_.IsFD()) { if (minidump_descriptor_.IsFD()) {
return google_breakpad::WriteMinidump(minidump_descriptor_.fd(), return google_breakpad::WriteMinidump(minidump_descriptor_.fd(),

View file

@ -44,7 +44,9 @@ MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor)
fd_(descriptor.fd_), fd_(descriptor.fd_),
directory_(descriptor.directory_), directory_(descriptor.directory_),
c_path_(NULL), c_path_(NULL),
size_limit_(descriptor.size_limit_) { size_limit_(descriptor.size_limit_),
microdump_build_fingerprint_(descriptor.microdump_build_fingerprint_),
microdump_product_info_(descriptor.microdump_product_info_) {
// The copy constructor is not allowed to be called on a MinidumpDescriptor // The copy constructor is not allowed to be called on a MinidumpDescriptor
// with a valid path_, as getting its c_path_ would require the heap which // with a valid path_, as getting its c_path_ would require the heap which
// can cause problems in compromised environments. // can cause problems in compromised environments.
@ -65,6 +67,8 @@ MinidumpDescriptor& MinidumpDescriptor::operator=(
UpdatePath(); UpdatePath();
} }
size_limit_ = descriptor.size_limit_; size_limit_ = descriptor.size_limit_;
microdump_build_fingerprint_ = descriptor.microdump_build_fingerprint_;
microdump_product_info_ = descriptor.microdump_product_info_;
return *this; return *this;
} }
@ -82,4 +86,15 @@ void MinidumpDescriptor::UpdatePath() {
c_path_ = path_.c_str(); c_path_ = path_.c_str();
} }
void MinidumpDescriptor::SetMicrodumpBuildFingerprint(
const char* build_fingerprint) {
assert(mode_ == kWriteMicrodumpToConsole);
microdump_build_fingerprint_ = build_fingerprint;
}
void MinidumpDescriptor::SetMicrodumpProductInfo(const char* product_info) {
assert(mode_ == kWriteMicrodumpToConsole);
microdump_product_info_ = product_info;
}
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -51,14 +51,18 @@ class MinidumpDescriptor {
MinidumpDescriptor() : mode_(kUninitialized), MinidumpDescriptor() : mode_(kUninitialized),
fd_(-1), fd_(-1),
size_limit_(-1) {} size_limit_(-1),
microdump_build_fingerprint_(NULL),
microdump_product_info_(NULL) {}
explicit MinidumpDescriptor(const string& directory) explicit MinidumpDescriptor(const string& directory)
: mode_(kWriteMinidumpToFile), : mode_(kWriteMinidumpToFile),
fd_(-1), fd_(-1),
directory_(directory), directory_(directory),
c_path_(NULL), c_path_(NULL),
size_limit_(-1) { size_limit_(-1),
microdump_build_fingerprint_(NULL),
microdump_product_info_(NULL) {
assert(!directory.empty()); assert(!directory.empty());
} }
@ -66,14 +70,18 @@ class MinidumpDescriptor {
: mode_(kWriteMinidumpToFd), : mode_(kWriteMinidumpToFd),
fd_(fd), fd_(fd),
c_path_(NULL), c_path_(NULL),
size_limit_(-1) { size_limit_(-1),
microdump_build_fingerprint_(NULL),
microdump_product_info_(NULL) {
assert(fd != -1); assert(fd != -1);
} }
explicit MinidumpDescriptor(const MicrodumpOnConsole&) explicit MinidumpDescriptor(const MicrodumpOnConsole&)
: mode_(kWriteMicrodumpToConsole), : mode_(kWriteMicrodumpToConsole),
fd_(-1), fd_(-1),
size_limit_(-1) {} size_limit_(-1),
microdump_build_fingerprint_(NULL),
microdump_product_info_(NULL) {}
explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor);
MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor);
@ -99,6 +107,18 @@ class MinidumpDescriptor {
off_t size_limit() const { return size_limit_; } off_t size_limit() const { return size_limit_; }
void set_size_limit(off_t limit) { size_limit_ = limit; } void set_size_limit(off_t limit) { size_limit_ = limit; }
// TODO(primiano): make this and product info (below) just part of the
// microdump ctor once it is rolled stably into Chrome. ETA: June 2015.
void SetMicrodumpBuildFingerprint(const char* build_fingerprint);
const char* microdump_build_fingerprint() const {
return microdump_build_fingerprint_;
}
void SetMicrodumpProductInfo(const char* product_info);
const char* microdump_product_info() const {
return microdump_product_info_;
}
private: private:
enum DumpMode { enum DumpMode {
kUninitialized = 0, kUninitialized = 0,
@ -115,13 +135,25 @@ class MinidumpDescriptor {
// The directory where the minidump should be generated. // The directory where the minidump should be generated.
string directory_; string directory_;
// The full path to the generated minidump. // The full path to the generated minidump.
string path_; string path_;
// The C string of |path_|. Precomputed so it can be access from a compromised // The C string of |path_|. Precomputed so it can be access from a compromised
// context. // context.
const char* c_path_; const char* c_path_;
off_t size_limit_; off_t size_limit_;
// The product name/version and build fingerprint that should be appended to
// the dump (microdump only). Microdumps don't have the ability of appending
// extra metadata after the dump is generated (as opposite to minidumps
// MIME fields), therefore the product details must be provided upfront.
// The string pointers are supposed to be valid through all the lifetime of
// the process (read: the caller has to guarantee that they are stored in
// global static storage).
const char* microdump_build_fingerprint_;
const char* microdump_product_info_;
}; };
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -60,6 +60,8 @@ class MicrodumpWriter {
public: public:
MicrodumpWriter(const ExceptionHandler::CrashContext* context, MicrodumpWriter(const ExceptionHandler::CrashContext* context,
const MappingList& mappings, const MappingList& mappings,
const char* build_fingerprint,
const char* product_info,
LinuxDumper* dumper) LinuxDumper* dumper)
: ucontext_(context ? &context->context : NULL), : ucontext_(context ? &context->context : NULL),
#if !defined(__ARM_EABI__) && !defined(__mips__) #if !defined(__ARM_EABI__) && !defined(__mips__)
@ -67,6 +69,8 @@ class MicrodumpWriter {
#endif #endif
dumper_(dumper), dumper_(dumper),
mapping_list_(mappings), mapping_list_(mappings),
build_fingerprint_(build_fingerprint),
product_info_(product_info),
log_line_(NULL) { log_line_(NULL) {
log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize)); log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize));
if (log_line_) if (log_line_)
@ -88,9 +92,9 @@ class MicrodumpWriter {
bool Dump() { bool Dump() {
bool success; bool success;
LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); LogLine("-----BEGIN BREAKPAD MICRODUMP-----");
success = DumpOSInformation(); DumpProductInformation();
if (success) DumpOSInformation();
success = DumpCrashingThread(); success = DumpCrashingThread();
if (success) if (success)
success = DumpMappings(); success = DumpMappings();
LogLine("-----END BREAKPAD MICRODUMP-----"); LogLine("-----END BREAKPAD MICRODUMP-----");
@ -143,10 +147,17 @@ class MicrodumpWriter {
my_strlcpy(log_line_, "", kLineBufferSize); my_strlcpy(log_line_, "", kLineBufferSize);
} }
bool DumpOSInformation() { void DumpProductInformation() {
struct utsname uts; LogAppend("V ");
if (uname(&uts)) if (product_info_) {
return false; LogAppend(product_info_);
} else {
LogAppend("UNKNOWN:0.0.0.0");
}
LogCommitLine();
}
void DumpOSInformation() {
const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF)); const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF));
#if defined(__ANDROID__) #if defined(__ANDROID__)
@ -178,13 +189,23 @@ class MicrodumpWriter {
LogAppend(" "); LogAppend(" ");
LogAppend(n_cpus); LogAppend(n_cpus);
LogAppend(" "); LogAppend(" ");
LogAppend(uts.machine); // If the client has attached a build fingerprint to the MinidumpDescriptor
LogAppend(" "); // use that one. Otherwise try to get some basic info from uname().
LogAppend(uts.release); if (build_fingerprint_) {
LogAppend(" "); LogAppend(build_fingerprint_);
LogAppend(uts.version); } else {
struct utsname uts;
if (uname(&uts) == 0) {
LogAppend(uts.machine);
LogAppend(" ");
LogAppend(uts.release);
LogAppend(" ");
LogAppend(uts.version);
} else {
LogAppend("no build fingerprint available");
}
}
LogCommitLine(); LogCommitLine();
return true;
} }
bool DumpThreadStack(uint32_t thread_id, bool DumpThreadStack(uint32_t thread_id,
@ -367,6 +388,8 @@ class MicrodumpWriter {
#endif #endif
LinuxDumper* dumper_; LinuxDumper* dumper_;
const MappingList& mapping_list_; const MappingList& mapping_list_;
const char* const build_fingerprint_;
const char* const product_info_;
char* log_line_; char* log_line_;
}; };
} // namespace } // namespace
@ -376,7 +399,9 @@ namespace google_breakpad {
bool WriteMicrodump(pid_t crashing_process, bool WriteMicrodump(pid_t crashing_process,
const void* blob, const void* blob,
size_t blob_size, size_t blob_size,
const MappingList& mappings) { const MappingList& mappings,
const char* build_fingerprint,
const char* product_info) {
LinuxPtraceDumper dumper(crashing_process); LinuxPtraceDumper dumper(crashing_process);
const ExceptionHandler::CrashContext* context = NULL; const ExceptionHandler::CrashContext* context = NULL;
if (blob) { if (blob) {
@ -388,7 +413,8 @@ bool WriteMicrodump(pid_t crashing_process,
dumper.set_crash_signal(context->siginfo.si_signo); dumper.set_crash_signal(context->siginfo.si_signo);
dumper.set_crash_thread(context->tid); dumper.set_crash_thread(context->tid);
} }
MicrodumpWriter writer(context, mappings, &dumper); MicrodumpWriter writer(context, mappings, build_fingerprint, product_info,
&dumper);
if (!writer.Init()) if (!writer.Init())
return false; return false;
return writer.Dump(); return writer.Dump();

View file

@ -46,12 +46,18 @@ namespace google_breakpad {
// blob: a blob of data from the crashing process. See exception_handler.h // blob: a blob of data from the crashing process. See exception_handler.h
// blob_size: the length of |blob| in bytes. // blob_size: the length of |blob| in bytes.
// mappings: a list of additional mappings provided by the application. // mappings: a list of additional mappings provided by the application.
// build_fingerprint: a (optional) C string which determines the OS
// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys).
// product_info: a (optional) C string which determines the product name and
// version (e.g., WebView:42.0.2311.136).
// //
// Returns true iff successful. // Returns true iff successful.
bool WriteMicrodump(pid_t crashing_process, bool WriteMicrodump(pid_t crashing_process,
const void* blob, const void* blob,
size_t blob_size, size_t blob_size,
const MappingList& mappings); const MappingList& mappings,
const char* build_fingerprint,
const char* product_info);
} // namespace google_breakpad } // namespace google_breakpad

View file

@ -48,7 +48,11 @@ namespace {
typedef testing::Test MicrodumpWriterTest; typedef testing::Test MicrodumpWriterTest;
TEST(MicrodumpWriterTest, Setup) { void CrashAndGetMicrodump(
const MappingList& mappings,
const char* build_fingerprint,
const char* product_info,
scoped_array<char>* buf) {
int fds[2]; int fds[2];
ASSERT_NE(-1, pipe(fds)); ASSERT_NE(-1, pipe(fds));
@ -73,6 +77,36 @@ TEST(MicrodumpWriterTest, Setup) {
// Set a non-zero tid to avoid tripping asserts. // Set a non-zero tid to avoid tripping asserts.
context.tid = child; context.tid = child;
// Redirect temporarily stderr to the stderr.log file.
int save_err = dup(STDERR_FILENO);
ASSERT_NE(-1, save_err);
ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings,
build_fingerprint, product_info));
// Revert stderr back to the console.
dup2(save_err, STDERR_FILENO);
close(save_err);
// Read back the stderr file and check for the microdump marker.
fsync(err_fd);
lseek(err_fd, 0, SEEK_SET);
const size_t kBufSize = 64 * 1024;
buf->reset(new char[kBufSize]);
ASSERT_GT(read(err_fd, buf->get(), kBufSize), 0);
close(err_fd);
close(fds[1]);
ASSERT_NE(static_cast<char*>(0), strstr(
buf->get(), "-----BEGIN BREAKPAD MICRODUMP-----"));
ASSERT_NE(static_cast<char*>(0), strstr(
buf->get(), "-----END BREAKPAD MICRODUMP-----"));
}
TEST(MicrodumpWriterTest, BasicWithMappings) {
// Push some extra mapping to check the MappingList logic. // Push some extra mapping to check the MappingList logic.
const uint32_t memory_size = sysconf(_SC_PAGESIZE); const uint32_t memory_size = sysconf(_SC_PAGESIZE);
const char* kMemoryName = "libfoo.so"; const char* kMemoryName = "libfoo.so";
@ -93,28 +127,8 @@ TEST(MicrodumpWriterTest, Setup) {
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping); mappings.push_back(mapping);
// Redirect temporarily stderr to the stderr.log file. scoped_array<char> buf;
int save_err = dup(STDERR_FILENO); CrashAndGetMicrodump(mappings, NULL, NULL, &buf);
ASSERT_NE(-1, save_err);
ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO));
ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings));
// Revert stderr back to the console.
dup2(save_err, STDERR_FILENO);
close(save_err);
// Read back the stderr file and check for the microdump marker.
fsync(err_fd);
lseek(err_fd, 0, SEEK_SET);
const size_t kBufSize = 64 * 1024;
scoped_array<char> buf(new char[kBufSize]);
ASSERT_GT(read(err_fd, buf.get(), kBufSize), 0);
ASSERT_NE(static_cast<char*>(0), strstr(
buf.get(), "-----BEGIN BREAKPAD MICRODUMP-----"));
ASSERT_NE(static_cast<char*>(0), strstr(
buf.get(), "-----END BREAKPAD MICRODUMP-----"));
#ifdef __LP64__ #ifdef __LP64__
ASSERT_NE(static_cast<char*>(0), strstr( ASSERT_NE(static_cast<char*>(0), strstr(
@ -126,8 +140,25 @@ TEST(MicrodumpWriterTest, Setup) {
"33221100554477668899AABBCCDDEEFF0 libfoo.so")); "33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#endif #endif
close(err_fd); // In absence of a product info in the minidump, the writer should just write
close(fds[1]); // an unknown marker.
ASSERT_NE(static_cast<char*>(0), strstr(
buf.get(), "V UNKNOWN:0.0.0.0"));
}
// Ensure that the product info and build fingerprint metadata show up in the
// final microdump if present.
TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
scoped_array<char> buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kBuildFingerprint, kProductInfo, &buf);
ASSERT_NE(static_cast<char*>(0), strstr(buf.get(), kBuildFingerprint));
ASSERT_NE(static_cast<char*>(0), strstr(buf.get(), kProductInfo));
} }
} // namespace } // namespace