diff --git a/src/client/linux/handler/exception_handler.cc b/src/client/linux/handler/exception_handler.cc index e515a73b..dca7e7f0 100644 --- a/src/client/linux/handler/exception_handler.cc +++ b/src/client/linux/handler/exception_handler.cc @@ -570,10 +570,13 @@ void ExceptionHandler::WaitForContinueSignal() { bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, size_t context_size) { if (minidump_descriptor_.IsMicrodumpOnConsole()) { - return google_breakpad::WriteMicrodump(crashing_process, - context, - context_size, - mapping_list_); + return google_breakpad::WriteMicrodump( + crashing_process, + context, + context_size, + mapping_list_, + minidump_descriptor_.microdump_build_fingerprint(), + minidump_descriptor_.microdump_product_info()); } if (minidump_descriptor_.IsFD()) { return google_breakpad::WriteMinidump(minidump_descriptor_.fd(), diff --git a/src/client/linux/handler/minidump_descriptor.cc b/src/client/linux/handler/minidump_descriptor.cc index 8c68bde2..c601d35f 100644 --- a/src/client/linux/handler/minidump_descriptor.cc +++ b/src/client/linux/handler/minidump_descriptor.cc @@ -44,7 +44,9 @@ MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor) fd_(descriptor.fd_), directory_(descriptor.directory_), 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 // with a valid path_, as getting its c_path_ would require the heap which // can cause problems in compromised environments. @@ -65,6 +67,8 @@ MinidumpDescriptor& MinidumpDescriptor::operator=( UpdatePath(); } size_limit_ = descriptor.size_limit_; + microdump_build_fingerprint_ = descriptor.microdump_build_fingerprint_; + microdump_product_info_ = descriptor.microdump_product_info_; return *this; } @@ -82,4 +86,15 @@ void MinidumpDescriptor::UpdatePath() { 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 diff --git a/src/client/linux/handler/minidump_descriptor.h b/src/client/linux/handler/minidump_descriptor.h index ed656cee..3584c692 100644 --- a/src/client/linux/handler/minidump_descriptor.h +++ b/src/client/linux/handler/minidump_descriptor.h @@ -51,14 +51,18 @@ class MinidumpDescriptor { MinidumpDescriptor() : mode_(kUninitialized), fd_(-1), - size_limit_(-1) {} + size_limit_(-1), + microdump_build_fingerprint_(NULL), + microdump_product_info_(NULL) {} explicit MinidumpDescriptor(const string& directory) : mode_(kWriteMinidumpToFile), fd_(-1), directory_(directory), c_path_(NULL), - size_limit_(-1) { + size_limit_(-1), + microdump_build_fingerprint_(NULL), + microdump_product_info_(NULL) { assert(!directory.empty()); } @@ -66,14 +70,18 @@ class MinidumpDescriptor { : mode_(kWriteMinidumpToFd), fd_(fd), c_path_(NULL), - size_limit_(-1) { + size_limit_(-1), + microdump_build_fingerprint_(NULL), + microdump_product_info_(NULL) { assert(fd != -1); } explicit MinidumpDescriptor(const MicrodumpOnConsole&) : mode_(kWriteMicrodumpToConsole), fd_(-1), - size_limit_(-1) {} + size_limit_(-1), + microdump_build_fingerprint_(NULL), + microdump_product_info_(NULL) {} explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); @@ -99,6 +107,18 @@ class MinidumpDescriptor { off_t size_limit() const { return size_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: enum DumpMode { kUninitialized = 0, @@ -115,13 +135,25 @@ class MinidumpDescriptor { // The directory where the minidump should be generated. string directory_; + // The full path to the generated minidump. string path_; + // The C string of |path_|. Precomputed so it can be access from a compromised // context. const char* c_path_; 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 diff --git a/src/client/linux/microdump_writer/microdump_writer.cc b/src/client/linux/microdump_writer/microdump_writer.cc index e14f9754..f45925fe 100644 --- a/src/client/linux/microdump_writer/microdump_writer.cc +++ b/src/client/linux/microdump_writer/microdump_writer.cc @@ -60,6 +60,8 @@ class MicrodumpWriter { public: MicrodumpWriter(const ExceptionHandler::CrashContext* context, const MappingList& mappings, + const char* build_fingerprint, + const char* product_info, LinuxDumper* dumper) : ucontext_(context ? &context->context : NULL), #if !defined(__ARM_EABI__) && !defined(__mips__) @@ -67,6 +69,8 @@ class MicrodumpWriter { #endif dumper_(dumper), mapping_list_(mappings), + build_fingerprint_(build_fingerprint), + product_info_(product_info), log_line_(NULL) { log_line_ = reinterpret_cast(Alloc(kLineBufferSize)); if (log_line_) @@ -88,9 +92,9 @@ class MicrodumpWriter { bool Dump() { bool success; LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); - success = DumpOSInformation(); - if (success) - success = DumpCrashingThread(); + DumpProductInformation(); + DumpOSInformation(); + success = DumpCrashingThread(); if (success) success = DumpMappings(); LogLine("-----END BREAKPAD MICRODUMP-----"); @@ -143,10 +147,17 @@ class MicrodumpWriter { my_strlcpy(log_line_, "", kLineBufferSize); } - bool DumpOSInformation() { - struct utsname uts; - if (uname(&uts)) - return false; + void DumpProductInformation() { + LogAppend("V "); + if (product_info_) { + LogAppend(product_info_); + } else { + LogAppend("UNKNOWN:0.0.0.0"); + } + LogCommitLine(); + } + + void DumpOSInformation() { const uint8_t n_cpus = static_cast(sysconf(_SC_NPROCESSORS_CONF)); #if defined(__ANDROID__) @@ -178,13 +189,23 @@ class MicrodumpWriter { LogAppend(" "); LogAppend(n_cpus); LogAppend(" "); - LogAppend(uts.machine); - LogAppend(" "); - LogAppend(uts.release); - LogAppend(" "); - LogAppend(uts.version); + // If the client has attached a build fingerprint to the MinidumpDescriptor + // use that one. Otherwise try to get some basic info from uname(). + if (build_fingerprint_) { + LogAppend(build_fingerprint_); + } 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(); - return true; } bool DumpThreadStack(uint32_t thread_id, @@ -367,6 +388,8 @@ class MicrodumpWriter { #endif LinuxDumper* dumper_; const MappingList& mapping_list_; + const char* const build_fingerprint_; + const char* const product_info_; char* log_line_; }; } // namespace @@ -376,7 +399,9 @@ namespace google_breakpad { bool WriteMicrodump(pid_t crashing_process, const void* blob, size_t blob_size, - const MappingList& mappings) { + const MappingList& mappings, + const char* build_fingerprint, + const char* product_info) { LinuxPtraceDumper dumper(crashing_process); const ExceptionHandler::CrashContext* context = NULL; if (blob) { @@ -388,7 +413,8 @@ bool WriteMicrodump(pid_t crashing_process, dumper.set_crash_signal(context->siginfo.si_signo); dumper.set_crash_thread(context->tid); } - MicrodumpWriter writer(context, mappings, &dumper); + MicrodumpWriter writer(context, mappings, build_fingerprint, product_info, + &dumper); if (!writer.Init()) return false; return writer.Dump(); diff --git a/src/client/linux/microdump_writer/microdump_writer.h b/src/client/linux/microdump_writer/microdump_writer.h index 3c19f3d0..e2185583 100644 --- a/src/client/linux/microdump_writer/microdump_writer.h +++ b/src/client/linux/microdump_writer/microdump_writer.h @@ -46,12 +46,18 @@ namespace google_breakpad { // blob: a blob of data from the crashing process. See exception_handler.h // blob_size: the length of |blob| in bytes. // 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. bool WriteMicrodump(pid_t crashing_process, const void* blob, size_t blob_size, - const MappingList& mappings); + const MappingList& mappings, + const char* build_fingerprint, + const char* product_info); } // namespace google_breakpad diff --git a/src/client/linux/microdump_writer/microdump_writer_unittest.cc b/src/client/linux/microdump_writer/microdump_writer_unittest.cc index 52b1d9a9..1fa6f1ff 100644 --- a/src/client/linux/microdump_writer/microdump_writer_unittest.cc +++ b/src/client/linux/microdump_writer/microdump_writer_unittest.cc @@ -48,7 +48,11 @@ namespace { typedef testing::Test MicrodumpWriterTest; -TEST(MicrodumpWriterTest, Setup) { +void CrashAndGetMicrodump( + const MappingList& mappings, + const char* build_fingerprint, + const char* product_info, + scoped_array* buf) { int fds[2]; ASSERT_NE(-1, pipe(fds)); @@ -73,6 +77,36 @@ TEST(MicrodumpWriterTest, Setup) { // Set a non-zero tid to avoid tripping asserts. 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(0), strstr( + buf->get(), "-----BEGIN BREAKPAD MICRODUMP-----")); + ASSERT_NE(static_cast(0), strstr( + buf->get(), "-----END BREAKPAD MICRODUMP-----")); + +} + +TEST(MicrodumpWriterTest, BasicWithMappings) { // Push some extra mapping to check the MappingList logic. const uint32_t memory_size = sysconf(_SC_PAGESIZE); const char* kMemoryName = "libfoo.so"; @@ -93,28 +127,8 @@ TEST(MicrodumpWriterTest, Setup) { memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); mappings.push_back(mapping); - // 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)); - - // 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 buf(new char[kBufSize]); - ASSERT_GT(read(err_fd, buf.get(), kBufSize), 0); - - ASSERT_NE(static_cast(0), strstr( - buf.get(), "-----BEGIN BREAKPAD MICRODUMP-----")); - ASSERT_NE(static_cast(0), strstr( - buf.get(), "-----END BREAKPAD MICRODUMP-----")); + scoped_array buf; + CrashAndGetMicrodump(mappings, NULL, NULL, &buf); #ifdef __LP64__ ASSERT_NE(static_cast(0), strstr( @@ -126,8 +140,25 @@ TEST(MicrodumpWriterTest, Setup) { "33221100554477668899AABBCCDDEEFF0 libfoo.so")); #endif - close(err_fd); - close(fds[1]); + // In absence of a product info in the minidump, the writer should just write + // an unknown marker. + ASSERT_NE(static_cast(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 buf; + MappingList no_mappings; + + CrashAndGetMicrodump(no_mappings, kBuildFingerprint, kProductInfo, &buf); + + ASSERT_NE(static_cast(0), strstr(buf.get(), kBuildFingerprint)); + ASSERT_NE(static_cast(0), strstr(buf.get(), kProductInfo)); } } // namespace