blob: 6339ac0cd83209c34ca45b78b3e03ba848ac875a [file] [log] [blame]
// Copyright (c) 2014 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <ctype.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <ucontext.h>
#include <sstream>
#include <string>
#include "breakpad_googletest_includes.h"
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/microdump_extra_info.h"
#include "client/linux/microdump_writer/microdump_writer.h"
#include "common/linux/breakpad_getcontext.h"
#include "common/linux/eintr_wrapper.h"
#include "common/linux/ignore_ret.h"
#include "common/scoped_ptr.h"
#include "common/tests/auto_tempdir.h"
#include "common/using_std_string.h"
using namespace google_breakpad;
extern "C" {
extern char __executable_start;
extern char __etext;
}
namespace {
typedef testing::Test MicrodumpWriterTest;
MicrodumpExtraInfo MakeMicrodumpExtraInfo(
const char* build_fingerprint,
const char* product_info,
const char* gpu_fingerprint) {
MicrodumpExtraInfo info;
info.build_fingerprint = build_fingerprint;
info.product_info = product_info;
info.gpu_fingerprint = gpu_fingerprint;
info.process_type = "Browser";
return info;
}
bool ContainsMicrodump(const std::string& buf) {
return std::string::npos != buf.find("-----BEGIN BREAKPAD MICRODUMP-----") &&
std::string::npos != buf.find("-----END BREAKPAD MICRODUMP-----");
}
const char kIdentifiableString[] = "_IDENTIFIABLE_";
const uintptr_t kCrashAddress = 0xdeaddeadu;
void CrashAndGetMicrodump(const MappingList& mappings,
const MicrodumpExtraInfo& microdump_extra_info,
std::string* microdump,
bool skip_dump_if_principal_mapping_not_referenced = false,
uintptr_t address_within_principal_mapping = 0,
bool sanitize_stack = false) {
int fds[2];
ASSERT_NE(-1, pipe(fds));
AutoTempDir temp_dir;
string stderr_file = temp_dir.path() + "/stderr.log";
int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ASSERT_NE(-1, err_fd);
char identifiable_string[sizeof(kIdentifiableString)];
// This string should not appear in the resulting microdump if it
// has been sanitized.
strcpy(identifiable_string, kIdentifiableString);
// Force the strcpy to not be optimized away.
IGNORE_RET(write(STDOUT_FILENO, identifiable_string, 0));
const pid_t child = fork();
if (child == 0) {
close(fds[1]);
char b;
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
close(fds[0]);
syscall(__NR_exit);
}
close(fds[0]);
ExceptionHandler::CrashContext context;
memset(&context, 0, sizeof(context));
// Pretend the current context is the child context (which is
// approximately right) so that we have a valid stack pointer, and
// can fetch child stack data via ptrace.
getcontext(&context.context);
// Set a non-zero tid to avoid tripping asserts.
context.tid = child;
context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED;
context.siginfo.si_addr = reinterpret_cast<void*>(kCrashAddress);
// 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,
skip_dump_if_principal_mapping_not_referenced,
address_within_principal_mapping, sanitize_stack,
microdump_extra_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);
microdump->clear();
char buf[1024];
while (true) {
int bytes_read = IGNORE_EINTR(read(err_fd, buf, 1024));
if (bytes_read <= 0) break;
microdump->append(buf, buf + bytes_read);
}
close(err_fd);
close(fds[1]);
}
void ExtractMicrodumpStackContents(const string& microdump_content,
string* result) {
std::istringstream iss(microdump_content);
result->clear();
for (string line; std::getline(iss, line);) {
if (line.find("S ") == 0) {
std::istringstream stack_data(line);
std::string key;
std::string addr;
std::string data;
stack_data >> key >> addr >> data;
EXPECT_TRUE((data.size() & 1u) == 0u);
result->reserve(result->size() + data.size() / 2);
for (size_t i = 0; i < data.size(); i += 2) {
std::string byte = data.substr(i, 2);
result->push_back(static_cast<char>(strtoul(byte.c_str(), NULL, 16)));
}
}
}
}
void CheckMicrodumpContents(const string& microdump_content,
const MicrodumpExtraInfo& expected_info) {
std::istringstream iss(microdump_content);
bool did_find_os_info = false;
bool did_find_product_info = false;
bool did_find_process_type = false;
bool did_find_crash_reason = false;
bool did_find_gpu_info = false;
for (string line; std::getline(iss, line);) {
if (line.find("O ") == 0) {
std::istringstream os_info_tokens(line);
string token;
os_info_tokens.ignore(2); // Ignore the "O " preamble.
// Check the OS descriptor char (L=Linux, A=Android).
os_info_tokens >> token;
ASSERT_TRUE(token == "L" || token == "A");
os_info_tokens >> token; // HW architecture.
os_info_tokens >> token; // Number of cpus.
for (size_t i = 0; i < token.size(); ++i)
ASSERT_TRUE(isxdigit(token[i]));
os_info_tokens >> token; // SW architecture.
// Check that the build fingerprint is in the right place.
os_info_tokens >> token;
ASSERT_FALSE(os_info_tokens.fail());
if (expected_info.build_fingerprint)
ASSERT_EQ(expected_info.build_fingerprint, token);
did_find_os_info = true;
} else if (line.find("P ") == 0) {
if (expected_info.process_type)
ASSERT_EQ(string("P ") + expected_info.process_type, line);
did_find_process_type = true;
} else if (line.find("R ") == 0) {
std::istringstream crash_reason_tokens(line);
string token;
unsigned crash_reason;
string crash_reason_str;
uintptr_t crash_address;
crash_reason_tokens.ignore(2); // Ignore the "R " preamble.
crash_reason_tokens >> std::hex >> crash_reason >> crash_reason_str >>
crash_address;
ASSERT_FALSE(crash_reason_tokens.fail());
ASSERT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, crash_reason);
ASSERT_EQ("DUMP_REQUESTED", crash_reason_str);
ASSERT_EQ(kCrashAddress, crash_address);
did_find_crash_reason = true;
} else if (line.find("V ") == 0) {
if (expected_info.product_info)
ASSERT_EQ(string("V ") + expected_info.product_info, line);
did_find_product_info = true;
} else if (line.find("G ") == 0) {
if (expected_info.gpu_fingerprint)
ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line);
did_find_gpu_info = true;
}
}
ASSERT_TRUE(did_find_os_info);
ASSERT_TRUE(did_find_product_info);
ASSERT_TRUE(did_find_process_type);
ASSERT_TRUE(did_find_crash_reason);
ASSERT_TRUE(did_find_gpu_info);
}
bool MicrodumpStackContains(const string& microdump_content,
const string& expected_content) {
string result;
ExtractMicrodumpStackContents(microdump_content, &result);
return result.find(kIdentifiableString) != string::npos;
}
void CheckMicrodumpContents(const string& microdump_content,
const string& expected_fingerprint,
const string& expected_product_info,
const string& expected_gpu_fingerprint) {
CheckMicrodumpContents(
microdump_content,
MakeMicrodumpExtraInfo(expected_fingerprint.c_str(),
expected_product_info.c_str(),
expected_gpu_fingerprint.c_str()));
}
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";
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
MappingInfo info;
info.start_addr = memory_size;
info.size = memory_size;
info.offset = 42;
strcpy(info.name, kMemoryName);
MappingList mappings;
MappingEntry mapping;
mapping.first = info;
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
mappings.push_back(mapping);
std::string buf;
CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
#ifdef __LP64__
ASSERT_NE(std::string::npos,
buf.find("M 0000000000001000 000000000000002A 0000000000001000 "
"33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#else
ASSERT_NE(std::string::npos,
buf.find("M 00001000 0000002A 00001000 "
"33221100554477668899AABBCCDDEEFF0 libfoo.so"));
#endif
// In absence of a product info in the minidump, the writer should just write
// an unknown marker.
ASSERT_NE(std::string::npos, buf.find("V UNKNOWN:0.0.0.0"));
}
// Ensure that no output occurs if the interest region is set, but
// doesn't overlap anything on the stack.
TEST(MicrodumpWriterTest, NoOutputIfUninteresting) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true, 0);
ASSERT_FALSE(ContainsMicrodump(buf));
}
// Ensure that stack content does not contain an identifiable string if the
// stack is sanitized.
TEST(MicrodumpWriterTest, StringRemovedBySanitization) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, true);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_FALSE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that stack content does contain an identifiable string if the
// stack is not sanitized.
TEST(MicrodumpWriterTest, StringPresentIfNotSanitized) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, false, 0u, false);
ASSERT_TRUE(ContainsMicrodump(buf));
ASSERT_TRUE(MicrodumpStackContains(buf, kIdentifiableString));
}
// Ensure that output occurs if the interest region is set, and
// does overlap something on the stack.
TEST(MicrodumpWriterTest, OutputIfInteresting) {
const char kProductInfo[] = "MockProduct:42.0.2311.99";
const char kBuildFingerprint[] =
"aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf, true,
reinterpret_cast<uintptr_t>(CrashAndGetMicrodump));
ASSERT_TRUE(ContainsMicrodump(buf));
}
// 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";
const char kGPUFingerprint[] =
"Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)";
const MicrodumpExtraInfo kMicrodumpExtraInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint));
std::string buf;
MappingList no_mappings;
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kMicrodumpExtraInfo);
}
TEST(MicrodumpWriterTest, NoProductInfo) {
const char kBuildFingerprint[] = "foobar";
const char kGPUFingerprint[] = "bazqux";
std::string buf;
MappingList no_mappings;
const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint));
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kBuildFingerprint, "UNKNOWN:0.0.0.0",
kGPUFingerprint);
}
TEST(MicrodumpWriterTest, NoGPUInfo) {
const char kProductInfo[] = "bazqux";
const char kBuildFingerprint[] = "foobar";
std::string buf;
MappingList no_mappings;
const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo(
MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL));
CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf);
ASSERT_TRUE(ContainsMicrodump(buf));
CheckMicrodumpContents(buf, kBuildFingerprint, kProductInfo, "UNKNOWN");
}
} // namespace