mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-11-28 02:14:15 +01:00
allow passing info about known memory mappings to MinidumpWriter and ExceptionHandler
r=thestig at http://breakpad.appspot.com/242001/show git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@741 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
b5dfa2834d
commit
ef7262d477
@ -88,10 +88,12 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/linux/linux_libc_support.h"
|
#include "common/linux/linux_libc_support.h"
|
||||||
#include "common/memory.h"
|
#include "common/memory.h"
|
||||||
|
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
#include "common/linux/guid_creator.h"
|
#include "common/linux/guid_creator.h"
|
||||||
#include "common/linux/eintr_wrapper.h"
|
#include "common/linux/eintr_wrapper.h"
|
||||||
@ -449,8 +451,11 @@ void ExceptionHandler::WaitForContinueSignal() {
|
|||||||
// Runs on the cloned process.
|
// Runs on the cloned process.
|
||||||
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) {
|
||||||
return google_breakpad::WriteMinidump(
|
return google_breakpad::WriteMinidump(next_minidump_path_c_,
|
||||||
next_minidump_path_c_, crashing_process, context, context_size);
|
crashing_process,
|
||||||
|
context,
|
||||||
|
context_size,
|
||||||
|
mapping_list_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@ -482,4 +487,21 @@ bool ExceptionHandler::WriteMinidump() {
|
|||||||
#endif // !defined(__ARM_EABI__)
|
#endif // !defined(__ARM_EABI__)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExceptionHandler::AddMappingInfo(const std::string& name,
|
||||||
|
const u_int8_t identifier[sizeof(MDGUID)],
|
||||||
|
uintptr_t start_address,
|
||||||
|
size_t mapping_size,
|
||||||
|
size_t file_offset) {
|
||||||
|
MappingInfo info;
|
||||||
|
info.start_addr = start_address;
|
||||||
|
info.size = mapping_size;
|
||||||
|
info.offset = file_offset;
|
||||||
|
strncpy(info.name, name.c_str(), std::min(name.size(), sizeof(info)));
|
||||||
|
|
||||||
|
MappingEntry mapping;
|
||||||
|
mapping.first = info;
|
||||||
|
memcpy(mapping.second, identifier, sizeof(MDGUID));
|
||||||
|
mapping_list_.push_back(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
@ -30,17 +30,20 @@
|
|||||||
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
#ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
#if defined(__ANDROID__)
|
||||||
#include "client/linux/android_ucontext.h"
|
#include "client/linux/android_ucontext.h"
|
||||||
#endif
|
#endif
|
||||||
#include "client/linux/crash_generation/crash_generation_client.h"
|
#include "client/linux/crash_generation/crash_generation_client.h"
|
||||||
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
|
#include "google_breakpad/common/minidump_format.h"
|
||||||
#include "processor/scoped_ptr.h"
|
#include "processor/scoped_ptr.h"
|
||||||
|
|
||||||
struct sigaction;
|
struct sigaction;
|
||||||
@ -181,6 +184,15 @@ class ExceptionHandler {
|
|||||||
return crash_generation_client_.get() != NULL;
|
return crash_generation_client_.get() != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add information about a memory mapping. This can be used if
|
||||||
|
// a custom library loader is used that maps things in a way
|
||||||
|
// that the linux dumper can't handle by reading the maps file.
|
||||||
|
void AddMappingInfo(const std::string& name,
|
||||||
|
const u_int8_t identifier[sizeof(MDGUID)],
|
||||||
|
uintptr_t start_address,
|
||||||
|
size_t mapping_size,
|
||||||
|
size_t file_offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Init(const std::string &dump_path,
|
void Init(const std::string &dump_path,
|
||||||
const int server_fd);
|
const int server_fd);
|
||||||
@ -236,6 +248,10 @@ class ExceptionHandler {
|
|||||||
// cloned process after creating it, until we have explicitly enabled
|
// cloned process after creating it, until we have explicitly enabled
|
||||||
// ptrace. This is used to store the file descriptors for the pipe
|
// ptrace. This is used to store the file descriptors for the pipe
|
||||||
int fdes[2];
|
int fdes[2];
|
||||||
|
|
||||||
|
// Callers can add extra info about mappings for cases where the
|
||||||
|
// dumper code cannot extract enough information from /proc/<pid>/maps.
|
||||||
|
MappingList mapping_list_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
#include "client/linux/handler/exception_handler.h"
|
#include "client/linux/handler/exception_handler.h"
|
||||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
#include "common/linux/eintr_wrapper.h"
|
#include "common/linux/eintr_wrapper.h"
|
||||||
|
#include "common/linux/file_id.h"
|
||||||
#include "common/linux/linux_libc_support.h"
|
#include "common/linux/linux_libc_support.h"
|
||||||
#include "third_party/lss/linux_syscall_support.h"
|
#include "third_party/lss/linux_syscall_support.h"
|
||||||
#include "google_breakpad/processor/minidump.h"
|
#include "google_breakpad/processor/minidump.h"
|
||||||
@ -54,6 +55,10 @@ using namespace google_breakpad;
|
|||||||
#define TEMPDIR "/data/local/tmp"
|
#define TEMPDIR "/data/local/tmp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Length of a formatted GUID string =
|
||||||
|
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||||
|
const int kGUIDStringSize = 37;
|
||||||
|
|
||||||
static void sigchld_handler(int signo) { }
|
static void sigchld_handler(int signo) { }
|
||||||
|
|
||||||
class ExceptionHandlerTest : public ::testing::Test {
|
class ExceptionHandlerTest : public ::testing::Test {
|
||||||
@ -573,6 +578,87 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
|||||||
free(filename);
|
free(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool SimpleCallback(const char* dump_path,
|
||||||
|
const char* minidump_id,
|
||||||
|
void* context,
|
||||||
|
bool succeeded) {
|
||||||
|
if (!succeeded)
|
||||||
|
return succeeded;
|
||||||
|
|
||||||
|
string* minidump_file = reinterpret_cast<string*>(context);
|
||||||
|
minidump_file->append(dump_path);
|
||||||
|
minidump_file->append("/");
|
||||||
|
minidump_file->append(minidump_id);
|
||||||
|
minidump_file->append(".dmp");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that anonymous memory maps can be annotated with names and IDs.
|
||||||
|
TEST(ExceptionHandlerTest, ModuleInfo) {
|
||||||
|
// These are defined here so the parent can use them to check the
|
||||||
|
// data from the minidump afterwards.
|
||||||
|
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||||
|
const char* kMemoryName = "a fake module";
|
||||||
|
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||||
|
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
};
|
||||||
|
char module_identifier_buffer[kGUIDStringSize];
|
||||||
|
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||||
|
module_identifier_buffer,
|
||||||
|
sizeof(module_identifier_buffer));
|
||||||
|
string module_identifier(module_identifier_buffer);
|
||||||
|
// Strip out dashes
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||||
|
module_identifier.erase(pos, 1);
|
||||||
|
}
|
||||||
|
// And append a zero, because module IDs include an "age" field
|
||||||
|
// which is always zero on Linux.
|
||||||
|
module_identifier += "0";
|
||||||
|
|
||||||
|
// Get some memory.
|
||||||
|
char* memory =
|
||||||
|
reinterpret_cast<char*>(mmap(NULL,
|
||||||
|
kMemorySize,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0));
|
||||||
|
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||||
|
ASSERT_TRUE(memory);
|
||||||
|
|
||||||
|
string minidump_filename;
|
||||||
|
ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback,
|
||||||
|
(void*)&minidump_filename, true);
|
||||||
|
// Add info about the anonymous memory mapping.
|
||||||
|
handler.AddMappingInfo(kMemoryName,
|
||||||
|
kModuleGUID,
|
||||||
|
kMemoryAddress,
|
||||||
|
kMemorySize,
|
||||||
|
0);
|
||||||
|
handler.WriteMinidump();
|
||||||
|
|
||||||
|
// Read the minidump. Load the module list, and ensure that
|
||||||
|
// the mmap'ed |memory| is listed with the given module name
|
||||||
|
// and debug ID.
|
||||||
|
Minidump minidump(minidump_filename);
|
||||||
|
ASSERT_TRUE(minidump.Read());
|
||||||
|
|
||||||
|
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||||
|
ASSERT_TRUE(module_list);
|
||||||
|
const MinidumpModule* module =
|
||||||
|
module_list->GetModuleForAddress(kMemoryAddress);
|
||||||
|
ASSERT_TRUE(module);
|
||||||
|
|
||||||
|
EXPECT_EQ(kMemoryAddress, module->base_address());
|
||||||
|
EXPECT_EQ(kMemorySize, module->size());
|
||||||
|
EXPECT_EQ(kMemoryName, module->code_file());
|
||||||
|
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||||
|
|
||||||
|
unlink(minidump_filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
static const unsigned kControlMsgSize =
|
static const unsigned kControlMsgSize =
|
||||||
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred));
|
||||||
|
|
||||||
|
@ -104,12 +104,12 @@ static bool ResumeThread(pid_t pid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline static bool IsMappedFileOpenUnsafe(
|
inline static bool IsMappedFileOpenUnsafe(
|
||||||
const google_breakpad::MappingInfo* mapping) {
|
const google_breakpad::MappingInfo& mapping) {
|
||||||
// It is unsafe to attempt to open a mapped file that lives under /dev,
|
// It is unsafe to attempt to open a mapped file that lives under /dev,
|
||||||
// because the semantics of the open may be driver-specific so we'd risk
|
// because the semantics of the open may be driver-specific so we'd risk
|
||||||
// hanging the crash dumper. And a file in /dev/ almost certainly has no
|
// hanging the crash dumper. And a file in /dev/ almost certainly has no
|
||||||
// ELF file identifier anyways.
|
// ELF file identifier anyways.
|
||||||
return my_strncmp(mapping->name,
|
return my_strncmp(mapping.name,
|
||||||
kMappedFileUnsafePrefix,
|
kMappedFileUnsafePrefix,
|
||||||
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
|
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
|
||||||
}
|
}
|
||||||
@ -203,21 +203,21 @@ LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id,
|
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||||
|
unsigned int mapping_id,
|
||||||
uint8_t identifier[sizeof(MDGUID)])
|
uint8_t identifier[sizeof(MDGUID)])
|
||||||
{
|
{
|
||||||
assert(mapping_id < mappings_.size());
|
assert(mapping_id == -1 || mapping_id < mappings_.size());
|
||||||
my_memset(identifier, 0, sizeof(MDGUID));
|
my_memset(identifier, 0, sizeof(MDGUID));
|
||||||
MappingInfo* mapping = mappings_[mapping_id];
|
|
||||||
if (IsMappedFileOpenUnsafe(mapping))
|
if (IsMappedFileOpenUnsafe(mapping))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
char filename[NAME_MAX];
|
char filename[NAME_MAX];
|
||||||
size_t filename_len = my_strlen(mapping->name);
|
size_t filename_len = my_strlen(mapping.name);
|
||||||
assert(filename_len < NAME_MAX);
|
assert(filename_len < NAME_MAX);
|
||||||
if (filename_len >= NAME_MAX)
|
if (filename_len >= NAME_MAX)
|
||||||
return false;
|
return false;
|
||||||
memcpy(filename, mapping->name, filename_len);
|
memcpy(filename, mapping.name, filename_len);
|
||||||
filename[filename_len] = '\0';
|
filename[filename_len] = '\0';
|
||||||
bool filename_modified = HandleDeletedFileInMapping(filename);
|
bool filename_modified = HandleDeletedFileInMapping(filename);
|
||||||
|
|
||||||
@ -239,8 +239,11 @@ LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id,
|
|||||||
|
|
||||||
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
|
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
|
||||||
sys_munmap(base, st.st_size);
|
sys_munmap(base, st.st_size);
|
||||||
if (success && filename_modified)
|
if (success && mapping_id != -1 && filename_modified) {
|
||||||
mapping->name[filename_len - sizeof(kDeletedSuffix) + 1] = '\0';
|
mappings_[mapping_id]->name[filename_len -
|
||||||
|
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +313,7 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
|||||||
module->offset = offset;
|
module->offset = offset;
|
||||||
const char* name = NULL;
|
const char* name = NULL;
|
||||||
// Only copy name if the name is a valid path name, or if
|
// Only copy name if the name is a valid path name, or if
|
||||||
// we've found the VDSO image
|
// it's the VDSO image.
|
||||||
if ((name = my_strchr(line, '/')) != NULL) {
|
if ((name = my_strchr(line, '/')) != NULL) {
|
||||||
const unsigned l = my_strlen(name);
|
const unsigned l = my_strlen(name);
|
||||||
if (l < sizeof(module->name))
|
if (l < sizeof(module->name))
|
||||||
@ -500,7 +503,7 @@ const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LinuxDumper::HandleDeletedFileInMapping(char* path) {
|
bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
|
||||||
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
|
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
|
||||||
|
|
||||||
// Check for ' (deleted)' in |path|.
|
// Check for ' (deleted)' in |path|.
|
||||||
|
@ -151,8 +151,10 @@ class LinuxDumper {
|
|||||||
// without any slashes.
|
// without any slashes.
|
||||||
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||||
|
|
||||||
// Generate a File ID from the .text section of a mapped entry
|
// Generate a File ID from the .text section of a mapped entry.
|
||||||
bool ElfFileIdentifierForMapping(unsigned int mapping_id,
|
// mapping_id may be -1 if this is not a member of mappings_.
|
||||||
|
bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||||
|
unsigned int mapping_id,
|
||||||
uint8_t identifier[sizeof(MDGUID)]);
|
uint8_t identifier[sizeof(MDGUID)]);
|
||||||
|
|
||||||
// Utility method to find the location of where the kernel has
|
// Utility method to find the location of where the kernel has
|
||||||
@ -174,7 +176,7 @@ class LinuxDumper {
|
|||||||
// For programs that don't end with ' (deleted)', this is a no-op.
|
// For programs that don't end with ' (deleted)', this is a no-op.
|
||||||
// This assumes |path| is a buffer with length NAME_MAX.
|
// This assumes |path| is a buffer with length NAME_MAX.
|
||||||
// Returns true if |path| is modified.
|
// Returns true if |path| is modified.
|
||||||
bool HandleDeletedFileInMapping(char* path);
|
bool HandleDeletedFileInMapping(char* path) const;
|
||||||
|
|
||||||
const pid_t pid_;
|
const pid_t pid_;
|
||||||
|
|
||||||
|
@ -32,26 +32,19 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include "breakpad_googletest_includes.h"
|
#include "breakpad_googletest_includes.h"
|
||||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||||
|
#include "common/linux/eintr_wrapper.h"
|
||||||
#include "common/linux/file_id.h"
|
#include "common/linux/file_id.h"
|
||||||
#include "common/memory.h"
|
#include "common/memory.h"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using namespace google_breakpad;
|
using namespace google_breakpad;
|
||||||
|
|
||||||
// This provides a wrapper around system calls which may be
|
|
||||||
// interrupted by a signal and return EINTR. See man 7 signal.
|
|
||||||
#define HANDLE_EINTR(x) ({ \
|
|
||||||
typeof(x) __eintr_result__; \
|
|
||||||
do { \
|
|
||||||
__eintr_result__ = x; \
|
|
||||||
} while (__eintr_result__ == -1 && errno == EINTR); \
|
|
||||||
__eintr_result__;\
|
|
||||||
})
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
typedef testing::Test LinuxDumperTest;
|
typedef testing::Test LinuxDumperTest;
|
||||||
}
|
}
|
||||||
@ -88,8 +81,14 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
|||||||
char kNumberOfThreadsArgument[2];
|
char kNumberOfThreadsArgument[2];
|
||||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||||
|
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(-1, pipe(fds));
|
||||||
|
|
||||||
pid_t child_pid = fork();
|
pid_t child_pid = fork();
|
||||||
if (child_pid == 0) {
|
if (child_pid == 0) {
|
||||||
|
// In child process.
|
||||||
|
close(fds[0]);
|
||||||
|
|
||||||
// Locate helper binary next to the current binary.
|
// Locate helper binary next to the current binary.
|
||||||
char self_path[PATH_MAX];
|
char self_path[PATH_MAX];
|
||||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
||||||
@ -105,9 +104,12 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
|||||||
helper_path.erase(pos + 1);
|
helper_path.erase(pos + 1);
|
||||||
helper_path += "linux_dumper_unittest_helper";
|
helper_path += "linux_dumper_unittest_helper";
|
||||||
|
|
||||||
// Set the number of threads
|
// Pass the pipe fd and the number of threads as arguments.
|
||||||
|
char pipe_fd_string[8];
|
||||||
|
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||||
execl(helper_path.c_str(),
|
execl(helper_path.c_str(),
|
||||||
"linux_dumper_unittest_helper",
|
"linux_dumper_unittest_helper",
|
||||||
|
pipe_fd_string,
|
||||||
kNumberOfThreadsArgument,
|
kNumberOfThreadsArgument,
|
||||||
NULL);
|
NULL);
|
||||||
// Kill if we get here.
|
// Kill if we get here.
|
||||||
@ -115,9 +117,21 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
|||||||
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
|
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
// The sleep is flaky, but prevents us from reading
|
close(fds[1]);
|
||||||
// the child process before all threads have been created.
|
// Wait for the child process to signal that it's ready.
|
||||||
sleep(1);
|
struct pollfd pfd;
|
||||||
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
|
pfd.fd = fds[0];
|
||||||
|
pfd.events = POLLIN | POLLERR;
|
||||||
|
|
||||||
|
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
|
||||||
|
ASSERT_EQ(1, r);
|
||||||
|
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||||
|
uint8_t junk;
|
||||||
|
read(fds[0], &junk, sizeof(junk));
|
||||||
|
close(fds[0]);
|
||||||
|
|
||||||
|
// Child is ready now.
|
||||||
LinuxDumper dumper(child_pid);
|
LinuxDumper dumper(child_pid);
|
||||||
ASSERT_TRUE(dumper.Init());
|
ASSERT_TRUE(dumper.Init());
|
||||||
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
|
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
|
||||||
@ -240,7 +254,8 @@ TEST(LinuxDumperTest, FileIDsMatch) {
|
|||||||
|
|
||||||
uint8_t identifier1[sizeof(MDGUID)];
|
uint8_t identifier1[sizeof(MDGUID)];
|
||||||
uint8_t identifier2[sizeof(MDGUID)];
|
uint8_t identifier2[sizeof(MDGUID)];
|
||||||
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(i, identifier1));
|
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], i,
|
||||||
|
identifier1));
|
||||||
FileID fileid(exe_name);
|
FileID fileid(exe_name);
|
||||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
|
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
|
||||||
char identifier_string1[37];
|
char identifier_string1[37];
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
// id.
|
// id.
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
@ -58,7 +59,13 @@ void *thread_function(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int num_threads = atoi(argv[1]);
|
if (argc < 2) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int pipefd = atoi(argv[1]);
|
||||||
|
int num_threads = atoi(argv[2]);
|
||||||
if (num_threads < 1) {
|
if (num_threads < 1) {
|
||||||
fprintf(stderr, "ERROR: number of threads is 0");
|
fprintf(stderr, "ERROR: number of threads is 0");
|
||||||
return 1;
|
return 1;
|
||||||
@ -70,6 +77,9 @@ int main(int argc, char *argv[]) {
|
|||||||
for (int i = 1; i < num_threads; i++) {
|
for (int i = 1; i < num_threads; i++) {
|
||||||
pthread_create(&threads[i], &thread_attributes, &thread_function, NULL);
|
pthread_create(&threads[i], &thread_attributes, &thread_function, NULL);
|
||||||
}
|
}
|
||||||
|
// Signal parent that this process has started all threads.
|
||||||
|
uint8_t byte = 1;
|
||||||
|
write(pipefd, &byte, sizeof(byte));
|
||||||
thread_function(NULL);
|
thread_function(NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,8 @@ class MinidumpWriter {
|
|||||||
public:
|
public:
|
||||||
MinidumpWriter(const char* filename,
|
MinidumpWriter(const char* filename,
|
||||||
pid_t crashing_pid,
|
pid_t crashing_pid,
|
||||||
const ExceptionHandler::CrashContext* context)
|
const ExceptionHandler::CrashContext* context,
|
||||||
|
const MappingList& mappings)
|
||||||
: filename_(filename),
|
: filename_(filename),
|
||||||
siginfo_(&context->siginfo),
|
siginfo_(&context->siginfo),
|
||||||
ucontext_(&context->context),
|
ucontext_(&context->context),
|
||||||
@ -380,7 +381,8 @@ class MinidumpWriter {
|
|||||||
#endif
|
#endif
|
||||||
crashing_tid_(context->tid),
|
crashing_tid_(context->tid),
|
||||||
dumper_(crashing_pid),
|
dumper_(crashing_pid),
|
||||||
memory_blocks_(dumper_.allocator()) {
|
memory_blocks_(dumper_.allocator()),
|
||||||
|
mapping_list_(mappings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Init() {
|
bool Init() {
|
||||||
@ -752,17 +754,34 @@ class MinidumpWriter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is caller-provided information about this mapping
|
||||||
|
// in the mapping_list_ list, return true. Otherwise, return false.
|
||||||
|
bool HaveMappingInfo(const MappingInfo& mapping) {
|
||||||
|
for (MappingList::const_iterator iter = mapping_list_.begin();
|
||||||
|
iter != mapping_list_.end();
|
||||||
|
++iter) {
|
||||||
|
// Ignore any mappings that are wholly contained within
|
||||||
|
// mappings in the mapping_info_ list.
|
||||||
|
if (mapping.start_addr >= iter->first.start_addr &&
|
||||||
|
(mapping.start_addr + mapping.size) <=
|
||||||
|
(iter->first.start_addr + iter->first.size)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Write information about the mappings in effect. Because we are using the
|
// Write information about the mappings in effect. Because we are using the
|
||||||
// minidump format, the information about the mappings is pretty limited.
|
// minidump format, the information about the mappings is pretty limited.
|
||||||
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
||||||
// another stream in the file.
|
// another stream in the file.
|
||||||
bool WriteMappings(MDRawDirectory* dirent) {
|
bool WriteMappings(MDRawDirectory* dirent) {
|
||||||
const unsigned num_mappings = dumper_.mappings().size();
|
const unsigned num_mappings = dumper_.mappings().size();
|
||||||
unsigned num_output_mappings = 0;
|
unsigned num_output_mappings = mapping_list_.size();
|
||||||
|
|
||||||
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||||
if (ShouldIncludeMapping(mapping))
|
if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
|
||||||
num_output_mappings++;
|
num_output_mappings++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,59 +793,89 @@ class MinidumpWriter {
|
|||||||
dirent->location = list.location();
|
dirent->location = list.location();
|
||||||
*list.get() = num_output_mappings;
|
*list.get() = num_output_mappings;
|
||||||
|
|
||||||
for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
|
// First write all the mappings from the dumper
|
||||||
|
unsigned int j = 0;
|
||||||
|
for (unsigned i = 0; i < num_mappings; ++i) {
|
||||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||||
if (!ShouldIncludeMapping(mapping))
|
if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
MDRawModule mod;
|
MDRawModule mod;
|
||||||
my_memset(&mod, 0, MD_MODULE_SIZE);
|
if (!FillRawModule(mapping, i, mod, NULL))
|
||||||
mod.base_of_image = mapping.start_addr;
|
|
||||||
mod.size_of_image = mapping.size;
|
|
||||||
const size_t filepath_len = my_strlen(mapping.name);
|
|
||||||
|
|
||||||
// Figure out file name from path
|
|
||||||
const char* filename_ptr = mapping.name + filepath_len - 1;
|
|
||||||
while (filename_ptr >= mapping.name) {
|
|
||||||
if (*filename_ptr == '/')
|
|
||||||
break;
|
|
||||||
filename_ptr--;
|
|
||||||
}
|
|
||||||
filename_ptr++;
|
|
||||||
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
|
||||||
|
|
||||||
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
|
||||||
uint8_t* cv_ptr = cv_buf;
|
|
||||||
UntypedMDRVA cv(&minidump_writer_);
|
|
||||||
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
|
||||||
return false;
|
return false;
|
||||||
|
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
|
||||||
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
}
|
||||||
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
// Next write all the mappings provided by the caller
|
||||||
cv_ptr += sizeof(cv_signature);
|
for (MappingList::const_iterator iter = mapping_list_.begin();
|
||||||
uint8_t* signature = cv_ptr;
|
iter != mapping_list_.end();
|
||||||
cv_ptr += sizeof(MDGUID);
|
++iter) {
|
||||||
dumper_.ElfFileIdentifierForMapping(i, signature);
|
MDRawModule mod;
|
||||||
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
if (!FillRawModule(iter->first, -1, mod, iter->second))
|
||||||
cv_ptr += sizeof(uint32_t);
|
|
||||||
|
|
||||||
// Write pdb_file_name
|
|
||||||
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
|
||||||
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
|
||||||
|
|
||||||
mod.cv_record = cv.location();
|
|
||||||
|
|
||||||
MDLocationDescriptor ld;
|
|
||||||
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
|
||||||
return false;
|
return false;
|
||||||
mod.module_name_rva = ld.rva;
|
|
||||||
|
|
||||||
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
|
list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill the MDRawModule |mod| with information about the provided
|
||||||
|
// |mapping|. If |identifier| is non-NULL, use it instead of calculating
|
||||||
|
// a file ID from the mapping. |mapping_id| can be -1 if this mapping
|
||||||
|
// is not from the LinuxDumper.
|
||||||
|
bool FillRawModule(const MappingInfo& mapping,
|
||||||
|
unsigned int mapping_id,
|
||||||
|
MDRawModule& mod,
|
||||||
|
const u_int8_t* identifier) {
|
||||||
|
my_memset(&mod, 0, MD_MODULE_SIZE);
|
||||||
|
|
||||||
|
mod.base_of_image = mapping.start_addr;
|
||||||
|
mod.size_of_image = mapping.size;
|
||||||
|
const size_t filepath_len = my_strlen(mapping.name);
|
||||||
|
|
||||||
|
// Figure out file name from path
|
||||||
|
const char* filename_ptr = mapping.name + filepath_len - 1;
|
||||||
|
while (filename_ptr >= mapping.name) {
|
||||||
|
if (*filename_ptr == '/')
|
||||||
|
break;
|
||||||
|
filename_ptr--;
|
||||||
|
}
|
||||||
|
filename_ptr++;
|
||||||
|
|
||||||
|
const size_t filename_len = mapping.name + filepath_len - filename_ptr;
|
||||||
|
|
||||||
|
uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
|
||||||
|
uint8_t* cv_ptr = cv_buf;
|
||||||
|
UntypedMDRVA cv(&minidump_writer_);
|
||||||
|
if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
|
||||||
|
memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
|
||||||
|
cv_ptr += sizeof(cv_signature);
|
||||||
|
uint8_t* signature = cv_ptr;
|
||||||
|
cv_ptr += sizeof(MDGUID);
|
||||||
|
if (identifier) {
|
||||||
|
// GUID was provided by caller.
|
||||||
|
memcpy(signature, identifier, sizeof(MDGUID));
|
||||||
|
} else {
|
||||||
|
dumper_.ElfFileIdentifierForMapping(mapping, mapping_id, signature);
|
||||||
|
}
|
||||||
|
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||||
|
cv_ptr += sizeof(uint32_t);
|
||||||
|
|
||||||
|
// Write pdb_file_name
|
||||||
|
memcpy(cv_ptr, filename_ptr, filename_len + 1);
|
||||||
|
cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
|
||||||
|
|
||||||
|
mod.cv_record = cv.location();
|
||||||
|
|
||||||
|
MDLocationDescriptor ld;
|
||||||
|
if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
|
||||||
|
return false;
|
||||||
|
mod.module_name_rva = ld.rva;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool WriteMemoryListStream(MDRawDirectory* dirent) {
|
bool WriteMemoryListStream(MDRawDirectory* dirent) {
|
||||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||||
if (!list.AllocateObjectAndArray(memory_blocks_.size(),
|
if (!list.AllocateObjectAndArray(memory_blocks_.size(),
|
||||||
@ -1233,15 +1282,24 @@ class MinidumpWriter {
|
|||||||
// written while writing the thread list stream, but saved here
|
// written while writing the thread list stream, but saved here
|
||||||
// so a memory list stream can be written afterwards.
|
// so a memory list stream can be written afterwards.
|
||||||
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
|
wasteful_vector<MDMemoryDescriptor> memory_blocks_;
|
||||||
|
// Additional information about some mappings provided by the caller.
|
||||||
|
const MappingList& mapping_list_;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
const void* blob, size_t blob_size) {
|
const void* blob, size_t blob_size) {
|
||||||
|
MappingList m;
|
||||||
|
return WriteMinidump(filename, crashing_process, blob, blob_size, m);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
|
const void* blob, size_t blob_size,
|
||||||
|
const MappingList& mappings) {
|
||||||
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
if (blob_size != sizeof(ExceptionHandler::CrashContext))
|
||||||
return false;
|
return false;
|
||||||
const ExceptionHandler::CrashContext* context =
|
const ExceptionHandler::CrashContext* context =
|
||||||
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||||
MinidumpWriter writer(filename, crashing_process, context);
|
MinidumpWriter writer(filename, crashing_process, context, mappings);
|
||||||
if (!writer.Init())
|
if (!writer.Init())
|
||||||
return false;
|
return false;
|
||||||
return writer.Dump();
|
return writer.Dump();
|
||||||
|
@ -33,8 +33,17 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "google_breakpad/common/minidump_format.h"
|
||||||
|
|
||||||
namespace google_breakpad {
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
// A list of <MappingInfo, GUID>
|
||||||
|
typedef std::pair<struct MappingInfo, u_int8_t[sizeof(MDGUID)]> MappingEntry;
|
||||||
|
typedef std::list<MappingEntry> MappingList;
|
||||||
|
|
||||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||||
// libc functions which may. Thus, it can be used in contexts where the state
|
// libc functions which may. Thus, it can be used in contexts where the state
|
||||||
// of the heap may be corrupt.
|
// of the heap may be corrupt.
|
||||||
@ -48,6 +57,11 @@ namespace google_breakpad {
|
|||||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
const void* blob, size_t blob_size);
|
const void* blob, size_t blob_size);
|
||||||
|
|
||||||
|
// This overload also allows passing a list of known mappings.
|
||||||
|
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||||
|
const void* blob, size_t blob_size,
|
||||||
|
const MappingList& mappings);
|
||||||
|
|
||||||
} // namespace google_breakpad
|
} // namespace google_breakpad
|
||||||
|
|
||||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||||
|
@ -28,12 +28,17 @@
|
|||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
#include "breakpad_googletest_includes.h"
|
||||||
#include "client/linux/handler/exception_handler.h"
|
#include "client/linux/handler/exception_handler.h"
|
||||||
|
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||||
#include "common/linux/eintr_wrapper.h"
|
#include "common/linux/eintr_wrapper.h"
|
||||||
#include "breakpad_googletest_includes.h"
|
#include "common/linux/file_id.h"
|
||||||
|
#include "google_breakpad/processor/minidump.h"
|
||||||
|
|
||||||
using namespace google_breakpad;
|
using namespace google_breakpad;
|
||||||
|
|
||||||
@ -43,6 +48,10 @@ using namespace google_breakpad;
|
|||||||
#define TEMPDIR "/data/local/tmp"
|
#define TEMPDIR "/data/local/tmp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Length of a formatted GUID string =
|
||||||
|
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||||
|
const int kGUIDStringSize = 37;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
typedef testing::Test MinidumpWriterTest;
|
typedef testing::Test MinidumpWriterTest;
|
||||||
}
|
}
|
||||||
@ -76,3 +85,307 @@ TEST(MinidumpWriterTest, Setup) {
|
|||||||
|
|
||||||
close(fds[1]);
|
close(fds[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that mapping info can be specified when writing a minidump,
|
||||||
|
// and that it ends up in the module list of the minidump.
|
||||||
|
TEST(MinidumpWriterTest, MappingInfo) {
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(-1, pipe(fds));
|
||||||
|
|
||||||
|
// These are defined here so the parent can use them to check the
|
||||||
|
// data from the minidump afterwards.
|
||||||
|
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||||
|
const char* kMemoryName = "a fake module";
|
||||||
|
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||||
|
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
};
|
||||||
|
char module_identifier_buffer[kGUIDStringSize];
|
||||||
|
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||||
|
module_identifier_buffer,
|
||||||
|
sizeof(module_identifier_buffer));
|
||||||
|
string module_identifier(module_identifier_buffer);
|
||||||
|
// Strip out dashes
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||||
|
module_identifier.erase(pos, 1);
|
||||||
|
}
|
||||||
|
// And append a zero, because module IDs include an "age" field
|
||||||
|
// which is always zero on Linux.
|
||||||
|
module_identifier += "0";
|
||||||
|
|
||||||
|
// Get some memory.
|
||||||
|
char* memory =
|
||||||
|
reinterpret_cast<char*>(mmap(NULL,
|
||||||
|
kMemorySize,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0));
|
||||||
|
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||||
|
ASSERT_TRUE(memory);
|
||||||
|
|
||||||
|
const pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
close(fds[1]);
|
||||||
|
char b;
|
||||||
|
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));
|
||||||
|
context.tid = 1;
|
||||||
|
|
||||||
|
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||||
|
mktemp(templ);
|
||||||
|
|
||||||
|
// Add information about the mapped memory.
|
||||||
|
MappingInfo info;
|
||||||
|
info.start_addr = kMemoryAddress;
|
||||||
|
info.size = kMemorySize;
|
||||||
|
info.offset = 0;
|
||||||
|
strcpy(info.name, kMemoryName);
|
||||||
|
|
||||||
|
MappingList mappings;
|
||||||
|
MappingEntry mapping;
|
||||||
|
mapping.first = info;
|
||||||
|
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||||
|
mappings.push_back(mapping);
|
||||||
|
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
|
||||||
|
|
||||||
|
// Read the minidump. Load the module list, and ensure that
|
||||||
|
// the mmap'ed |memory| is listed with the given module name
|
||||||
|
// and debug ID.
|
||||||
|
Minidump minidump(templ);
|
||||||
|
ASSERT_TRUE(minidump.Read());
|
||||||
|
|
||||||
|
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||||
|
ASSERT_TRUE(module_list);
|
||||||
|
const MinidumpModule* module =
|
||||||
|
module_list->GetModuleForAddress(kMemoryAddress);
|
||||||
|
ASSERT_TRUE(module);
|
||||||
|
|
||||||
|
EXPECT_EQ(kMemoryAddress, module->base_address());
|
||||||
|
EXPECT_EQ(kMemorySize, module->size());
|
||||||
|
EXPECT_EQ(kMemoryName, module->code_file());
|
||||||
|
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||||
|
|
||||||
|
unlink(templ);
|
||||||
|
close(fds[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that mapping info can be specified, and that it overrides
|
||||||
|
// existing mappings that are wholly contained within the specified
|
||||||
|
// range.
|
||||||
|
TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(-1, pipe(fds));
|
||||||
|
|
||||||
|
// These are defined here so the parent can use them to check the
|
||||||
|
// data from the minidump afterwards.
|
||||||
|
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||||
|
const char* kMemoryName = "a fake module";
|
||||||
|
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||||
|
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||||
|
};
|
||||||
|
char module_identifier_buffer[kGUIDStringSize];
|
||||||
|
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||||
|
module_identifier_buffer,
|
||||||
|
sizeof(module_identifier_buffer));
|
||||||
|
string module_identifier(module_identifier_buffer);
|
||||||
|
// Strip out dashes
|
||||||
|
size_t pos;
|
||||||
|
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||||
|
module_identifier.erase(pos, 1);
|
||||||
|
}
|
||||||
|
// And append a zero, because module IDs include an "age" field
|
||||||
|
// which is always zero on Linux.
|
||||||
|
module_identifier += "0";
|
||||||
|
|
||||||
|
// mmap a file
|
||||||
|
char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
|
||||||
|
mktemp(tempfile);
|
||||||
|
int fd = open(tempfile, O_RDWR | O_CREAT, 0);
|
||||||
|
ASSERT_NE(-1, fd);
|
||||||
|
unlink(tempfile);
|
||||||
|
// fill with zeros
|
||||||
|
char buffer[kMemorySize];
|
||||||
|
memset(buffer, 0, kMemorySize);
|
||||||
|
ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
char* memory =
|
||||||
|
reinterpret_cast<char*>(mmap(NULL,
|
||||||
|
kMemorySize,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE,
|
||||||
|
fd,
|
||||||
|
0));
|
||||||
|
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||||
|
ASSERT_TRUE(memory);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
const pid_t child = fork();
|
||||||
|
if (child == 0) {
|
||||||
|
close(fds[1]);
|
||||||
|
char b;
|
||||||
|
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));
|
||||||
|
context.tid = 1;
|
||||||
|
|
||||||
|
char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||||
|
mktemp(dumpfile);
|
||||||
|
|
||||||
|
// Add information about the mapped memory. Report it as being larger than
|
||||||
|
// it actually is.
|
||||||
|
MappingInfo info;
|
||||||
|
info.start_addr = kMemoryAddress - kMemorySize;
|
||||||
|
info.size = kMemorySize * 3;
|
||||||
|
info.offset = 0;
|
||||||
|
strcpy(info.name, kMemoryName);
|
||||||
|
|
||||||
|
MappingList mappings;
|
||||||
|
MappingEntry mapping;
|
||||||
|
mapping.first = info;
|
||||||
|
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||||
|
mappings.push_back(mapping);
|
||||||
|
ASSERT_TRUE(WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
|
||||||
|
|
||||||
|
// Read the minidump. Load the module list, and ensure that
|
||||||
|
// the mmap'ed |memory| is listed with the given module name
|
||||||
|
// and debug ID.
|
||||||
|
Minidump minidump(dumpfile);
|
||||||
|
ASSERT_TRUE(minidump.Read());
|
||||||
|
|
||||||
|
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||||
|
ASSERT_TRUE(module_list);
|
||||||
|
const MinidumpModule* module =
|
||||||
|
module_list->GetModuleForAddress(kMemoryAddress);
|
||||||
|
ASSERT_TRUE(module);
|
||||||
|
|
||||||
|
EXPECT_EQ(info.start_addr, module->base_address());
|
||||||
|
EXPECT_EQ(info.size, module->size());
|
||||||
|
EXPECT_EQ(kMemoryName, module->code_file());
|
||||||
|
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||||
|
|
||||||
|
unlink(dumpfile);
|
||||||
|
close(fds[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MinidumpWriterTest, DeletedBinary) {
|
||||||
|
static const int kNumberOfThreadsInHelperProgram = 1;
|
||||||
|
char kNumberOfThreadsArgument[2];
|
||||||
|
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||||
|
|
||||||
|
// Locate helper binary next to the current binary.
|
||||||
|
char self_path[PATH_MAX];
|
||||||
|
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
||||||
|
FAIL() << "readlink failed: " << strerror(errno);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
string helper_path(self_path);
|
||||||
|
size_t pos = helper_path.rfind('/');
|
||||||
|
if (pos == string::npos) {
|
||||||
|
FAIL() << "no trailing slash in path: " << helper_path;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
helper_path.erase(pos + 1);
|
||||||
|
helper_path += "linux_dumper_unittest_helper";
|
||||||
|
|
||||||
|
// Copy binary to a temp file.
|
||||||
|
char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
|
||||||
|
mktemp(binpath);
|
||||||
|
char cmdline[2 * PATH_MAX];
|
||||||
|
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
|
||||||
|
ASSERT_EQ(0, system(cmdline));
|
||||||
|
ASSERT_EQ(0, chmod(binpath, 0755));
|
||||||
|
|
||||||
|
int fds[2];
|
||||||
|
ASSERT_NE(-1, pipe(fds));
|
||||||
|
|
||||||
|
pid_t child_pid = fork();
|
||||||
|
if (child_pid == 0) {
|
||||||
|
// In child process.
|
||||||
|
close(fds[0]);
|
||||||
|
|
||||||
|
// Pass the pipe fd and the number of threads as arguments.
|
||||||
|
char pipe_fd_string[8];
|
||||||
|
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||||
|
execl(binpath,
|
||||||
|
binpath,
|
||||||
|
pipe_fd_string,
|
||||||
|
kNumberOfThreadsArgument,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
close(fds[1]);
|
||||||
|
// Wait for the child process to signal that it's ready.
|
||||||
|
struct pollfd pfd;
|
||||||
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
|
pfd.fd = fds[0];
|
||||||
|
pfd.events = POLLIN | POLLERR;
|
||||||
|
|
||||||
|
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
|
||||||
|
ASSERT_EQ(1, r);
|
||||||
|
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||||
|
uint8_t junk;
|
||||||
|
read(fds[0], &junk, sizeof(junk));
|
||||||
|
close(fds[0]);
|
||||||
|
|
||||||
|
// Child is ready now.
|
||||||
|
// Unlink the test binary.
|
||||||
|
unlink(binpath);
|
||||||
|
|
||||||
|
ExceptionHandler::CrashContext context;
|
||||||
|
memset(&context, 0, sizeof(context));
|
||||||
|
|
||||||
|
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||||
|
mktemp(templ);
|
||||||
|
// Set a non-zero tid to avoid tripping asserts.
|
||||||
|
context.tid = 1;
|
||||||
|
ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
|
||||||
|
kill(child_pid, SIGKILL);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
ASSERT_EQ(stat(templ, &st), 0);
|
||||||
|
ASSERT_GT(st.st_size, 0u);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Minidump minidump(templ);
|
||||||
|
ASSERT_TRUE(minidump.Read());
|
||||||
|
|
||||||
|
// Check that the main module filename is correct.
|
||||||
|
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||||
|
ASSERT_TRUE(module_list);
|
||||||
|
const MinidumpModule* module = module_list->GetMainModule();
|
||||||
|
EXPECT_STREQ(binpath, module->code_file().c_str());
|
||||||
|
// Check that the file ID is correct.
|
||||||
|
FileID fileid(helper_path.c_str());
|
||||||
|
uint8_t identifier[sizeof(MDGUID)];
|
||||||
|
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
|
||||||
|
char identifier_string[kGUIDStringSize];
|
||||||
|
FileID::ConvertIdentifierToString(identifier,
|
||||||
|
identifier_string,
|
||||||
|
kGUIDStringSize);
|
||||||
|
string module_identifier(identifier_string);
|
||||||
|
// Strip out dashes
|
||||||
|
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||||
|
module_identifier.erase(pos, 1);
|
||||||
|
}
|
||||||
|
// And append a zero, because module IDs include an "age" field
|
||||||
|
// which is always zero on Linux.
|
||||||
|
module_identifier += "0";
|
||||||
|
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||||
|
|
||||||
|
unlink(templ);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user