mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-25 15:01:11 +02:00
* Updated breakpad to latest version.
This commit is contained in:
@@ -33,7 +33,11 @@
|
||||
#ifndef GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
||||
#define GOOGLE_BREAKPAD_CLIENT_LINUX_ANDROID_LINK_H_
|
||||
|
||||
#include <sys/exec_elf.h>
|
||||
// TODO(zhenghao): exec_elf.h conflicts with linux/elf.h.
|
||||
// But we still need ELFSIZE.
|
||||
//#include <sys/exec_elf.h>
|
||||
#include <machine/exec.h>
|
||||
#define ELFSIZE ARCH_ELFSIZE
|
||||
|
||||
#ifndef ElfW
|
||||
#define ElfW(type) _ElfW (Elf, ELFSIZE, type)
|
||||
|
@@ -47,6 +47,7 @@
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
|
||||
static const char kCommandQuit = 'x';
|
||||
|
||||
@@ -79,12 +80,10 @@ GetInodeForProcPath(ino_t* inode_out, const char* path)
|
||||
assert(inode_out);
|
||||
assert(path);
|
||||
|
||||
char buf[256];
|
||||
const ssize_t n = readlink(path, buf, sizeof(buf) - 1);
|
||||
if (n == -1) {
|
||||
char buf[PATH_MAX];
|
||||
if (!google_breakpad::SafeReadLink(path, buf)) {
|
||||
return false;
|
||||
}
|
||||
buf[n] = 0;
|
||||
|
||||
if (0 != memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) {
|
||||
return false;
|
||||
@@ -130,7 +129,7 @@ FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode)
|
||||
for (std::vector<pid_t>::const_iterator
|
||||
i = pids.begin(); i != pids.end(); ++i) {
|
||||
const pid_t current_pid = *i;
|
||||
char buf[256];
|
||||
char buf[PATH_MAX];
|
||||
snprintf(buf, sizeof(buf), "/proc/%d/fd", current_pid);
|
||||
DIR* fd = opendir(buf);
|
||||
if (!fd)
|
||||
|
@@ -73,19 +73,16 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/signal.h>
|
||||
#endif
|
||||
#include <sys/syscall.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
#include <sys/wait.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
@@ -93,6 +90,7 @@
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/memory.h"
|
||||
#include "client/linux/log/log.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/guid_creator.h"
|
||||
@@ -190,14 +188,18 @@ bool ExceptionHandler::InstallHandlers() {
|
||||
// such a small stack.
|
||||
static const unsigned kSigStackSize = 8192;
|
||||
|
||||
signal_stack = malloc(kSigStackSize);
|
||||
stack_t stack;
|
||||
memset(&stack, 0, sizeof(stack));
|
||||
stack.ss_sp = signal_stack;
|
||||
stack.ss_size = kSigStackSize;
|
||||
// Only set an alternative stack if there isn't already one, or if the current
|
||||
// one is too small.
|
||||
if (sys_sigaltstack(NULL, &stack) == -1 || !stack.ss_sp ||
|
||||
stack.ss_size < kSigStackSize) {
|
||||
memset(&stack, 0, sizeof(stack));
|
||||
stack.ss_sp = malloc(kSigStackSize);
|
||||
stack.ss_size = kSigStackSize;
|
||||
|
||||
if (sys_sigaltstack(&stack, NULL) == -1)
|
||||
return false;
|
||||
if (sys_sigaltstack(&stack, NULL) == -1)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
@@ -390,17 +392,23 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
|
||||
// is the write() and read() calls will fail with EBADF
|
||||
static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump \
|
||||
sys_pipe failed:";
|
||||
sys_write(2, no_pipe_msg, sizeof(no_pipe_msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1);
|
||||
logger::write(strerror(errno), strlen(strerror(errno)));
|
||||
logger::write("\n", 1);
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
const pid_t child = clone(
|
||||
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
&thread_arg);
|
||||
#else
|
||||
const pid_t child = sys_clone(
|
||||
ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED,
|
||||
&thread_arg, NULL, NULL, NULL);
|
||||
#endif
|
||||
int r, status;
|
||||
// Allow the child to ptrace us
|
||||
prctl(PR_SET_PTRACER, child, 0, 0, 0);
|
||||
sys_prctl(PR_SET_PTRACER, child);
|
||||
SendContinueSignalToChild();
|
||||
do {
|
||||
r = sys_waitpid(child, &status, __WALL);
|
||||
@@ -411,9 +419,9 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
|
||||
|
||||
if (r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
logger::write(msg, sizeof(msg) - 1);
|
||||
logger::write(strerror(errno), strlen(strerror(errno)));
|
||||
logger::write("\n", 1);
|
||||
}
|
||||
|
||||
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
||||
@@ -433,9 +441,9 @@ void ExceptionHandler::SendContinueSignalToChild() {
|
||||
if(r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::SendContinueSignalToChild \
|
||||
sys_write failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
logger::write(msg, sizeof(msg) - 1);
|
||||
logger::write(strerror(errno), strlen(strerror(errno)));
|
||||
logger::write("\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,9 +456,9 @@ void ExceptionHandler::WaitForContinueSignal() {
|
||||
if(r == -1) {
|
||||
static const char msg[] = "ExceptionHandler::WaitForContinueSignal \
|
||||
sys_read failed:";
|
||||
sys_write(2, msg, sizeof(msg) - 1);
|
||||
sys_write(2, strerror(errno), strlen(strerror(errno)));
|
||||
sys_write(2, "\n", 1);
|
||||
logger::write(msg, sizeof(msg) - 1);
|
||||
logger::write(strerror(errno), strlen(strerror(errno)));
|
||||
logger::write("\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +511,8 @@ void ExceptionHandler::AddMappingInfo(const std::string& name,
|
||||
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)));
|
||||
strncpy(info.name, name.c_str(), sizeof(info.name) - 1);
|
||||
info.name[sizeof(info.name) - 1] = '\0';
|
||||
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
|
@@ -228,7 +228,6 @@ class ExceptionHandler {
|
||||
const char* next_minidump_id_c_;
|
||||
|
||||
const bool handler_installed_;
|
||||
void* signal_stack; // the handler stack.
|
||||
HandlerCallback crash_handler_;
|
||||
|
||||
// The global exception handler stack. This is need becuase there may exist
|
||||
|
@@ -44,17 +44,12 @@
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/tests/auto_tempdir.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37;
|
||||
@@ -79,7 +74,8 @@ class ExceptionHandlerTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST(ExceptionHandlerTest, Simple) {
|
||||
ExceptionHandler handler(TEMPDIR, NULL, NULL, NULL, true);
|
||||
AutoTempDir temp_dir;
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, NULL, NULL, true);
|
||||
}
|
||||
|
||||
static bool DoneCallback(const char* dump_path,
|
||||
@@ -99,13 +95,14 @@ static bool DoneCallback(const char* dump_path,
|
||||
}
|
||||
|
||||
TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
AutoTempDir temp_dir;
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
*reinterpret_cast<volatile int*>(NULL) = 0;
|
||||
}
|
||||
@@ -133,7 +130,7 @@ TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
const std::string minidump_filename = temp_dir.path() + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -145,6 +142,7 @@ TEST(ExceptionHandlerTest, ChildCrash) {
|
||||
// Test that memory around the instruction pointer is written
|
||||
// to the dump as a MinidumpMemoryRegion.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
AutoTempDir temp_dir;
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
@@ -158,8 +156,8 @@ TEST(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, DoneCallback,
|
||||
(void*) fds[1], true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
@@ -206,7 +204,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
const std::string minidump_filename = temp_dir.path() + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -269,6 +267,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemory) {
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the low end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
AutoTempDir temp_dir;
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
@@ -282,8 +281,8 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, DoneCallback,
|
||||
(void*) fds[1], true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
@@ -330,7 +329,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
const std::string minidump_filename = temp_dir.path() + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -390,6 +389,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
|
||||
// Test that the memory region around the instruction pointer is
|
||||
// bounded correctly on the high end.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
AutoTempDir temp_dir;
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
@@ -406,8 +406,8 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, DoneCallback,
|
||||
(void*) fds[1], true);
|
||||
// Get some executable memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
@@ -454,7 +454,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
const std::string minidump_filename = temp_dir.path() + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -515,6 +515,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
|
||||
// Ensure that an extra memory block doesn't get added when the
|
||||
// instruction pointer is not in mapped memory.
|
||||
TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
||||
AutoTempDir temp_dir;
|
||||
int fds[2];
|
||||
ASSERT_NE(pipe(fds), -1);
|
||||
|
||||
@@ -522,8 +523,8 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[0]);
|
||||
ExceptionHandler handler(TEMPDIR, NULL, DoneCallback, (void*) fds[1],
|
||||
true);
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, DoneCallback,
|
||||
(void*) fds[1], true);
|
||||
// Try calling a NULL pointer.
|
||||
typedef void (*void_function)(void);
|
||||
void_function memory_function =
|
||||
@@ -554,7 +555,7 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
|
||||
filename[len] = 0;
|
||||
close(fds[0]);
|
||||
|
||||
const std::string minidump_filename = std::string(TEMPDIR) + "/" + filename +
|
||||
const std::string minidump_filename = temp_dir.path() + "/" + filename +
|
||||
".dmp";
|
||||
|
||||
struct stat st;
|
||||
@@ -625,11 +626,12 @@ TEST(ExceptionHandlerTest, ModuleInfo) {
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
string minidump_filename;
|
||||
ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback,
|
||||
AutoTempDir temp_dir;
|
||||
ExceptionHandler handler(temp_dir.path(), NULL, SimpleCallback,
|
||||
(void*)&minidump_filename, true);
|
||||
// Add info about the anonymous memory mapping.
|
||||
handler.AddMappingInfo(kMemoryName,
|
||||
@@ -667,7 +669,14 @@ CrashHandler(const void* crash_context, size_t crash_context_size,
|
||||
void* context) {
|
||||
const int fd = (intptr_t) context;
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
if (pipe(fds) == -1) {
|
||||
// There doesn't seem to be any way to reliably handle
|
||||
// this failure without the parent process hanging
|
||||
// At least make sure that this process doesn't access
|
||||
// unexpected file descriptors
|
||||
fds[0] = -1;
|
||||
fds[1] = -1;
|
||||
}
|
||||
struct kernel_msghdr msg = {0};
|
||||
struct kernel_iovec iov;
|
||||
iov.iov_base = const_cast<void*>(crash_context);
|
||||
@@ -734,6 +743,7 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
ASSERT_EQ(n, kCrashContextSize);
|
||||
ASSERT_EQ(msg.msg_controllen, kControlMsgSize);
|
||||
ASSERT_EQ(msg.msg_flags, 0);
|
||||
ASSERT_EQ(close(fds[0]), 0);
|
||||
|
||||
pid_t crashing_pid = -1;
|
||||
int signal_fd = -1;
|
||||
@@ -756,12 +766,13 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
ASSERT_NE(crashing_pid, -1);
|
||||
ASSERT_NE(signal_fd, -1);
|
||||
|
||||
char templ[] = TEMPDIR "/exception-handler-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
ASSERT_TRUE(WriteMinidump(templ, crashing_pid, context,
|
||||
AutoTempDir temp_dir;
|
||||
std::string templ = temp_dir.path() + "/exception-handler-unittest";
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), crashing_pid, context,
|
||||
kCrashContextSize));
|
||||
static const char b = 0;
|
||||
HANDLE_EINTR(write(signal_fd, &b, 1));
|
||||
ASSERT_EQ(close(signal_fd), 0);
|
||||
|
||||
int status;
|
||||
ASSERT_NE(HANDLE_EINTR(waitpid(child, &status, 0)), -1);
|
||||
@@ -769,7 +780,7 @@ TEST(ExceptionHandlerTest, ExternalDumper) {
|
||||
ASSERT_EQ(WTERMSIG(status), SIGSEGV);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_EQ(stat(templ.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
unlink(templ.c_str());
|
||||
}
|
||||
|
48
thirdparty/breakpad/client/linux/log/log.cc
vendored
Normal file
48
thirdparty/breakpad/client/linux/log/log.cc
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2012 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 "client/linux/log/log.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#else
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
#endif
|
||||
|
||||
namespace logger {
|
||||
|
||||
int write(const char* buf, size_t nbytes) {
|
||||
#if defined(__ANDROID__)
|
||||
return __android_log_write(ANDROID_LOG_WARN, "google-breakpad", buf);
|
||||
#else
|
||||
return sys_write(2, buf, nbytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace logger
|
41
thirdparty/breakpad/client/linux/log/log.h
vendored
Normal file
41
thirdparty/breakpad/client/linux/log/log.h
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
#ifndef CLIENT_LINUX_LOG_LOG_H_
|
||||
#define CLIENT_LINUX_LOG_LOG_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace logger {
|
||||
|
||||
int write(const char* buf, size_t nbytes);
|
||||
|
||||
} // namespace logger
|
||||
|
||||
#endif // CLIENT_LINUX_LOG_LOG_H_
|
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
@@ -70,16 +71,17 @@ TEST(LineReaderTest, EmptyFile) {
|
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\n", 2);
|
||||
const int r = HANDLE_EINTR(write(fd, "a\n", 2));
|
||||
ASSERT_EQ(2, r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned int len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned int)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned int)1, len);
|
||||
ASSERT_EQ('a', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
@@ -89,16 +91,17 @@ TEST(LineReaderTest, OneLineTerminated) {
|
||||
|
||||
TEST(LineReaderTest, OneLine) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a", 1);
|
||||
const int r = HANDLE_EINTR(write(fd, "a", 1));
|
||||
ASSERT_EQ(1, r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('a', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
@@ -108,22 +111,23 @@ TEST(LineReaderTest, OneLine) {
|
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb\n", 4);
|
||||
const int r = HANDLE_EINTR(write(fd, "a\nb\n", 4));
|
||||
ASSERT_EQ(4, r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('a', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('b', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
@@ -133,22 +137,23 @@ TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
|
||||
TEST(LineReaderTest, TwoLines) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb", 3);
|
||||
const int r = HANDLE_EINTR(write(fd, "a\nb", 3));
|
||||
ASSERT_EQ(3, r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'a');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('a', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('b', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
@@ -160,16 +165,17 @@ TEST(LineReaderTest, MaxLength) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen - 1];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
|
||||
ASSERT_EQ(sizeof(l), r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, sizeof(l));
|
||||
ASSERT_EQ(sizeof(l), len);
|
||||
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
|
||||
ASSERT_EQ(line[len], 0);
|
||||
ASSERT_EQ('\0', line[len]);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
@@ -178,7 +184,8 @@ TEST(LineReaderTest, TooLong) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
const int r = HANDLE_EINTR(write(fd, l, sizeof(l)));
|
||||
ASSERT_EQ(sizeof(l), r);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
|
234
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper.cc
vendored
Normal file
234
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper.cc
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper.
|
||||
// See linux_core_dumper.h for details.
|
||||
|
||||
#include "client/linux/minidump_writer/linux_core_dumper.h"
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <assert.h>
|
||||
#include <elf.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/procfs.h>
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
|
||||
const char* core_path,
|
||||
const char* procfs_path)
|
||||
: LinuxDumper(pid),
|
||||
core_path_(core_path),
|
||||
procfs_path_(procfs_path),
|
||||
thread_infos_(&allocator_, 8) {
|
||||
assert(core_path_);
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid,
|
||||
const char* node) const {
|
||||
if (!path || !node)
|
||||
return false;
|
||||
|
||||
size_t node_len = my_strlen(node);
|
||||
if (node_len == 0)
|
||||
return false;
|
||||
|
||||
size_t procfs_path_len = my_strlen(procfs_path_);
|
||||
size_t total_length = procfs_path_len + 1 + node_len;
|
||||
if (total_length >= NAME_MAX)
|
||||
return false;
|
||||
|
||||
memcpy(path, procfs_path_, procfs_path_len);
|
||||
path[procfs_path_len] = '/';
|
||||
memcpy(path + procfs_path_len + 1, node, node_len);
|
||||
path[total_length] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child,
|
||||
const void* src, size_t length) {
|
||||
ElfCoreDump::Addr virtual_address = reinterpret_cast<ElfCoreDump::Addr>(src);
|
||||
// TODO(benchan): Investigate whether the data to be copied could span
|
||||
// across multiple segments in the core dump file. ElfCoreDump::CopyData
|
||||
// and this method do not handle that case yet.
|
||||
if (!core_.CopyData(dest, virtual_address, length)) {
|
||||
// If the data segment is not found in the core dump, fill the result
|
||||
// with marker characters.
|
||||
memset(dest, 0xab, length);
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
|
||||
if (index >= thread_infos_.size())
|
||||
return false;
|
||||
|
||||
*info = thread_infos_[index];
|
||||
const uint8_t* stack_pointer;
|
||||
#if defined(__i386)
|
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#elif defined(__ARM_EABI__)
|
||||
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
|
||||
#else
|
||||
#error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
return GetStackInfo(&info->stack, &info->stack_len,
|
||||
reinterpret_cast<uintptr_t>(stack_pointer));
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::IsPostMortem() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::ThreadsSuspend() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::ThreadsResume() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxCoreDumper::EnumerateThreads() {
|
||||
if (!mapped_core_file_.Map(core_path_)) {
|
||||
fprintf(stderr, "Could not map core dump file into memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
core_.SetContent(mapped_core_file_.content());
|
||||
if (!core_.IsValid()) {
|
||||
fprintf(stderr, "Invalid core dump file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ElfCoreDump::Note note = core_.GetFirstNote();
|
||||
if (!note.IsValid()) {
|
||||
fprintf(stderr, "PT_NOTE section not found\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool first_thread = true;
|
||||
do {
|
||||
ElfCoreDump::Word type = note.GetType();
|
||||
MemoryRange name = note.GetName();
|
||||
MemoryRange description = note.GetDescription();
|
||||
|
||||
if (type == 0 || name.IsEmpty() || description.IsEmpty()) {
|
||||
fprintf(stderr, "Could not found a valid PT_NOTE.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are
|
||||
// ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific):
|
||||
// Thread Name Type
|
||||
// -------------------------------------------------------------------
|
||||
// 1st thread CORE NT_PRSTATUS
|
||||
// process-wide CORE NT_PRPSINFO
|
||||
// process-wide CORE NT_AUXV
|
||||
// 1st thread CORE NT_FPREGSET
|
||||
// 1st thread LINUX NT_PRXFPREG
|
||||
// 1st thread LINUX NT_386_TLS
|
||||
//
|
||||
// 2nd thread CORE NT_PRSTATUS
|
||||
// 2nd thread CORE NT_FPREGSET
|
||||
// 2nd thread LINUX NT_PRXFPREG
|
||||
// 2nd thread LINUX NT_386_TLS
|
||||
//
|
||||
// 3rd thread CORE NT_PRSTATUS
|
||||
// 3rd thread CORE NT_FPREGSET
|
||||
// 3rd thread LINUX NT_PRXFPREG
|
||||
// 3rd thread LINUX NT_386_TLS
|
||||
//
|
||||
// The following code only works if notes are ordered as expected.
|
||||
switch (type) {
|
||||
case NT_PRSTATUS: {
|
||||
if (description.length() != sizeof(elf_prstatus)) {
|
||||
fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const elf_prstatus* status =
|
||||
reinterpret_cast<const elf_prstatus*>(description.data());
|
||||
pid_t pid = status->pr_pid;
|
||||
ThreadInfo info;
|
||||
memset(&info, 0, sizeof(ThreadInfo));
|
||||
info.tgid = status->pr_pgrp;
|
||||
info.ppid = status->pr_ppid;
|
||||
memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
|
||||
if (first_thread) {
|
||||
crash_thread_ = pid;
|
||||
crash_signal_ = status->pr_info.si_signo;
|
||||
}
|
||||
first_thread = false;
|
||||
threads_.push_back(pid);
|
||||
thread_infos_.push_back(info);
|
||||
break;
|
||||
}
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
case NT_FPREGSET: {
|
||||
if (thread_infos_.empty())
|
||||
return false;
|
||||
|
||||
ThreadInfo* info = &thread_infos_.back();
|
||||
if (description.length() != sizeof(info->fpregs)) {
|
||||
fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&info->fpregs, description.data(), sizeof(info->fpregs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(__i386)
|
||||
case NT_PRXFPREG: {
|
||||
if (thread_infos_.empty())
|
||||
return false;
|
||||
|
||||
ThreadInfo* info = &thread_infos_.back();
|
||||
if (description.length() != sizeof(info->fpxregs)) {
|
||||
fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
note = note.GetNextNote();
|
||||
} while (note.IsValid());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
122
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper.h
vendored
Normal file
122
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper.h
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper
|
||||
// class, which is derived from google_breakpad::LinuxDumper to extract
|
||||
// information from a crashed process via its core dump and proc files.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "common/linux/elf_core_dump.h"
|
||||
#include "common/linux/memory_mapped_file.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class LinuxCoreDumper : public LinuxDumper {
|
||||
public:
|
||||
// Constructs a dumper for extracting information of a given process
|
||||
// with a process ID of |pid| via its core dump file at |core_path| and
|
||||
// its proc files at |procfs_path|. If |procfs_path| is a copy of
|
||||
// /proc/<pid>, it should contain the following files:
|
||||
// auxv, cmdline, environ, exe, maps, status
|
||||
LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path);
|
||||
|
||||
// Implements LinuxDumper::BuildProcPath().
|
||||
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
|
||||
// |path| is a character array of at least NAME_MAX bytes to return the
|
||||
// result.|node| is the final node without any slashes. Return true on
|
||||
// success.
|
||||
//
|
||||
// As this dumper performs a post-mortem dump and makes use of a copy
|
||||
// of the proc files of the crashed process, this derived method does
|
||||
// not actually make use of |pid| and always returns a subpath of
|
||||
// |procfs_path_| regardless of whether |pid| corresponds to the main
|
||||
// process or a thread of the process, i.e. assuming both the main process
|
||||
// and its threads have the following proc files with the same content:
|
||||
// auxv, cmdline, environ, exe, maps, status
|
||||
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||
|
||||
// Implements LinuxDumper::CopyFromProcess().
|
||||
// Copies content of |length| bytes from a given process |child|,
|
||||
// starting from |src|, into |dest|. This method extracts the content
|
||||
// the core dump and fills |dest| with a sequence of marker bytes
|
||||
// if the expected data is not found in the core dump.
|
||||
virtual void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length);
|
||||
|
||||
// Implements LinuxDumper::GetThreadInfoByIndex().
|
||||
// Reads information about the |index|-th thread of |threads_|.
|
||||
// Returns true on success. One must have called |ThreadsSuspend| first.
|
||||
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
|
||||
|
||||
// Implements LinuxDumper::IsPostMortem().
|
||||
// Always returns true to indicate that this dumper performs a
|
||||
// post-mortem dump of a crashed process via a core dump file.
|
||||
virtual bool IsPostMortem() const;
|
||||
|
||||
// Implements LinuxDumper::ThreadsSuspend().
|
||||
// As the dumper performs a post-mortem dump via a core dump file,
|
||||
// there is no threads to suspend. This method does nothing and
|
||||
// always returns true.
|
||||
virtual bool ThreadsSuspend();
|
||||
|
||||
// Implements LinuxDumper::ThreadsResume().
|
||||
// As the dumper performs a post-mortem dump via a core dump file,
|
||||
// there is no threads to resume. This method does nothing and
|
||||
// always returns true.
|
||||
virtual bool ThreadsResume();
|
||||
|
||||
protected:
|
||||
// Implements LinuxDumper::EnumerateThreads().
|
||||
// Enumerates all threads of the given process into |threads_|.
|
||||
virtual bool EnumerateThreads();
|
||||
|
||||
private:
|
||||
// Path of the core dump file.
|
||||
const char* core_path_;
|
||||
|
||||
// Path of the directory containing the proc files of the given process,
|
||||
// which is usually a copy of /proc/<pid>.
|
||||
const char* procfs_path_;
|
||||
|
||||
// Memory-mapped core dump file at |core_path_|.
|
||||
MemoryMappedFile mapped_core_file_;
|
||||
|
||||
// Content of the core dump file.
|
||||
ElfCoreDump core_;
|
||||
|
||||
// Thread info found in the core dump file.
|
||||
wasteful_vector<ThreadInfo> thread_infos_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_CORE_DUMPER_H_
|
109
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper_unittest.cc
vendored
Normal file
109
thirdparty/breakpad/client/linux/minidump_writer/linux_core_dumper_unittest.cc
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
// linux_core_dumper_unittest.cc:
|
||||
// Unit tests for google_breakpad::LinuxCoreDumoer.
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_core_dumper.h"
|
||||
#include "common/linux/tests/crash_generator.h"
|
||||
|
||||
using std::string;
|
||||
using namespace google_breakpad;
|
||||
|
||||
TEST(LinuxCoreDumperTest, BuildProcPath) {
|
||||
const pid_t pid = getpid();
|
||||
const char procfs_path[] = "/procfs_copy";
|
||||
LinuxCoreDumper dumper(getpid(), "core_file", procfs_path);
|
||||
|
||||
char maps_path[NAME_MAX] = "";
|
||||
char maps_path_expected[NAME_MAX];
|
||||
snprintf(maps_path_expected, sizeof(maps_path_expected),
|
||||
"%s/maps", procfs_path);
|
||||
EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
|
||||
EXPECT_STREQ(maps_path_expected, maps_path);
|
||||
|
||||
EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
|
||||
|
||||
char long_node[NAME_MAX];
|
||||
size_t long_node_len = NAME_MAX - strlen(procfs_path) - 1;
|
||||
memset(long_node, 'a', long_node_len);
|
||||
long_node[long_node_len] = '\0';
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, long_node));
|
||||
}
|
||||
|
||||
TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) {
|
||||
CrashGenerator crash_generator;
|
||||
if (!crash_generator.HasDefaultCorePattern()) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
|
||||
"is skipped due to non-default core pattern\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned kNumOfThreads = 3;
|
||||
const unsigned kCrashThread = 1;
|
||||
const int kCrashSignal = SIGABRT;
|
||||
pid_t child_pid;
|
||||
// TODO(benchan): Revert to use ASSERT_TRUE once the flakiness in
|
||||
// CrashGenerator is identified and fixed.
|
||||
if (!crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
|
||||
kCrashSignal, &child_pid)) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
|
||||
"is skipped due to no core dump generated\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pid_t pid = getpid();
|
||||
const string core_file = crash_generator.GetCoreFilePath();
|
||||
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
|
||||
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
|
||||
dumper.Init();
|
||||
|
||||
EXPECT_TRUE(dumper.IsPostMortem());
|
||||
|
||||
// These are no-ops and should always return true.
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
|
||||
// LinuxCoreDumper cannot determine the crash address and thus it always
|
||||
// sets the crash address to 0.
|
||||
EXPECT_EQ(0, dumper.crash_address());
|
||||
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
|
||||
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
|
||||
dumper.crash_thread());
|
||||
|
||||
EXPECT_EQ(kNumOfThreads, dumper.threads().size());
|
||||
for (unsigned i = 0; i < kNumOfThreads; ++i) {
|
||||
ThreadInfo info;
|
||||
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info));
|
||||
EXPECT_EQ(getpid(), info.ppid);
|
||||
}
|
||||
}
|
@@ -27,6 +27,9 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// linux_dumper.cc: Implement google_breakpad::LinuxDumper.
|
||||
// See linux_dumper.h for details.
|
||||
|
||||
// This code deals with the mechanics of getting information about a crashed
|
||||
// process. Since this code may run in a compromised address space, the same
|
||||
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
|
||||
@@ -34,75 +37,22 @@
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <link.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "common/linux/memory_mapped_file.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
static const char kMappedFileUnsafePrefix[] = "/dev/";
|
||||
static const char kDeletedSuffix[] = " (deleted)";
|
||||
|
||||
// Suspend a thread by attaching to it.
|
||||
static bool SuspendThread(pid_t pid) {
|
||||
// This may fail if the thread has just died or debugged.
|
||||
errno = 0;
|
||||
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
||||
errno != 0) {
|
||||
return false;
|
||||
}
|
||||
while (sys_waitpid(pid, NULL, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
// On x86, the stack pointer is NULL or -1, when executing trusted code in
|
||||
// the seccomp sandbox. Not only does this cause difficulties down the line
|
||||
// when trying to dump the thread's stack, it also results in the minidumps
|
||||
// containing information about the trusted threads. This information is
|
||||
// generally completely meaningless and just pollutes the minidumps.
|
||||
// We thus test the stack pointer and exclude any threads that are part of
|
||||
// the seccomp sandbox's trusted code.
|
||||
user_regs_struct regs;
|
||||
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 ||
|
||||
#if defined(__i386)
|
||||
!regs.esp
|
||||
#elif defined(__x86_64)
|
||||
!regs.rsp
|
||||
#endif
|
||||
) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resume a thread by detaching from it.
|
||||
static bool ResumeThread(pid_t pid) {
|
||||
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||
}
|
||||
|
||||
inline static bool IsMappedFileOpenUnsafe(
|
||||
const google_breakpad::MappingInfo& mapping) {
|
||||
// It is unsafe to attempt to open a mapped file that lives under /dev,
|
||||
@@ -116,90 +66,20 @@ inline static bool IsMappedFileOpenUnsafe(
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxDumper::LinuxDumper(int pid)
|
||||
LinuxDumper::LinuxDumper(pid_t pid)
|
||||
: pid_(pid),
|
||||
threads_suspended_(false),
|
||||
crash_address_(0),
|
||||
crash_signal_(0),
|
||||
crash_thread_(0),
|
||||
threads_(&allocator_, 8),
|
||||
mappings_(&allocator_) {
|
||||
}
|
||||
|
||||
LinuxDumper::~LinuxDumper() {
|
||||
}
|
||||
|
||||
bool LinuxDumper::Init() {
|
||||
return EnumerateThreads(&threads_) &&
|
||||
EnumerateMappings(&mappings_);
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsSuspend() {
|
||||
if (threads_suspended_)
|
||||
return true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
if (!SuspendThread(threads_[i])) {
|
||||
// If the thread either disappeared before we could attach to it, or if
|
||||
// it was part of the seccomp sandbox's trusted code, it is OK to
|
||||
// silently drop it from the minidump.
|
||||
memmove(&threads_[i], &threads_[i+1],
|
||||
(threads_.size() - i - 1) * sizeof(threads_[i]));
|
||||
threads_.resize(threads_.size() - 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
threads_suspended_ = true;
|
||||
return threads_.size() > 0;
|
||||
}
|
||||
|
||||
bool LinuxDumper::ThreadsResume() {
|
||||
if (!threads_suspended_)
|
||||
return false;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= ResumeThread(threads_[i]);
|
||||
threads_suspended_ = false;
|
||||
return good;
|
||||
}
|
||||
|
||||
void
|
||||
LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
|
||||
assert(path);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
path[0] = '\0';
|
||||
|
||||
const unsigned pid_len = my_int_len(pid);
|
||||
|
||||
assert(node);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t node_len = my_strlen(node);
|
||||
assert(node_len < NAME_MAX);
|
||||
if (node_len >= NAME_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(node_len > 0);
|
||||
if (node_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(pid > 0);
|
||||
if (pid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t total_length = 6 + pid_len + 1 + node_len;
|
||||
|
||||
assert(total_length < NAME_MAX);
|
||||
if (total_length >= NAME_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(path, "/proc/", 6);
|
||||
my_itos(path + 6, pid, pid_len);
|
||||
memcpy(path + 6 + pid_len, "/", 1);
|
||||
memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||
path[total_length] = '\0';
|
||||
return EnumerateThreads() && EnumerateMappings();
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -237,24 +117,12 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
filename[filename_len] = '\0';
|
||||
bool filename_modified = HandleDeletedFileInMapping(filename);
|
||||
|
||||
int fd = sys_open(filename, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
struct kernel_stat st;
|
||||
if (sys_fstat(fd, &st) != 0) {
|
||||
sys_close(fd);
|
||||
return false;
|
||||
}
|
||||
#if defined(__x86_64)
|
||||
#define sys_mmap2 sys_mmap
|
||||
#endif
|
||||
void* base = sys_mmap2(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
sys_close(fd);
|
||||
if (base == MAP_FAILED)
|
||||
MemoryMappedFile mapped_file(filename);
|
||||
if (!mapped_file.data()) // Should probably check if size >= ElfW(Ehdr)?
|
||||
return false;
|
||||
|
||||
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
|
||||
sys_munmap(base, st.st_size);
|
||||
bool success =
|
||||
FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
|
||||
if (success && member && filename_modified) {
|
||||
mappings_[mapping_id]->name[filename_len -
|
||||
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||
@@ -264,12 +132,10 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
}
|
||||
|
||||
void*
|
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(pid_t pid) const {
|
||||
char auxv_path[NAME_MAX];
|
||||
BuildProcPath(auxv_path, pid, "auxv");
|
||||
|
||||
// If BuildProcPath errors out due to invalid input, we'll handle it when
|
||||
// we try to sys_open the file.
|
||||
if (!BuildProcPath(auxv_path, pid, "auxv"))
|
||||
return NULL;
|
||||
|
||||
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
|
||||
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||
@@ -293,10 +159,36 @@ LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
void*
|
||||
LinuxDumper::FindEntryPoint(pid_t pid) const {
|
||||
char auxv_path[NAME_MAX];
|
||||
if (!BuildProcPath(auxv_path, pid, "auxv"))
|
||||
return NULL;
|
||||
|
||||
int fd = sys_open(auxv_path, O_RDONLY, 0);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find the AT_ENTRY entry
|
||||
elf_aux_entry one_aux_entry;
|
||||
while (sys_read(fd,
|
||||
&one_aux_entry,
|
||||
sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
|
||||
one_aux_entry.a_type != AT_NULL) {
|
||||
if (one_aux_entry.a_type == AT_ENTRY) {
|
||||
close(fd);
|
||||
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool LinuxDumper::EnumerateMappings() {
|
||||
char maps_path[NAME_MAX];
|
||||
BuildProcPath(maps_path, pid_, "maps");
|
||||
if (!BuildProcPath(maps_path, pid_, "maps"))
|
||||
return false;
|
||||
|
||||
// linux_gate_loc is the beginning of the kernel's mapping of
|
||||
// linux-gate.so in the process. It doesn't actually show up in the
|
||||
@@ -305,6 +197,10 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
// of mappings.
|
||||
const void* linux_gate_loc;
|
||||
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
|
||||
// Although the initial executable is usually the first mapping, it's not
|
||||
// guaranteed (see http://crosbug.com/25355); therefore, try to use the
|
||||
// actual entry point to find the mapping.
|
||||
const void* entry_point_loc = FindEntryPoint(pid_);
|
||||
|
||||
const int fd = sys_open(maps_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
@@ -333,8 +229,8 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
}
|
||||
// Merge adjacent mappings with the same name into one module,
|
||||
// assuming they're a single library mapped by the dynamic linker
|
||||
if (name && result->size()) {
|
||||
MappingInfo* module = (*result)[result->size() - 1];
|
||||
if (name && !mappings_.empty()) {
|
||||
MappingInfo* module = mappings_.back();
|
||||
if ((start_addr == module->start_addr + module->size) &&
|
||||
(my_strlen(name) == my_strlen(module->name)) &&
|
||||
(my_strncmp(name, module->name, my_strlen(name)) == 0)) {
|
||||
@@ -353,7 +249,25 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
if (l < sizeof(module->name))
|
||||
memcpy(module->name, name, l);
|
||||
}
|
||||
result->push_back(module);
|
||||
// If this is the entry-point mapping, and it's not already the
|
||||
// first one, then we need to make it be first. This is because
|
||||
// the minidump format assumes the first module is the one that
|
||||
// corresponds to the main executable (as codified in
|
||||
// processor/minidump.cc:MinidumpModuleList::GetMainModule()).
|
||||
if (entry_point_loc &&
|
||||
(entry_point_loc >=
|
||||
reinterpret_cast<void*>(module->start_addr)) &&
|
||||
(entry_point_loc <
|
||||
reinterpret_cast<void*>(module->start_addr+module->size)) &&
|
||||
!mappings_.empty()) {
|
||||
// push the module onto the front of the list.
|
||||
mappings_.resize(mappings_.size() + 1);
|
||||
for (size_t idx = mappings_.size() - 1; idx > 0; idx--)
|
||||
mappings_[idx] = mappings_[idx - 1];
|
||||
mappings_[0] = module;
|
||||
} else {
|
||||
mappings_.push_back(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,114 +276,7 @@ LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
|
||||
sys_close(fd);
|
||||
|
||||
return result->size() > 0;
|
||||
}
|
||||
|
||||
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||
// pid.
|
||||
bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
|
||||
char task_path[NAME_MAX];
|
||||
BuildProcPath(task_path, pid_, "task");
|
||||
|
||||
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
|
||||
|
||||
// The directory may contain duplicate entries which we filter by assuming
|
||||
// that they are consecutive.
|
||||
int last_tid = -1;
|
||||
const char* dent_name;
|
||||
while (dir_reader->GetNextEntry(&dent_name)) {
|
||||
if (my_strcmp(dent_name, ".") &&
|
||||
my_strcmp(dent_name, "..")) {
|
||||
int tid = 0;
|
||||
if (my_strtoui(&tid, dent_name) &&
|
||||
last_tid != tid) {
|
||||
last_tid = tid;
|
||||
result->push_back(tid);
|
||||
}
|
||||
}
|
||||
dir_reader->PopEntry();
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
|
||||
// these members are set to -1. Returns true iff all three members are
|
||||
// available.
|
||||
bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
|
||||
assert(info != NULL);
|
||||
char status_path[NAME_MAX];
|
||||
BuildProcPath(status_path, tid, "status");
|
||||
|
||||
const int fd = open(status_path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
|
||||
info->ppid = info->tgid = -1;
|
||||
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
if (my_strncmp("Tgid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->tgid, line + 6);
|
||||
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->ppid, line + 6);
|
||||
}
|
||||
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
|
||||
if (info->ppid == -1 || info->tgid == -1)
|
||||
return false;
|
||||
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__i386)
|
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
|
||||
if (sys_ptrace(
|
||||
PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*> (offsetof(struct user,
|
||||
u_debugreg[0]) + i *
|
||||
sizeof(debugreg_t)),
|
||||
&info->dregs[i]) == -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint8_t* stack_pointer;
|
||||
#if defined(__i386)
|
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#elif defined(__ARM_EABI__)
|
||||
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
|
||||
#else
|
||||
#error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
return GetStackInfo(&info->stack, &info->stack_len,
|
||||
(uintptr_t) stack_pointer);
|
||||
return !mappings_.empty();
|
||||
}
|
||||
|
||||
// Get information about the stack, given the stack pointer. We don't try to
|
||||
@@ -498,25 +305,6 @@ bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length) {
|
||||
unsigned long tmp = 55;
|
||||
size_t done = 0;
|
||||
static const size_t word_size = sizeof(tmp);
|
||||
uint8_t* const local = (uint8_t*) dest;
|
||||
uint8_t* const remote = (uint8_t*) src;
|
||||
|
||||
while (done < length) {
|
||||
const size_t l = length - done > word_size ? word_size : length - done;
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
|
||||
tmp = 0;
|
||||
}
|
||||
memcpy(local + done, &tmp, l);
|
||||
done += l;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the mapping which the given memory address falls in.
|
||||
const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
|
||||
const uintptr_t addr = (uintptr_t) address;
|
||||
@@ -546,11 +334,10 @@ bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
|
||||
// Check |path| against the /proc/pid/exe 'symlink'.
|
||||
char exe_link[NAME_MAX];
|
||||
char new_path[NAME_MAX];
|
||||
BuildProcPath(exe_link, pid_, "exe");
|
||||
ssize_t new_path_len = sys_readlink(exe_link, new_path, NAME_MAX);
|
||||
if (new_path_len <= 0 || new_path_len == NAME_MAX)
|
||||
if (!BuildProcPath(exe_link, pid_, "exe"))
|
||||
return false;
|
||||
if (!SafeReadLink(exe_link, new_path))
|
||||
return false;
|
||||
new_path[new_path_len] = '\0';
|
||||
if (my_strcmp(path, new_path) != 0)
|
||||
return false;
|
||||
|
||||
|
@@ -27,6 +27,14 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which
|
||||
// is a base class for extracting information of a crashed process. It
|
||||
// was originally a complete implementation using the ptrace API, but
|
||||
// has been refactored to allow derived implementations supporting both
|
||||
// ptrace and core dump. A portion of the original implementation is now
|
||||
// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for
|
||||
// details).
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
|
||||
@@ -66,7 +74,7 @@ typedef struct
|
||||
#define AT_SYSINFO_EHDR 33
|
||||
#endif
|
||||
#endif // __ANDROID__
|
||||
#elif defined(__x86_64__)
|
||||
#elif defined(__x86_64)
|
||||
typedef Elf64_auxv_t elf_aux_entry;
|
||||
#endif
|
||||
// When we find the VDSO mapping in the process's address space, this
|
||||
@@ -118,16 +126,21 @@ class LinuxDumper {
|
||||
public:
|
||||
explicit LinuxDumper(pid_t pid);
|
||||
|
||||
virtual ~LinuxDumper();
|
||||
|
||||
// Parse the data for |threads| and |mappings|.
|
||||
bool Init();
|
||||
virtual bool Init();
|
||||
|
||||
// Return true if the dumper performs a post-mortem dump.
|
||||
virtual bool IsPostMortem() const = 0;
|
||||
|
||||
// Suspend/resume all threads in the given process.
|
||||
bool ThreadsSuspend();
|
||||
bool ThreadsResume();
|
||||
virtual bool ThreadsSuspend() = 0;
|
||||
virtual bool ThreadsResume() = 0;
|
||||
|
||||
// Read information about the given thread. Returns true on success. One must
|
||||
// have called |ThreadsSuspend| first.
|
||||
bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
|
||||
// Read information about the |index|-th thread of |threads_|.
|
||||
// Returns true on success. One must have called |ThreadsSuspend| first.
|
||||
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0;
|
||||
|
||||
// These are only valid after a call to |Init|.
|
||||
const wasteful_vector<pid_t> &threads() { return threads_; }
|
||||
@@ -142,14 +155,16 @@ class LinuxDumper {
|
||||
|
||||
PageAllocator* allocator() { return &allocator_; }
|
||||
|
||||
// memcpy from a remote process.
|
||||
static void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length);
|
||||
// Copy content of |length| bytes from a given process |child|,
|
||||
// starting from |src|, into |dest|.
|
||||
virtual void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length) = 0;
|
||||
|
||||
// Builds a proc path for a certain pid for a node. path is a
|
||||
// character array that is overwritten, and node is the final node
|
||||
// without any slashes.
|
||||
void BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
|
||||
// |path| is a character array of at least NAME_MAX bytes to return the
|
||||
// result.|node| is the final node without any slashes. Returns true on
|
||||
// success.
|
||||
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0;
|
||||
|
||||
// Generate a File ID from the .text section of a mapped entry.
|
||||
// If not a member, mapping_id is ignored.
|
||||
@@ -163,10 +178,25 @@ class LinuxDumper {
|
||||
// [vdso], but we can't guarantee that it's the only virtual dynamic
|
||||
// shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
|
||||
// is the safest way to go.)
|
||||
void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
|
||||
private:
|
||||
bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
|
||||
bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
|
||||
void* FindBeginningOfLinuxGateSharedLibrary(pid_t pid) const;
|
||||
// Utility method to find the entry point location.
|
||||
void* FindEntryPoint(pid_t pid) const;
|
||||
|
||||
uintptr_t crash_address() const { return crash_address_; }
|
||||
void set_crash_address(uintptr_t crash_address) {
|
||||
crash_address_ = crash_address;
|
||||
}
|
||||
|
||||
int crash_signal() const { return crash_signal_; }
|
||||
void set_crash_signal(int crash_signal) { crash_signal_ = crash_signal; }
|
||||
|
||||
pid_t crash_thread() const { return crash_thread_; }
|
||||
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
|
||||
|
||||
protected:
|
||||
virtual bool EnumerateMappings();
|
||||
|
||||
virtual bool EnumerateThreads() = 0;
|
||||
|
||||
// For the case where a running program has been deleted, it'll show up in
|
||||
// /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
|
||||
@@ -179,13 +209,25 @@ class LinuxDumper {
|
||||
// Returns true if |path| is modified.
|
||||
bool HandleDeletedFileInMapping(char* path) const;
|
||||
|
||||
// ID of the crashed process.
|
||||
const pid_t pid_;
|
||||
|
||||
// Virtual address at which the process crashed.
|
||||
uintptr_t crash_address_;
|
||||
|
||||
// Signal that terminated the crashed process.
|
||||
int crash_signal_;
|
||||
|
||||
// ID of the crashed thread.
|
||||
pid_t crash_thread_;
|
||||
|
||||
mutable PageAllocator allocator_;
|
||||
|
||||
bool threads_suspended_;
|
||||
wasteful_vector<pid_t> threads_; // the ids of all the threads
|
||||
wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
|
||||
// IDs of all the threads.
|
||||
wasteful_vector<pid_t> threads_;
|
||||
|
||||
// Info from /proc/<pid>/maps.
|
||||
wasteful_vector<MappingInfo*> mappings_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
@@ -51,7 +51,14 @@
|
||||
#endif
|
||||
|
||||
void *thread_function(void *data) {
|
||||
int pipefd = *static_cast<int *>(data);
|
||||
volatile pid_t thread_id = syscall(__NR_gettid);
|
||||
// Signal parent that a thread has started.
|
||||
uint8_t byte = 1;
|
||||
if (write(pipefd, &byte, sizeof(byte)) != sizeof(byte)) {
|
||||
perror("ERROR: parent notification failed");
|
||||
return NULL;
|
||||
}
|
||||
register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = &thread_id;
|
||||
while (true)
|
||||
asm volatile ("" : : "r" (thread_id_ptr));
|
||||
@@ -59,9 +66,9 @@ void *thread_function(void *data) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr,
|
||||
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n");
|
||||
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads>\n");
|
||||
return 1;
|
||||
}
|
||||
int pipefd = atoi(argv[1]);
|
||||
@@ -75,11 +82,8 @@ int main(int argc, char *argv[]) {
|
||||
pthread_attr_init(&thread_attributes);
|
||||
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
|
||||
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, &pipefd);
|
||||
}
|
||||
// Signal parent that this process has started all threads.
|
||||
uint8_t byte = 1;
|
||||
write(pipefd, &byte, sizeof(byte));
|
||||
thread_function(NULL);
|
||||
thread_function(&pipefd);
|
||||
return 0;
|
||||
}
|
||||
|
293
thirdparty/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc
vendored
Normal file
293
thirdparty/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.cc
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper.
|
||||
// See linux_ptrace_dumper.h for detals.
|
||||
// This class was originally splitted from google_breakpad::LinuxDumper.
|
||||
|
||||
// This code deals with the mechanics of getting information about a crashed
|
||||
// process. Since this code may run in a compromised address space, the same
|
||||
// rules apply as detailed at the top of minidump_writer.h: no libc calls and
|
||||
// use the alternative allocator.
|
||||
|
||||
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
// Suspends a thread by attaching to it.
|
||||
static bool SuspendThread(pid_t pid) {
|
||||
// This may fail if the thread has just died or debugged.
|
||||
errno = 0;
|
||||
if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
|
||||
errno != 0) {
|
||||
return false;
|
||||
}
|
||||
while (sys_waitpid(pid, NULL, __WALL) < 0) {
|
||||
if (errno != EINTR) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
// On x86, the stack pointer is NULL or -1, when executing trusted code in
|
||||
// the seccomp sandbox. Not only does this cause difficulties down the line
|
||||
// when trying to dump the thread's stack, it also results in the minidumps
|
||||
// containing information about the trusted threads. This information is
|
||||
// generally completely meaningless and just pollutes the minidumps.
|
||||
// We thus test the stack pointer and exclude any threads that are part of
|
||||
// the seccomp sandbox's trusted code.
|
||||
user_regs_struct regs;
|
||||
if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 ||
|
||||
#if defined(__i386)
|
||||
!regs.esp
|
||||
#elif defined(__x86_64)
|
||||
!regs.rsp
|
||||
#endif
|
||||
) {
|
||||
sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resumes a thread by detaching from it.
|
||||
static bool ResumeThread(pid_t pid) {
|
||||
return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid)
|
||||
: LinuxDumper(pid),
|
||||
threads_suspended_(false) {
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid,
|
||||
const char* node) const {
|
||||
if (!path || !node || pid <= 0)
|
||||
return false;
|
||||
|
||||
size_t node_len = my_strlen(node);
|
||||
if (node_len == 0)
|
||||
return false;
|
||||
|
||||
const unsigned pid_len = my_int_len(pid);
|
||||
const size_t total_length = 6 + pid_len + 1 + node_len;
|
||||
if (total_length >= NAME_MAX)
|
||||
return false;
|
||||
|
||||
memcpy(path, "/proc/", 6);
|
||||
my_itos(path + 6, pid, pid_len);
|
||||
path[6 + pid_len] = '/';
|
||||
memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||
path[total_length] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child,
|
||||
const void* src, size_t length) {
|
||||
unsigned long tmp = 55;
|
||||
size_t done = 0;
|
||||
static const size_t word_size = sizeof(tmp);
|
||||
uint8_t* const local = (uint8_t*) dest;
|
||||
uint8_t* const remote = (uint8_t*) src;
|
||||
|
||||
while (done < length) {
|
||||
const size_t l = (length - done > word_size) ? word_size : (length - done);
|
||||
if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
|
||||
tmp = 0;
|
||||
}
|
||||
memcpy(local + done, &tmp, l);
|
||||
done += l;
|
||||
}
|
||||
}
|
||||
|
||||
// Read thread info from /proc/$pid/status.
|
||||
// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
|
||||
// these members are set to -1. Returns true iff all three members are
|
||||
// available.
|
||||
bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) {
|
||||
if (index >= threads_.size())
|
||||
return false;
|
||||
|
||||
pid_t tid = threads_[index];
|
||||
|
||||
assert(info != NULL);
|
||||
char status_path[NAME_MAX];
|
||||
if (!BuildProcPath(status_path, tid, "status"))
|
||||
return false;
|
||||
|
||||
const int fd = sys_open(status_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
|
||||
info->ppid = info->tgid = -1;
|
||||
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
if (my_strncmp("Tgid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->tgid, line + 6);
|
||||
} else if (my_strncmp("PPid:\t", line, 6) == 0) {
|
||||
my_strtoui(&info->ppid, line + 6);
|
||||
}
|
||||
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
sys_close(fd);
|
||||
|
||||
if (info->ppid == -1 || info->tgid == -1)
|
||||
return false;
|
||||
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__i386)
|
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
|
||||
if (sys_ptrace(
|
||||
PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*> (offsetof(struct user,
|
||||
u_debugreg[0]) + i *
|
||||
sizeof(debugreg_t)),
|
||||
&info->dregs[i]) == -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint8_t* stack_pointer;
|
||||
#if defined(__i386)
|
||||
memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#elif defined(__ARM_EABI__)
|
||||
memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
|
||||
#else
|
||||
#error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
return GetStackInfo(&info->stack, &info->stack_len,
|
||||
(uintptr_t) stack_pointer);
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::IsPostMortem() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::ThreadsSuspend() {
|
||||
if (threads_suspended_)
|
||||
return true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
if (!SuspendThread(threads_[i])) {
|
||||
// If the thread either disappeared before we could attach to it, or if
|
||||
// it was part of the seccomp sandbox's trusted code, it is OK to
|
||||
// silently drop it from the minidump.
|
||||
memmove(&threads_[i], &threads_[i+1],
|
||||
(threads_.size() - i - 1) * sizeof(threads_[i]));
|
||||
threads_.resize(threads_.size() - 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
threads_suspended_ = true;
|
||||
return threads_.size() > 0;
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::ThreadsResume() {
|
||||
if (!threads_suspended_)
|
||||
return false;
|
||||
bool good = true;
|
||||
for (size_t i = 0; i < threads_.size(); ++i)
|
||||
good &= ResumeThread(threads_[i]);
|
||||
threads_suspended_ = false;
|
||||
return good;
|
||||
}
|
||||
|
||||
// Parse /proc/$pid/task to list all the threads of the process identified by
|
||||
// pid.
|
||||
bool LinuxPtraceDumper::EnumerateThreads() {
|
||||
char task_path[NAME_MAX];
|
||||
if (!BuildProcPath(task_path, pid_, "task"))
|
||||
return false;
|
||||
|
||||
const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
|
||||
|
||||
// The directory may contain duplicate entries which we filter by assuming
|
||||
// that they are consecutive.
|
||||
int last_tid = -1;
|
||||
const char* dent_name;
|
||||
while (dir_reader->GetNextEntry(&dent_name)) {
|
||||
if (my_strcmp(dent_name, ".") &&
|
||||
my_strcmp(dent_name, "..")) {
|
||||
int tid = 0;
|
||||
if (my_strtoui(&tid, dent_name) &&
|
||||
last_tid != tid) {
|
||||
last_tid = tid;
|
||||
threads_.push_back(tid);
|
||||
}
|
||||
}
|
||||
dir_reader->PopEntry();
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
92
thirdparty/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.h
vendored
Normal file
92
thirdparty/breakpad/client/linux/minidump_writer/linux_ptrace_dumper.h
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012, 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.
|
||||
|
||||
// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper
|
||||
// class, which is derived from google_breakpad::LinuxDumper to extract
|
||||
// information from a crashed process via ptrace.
|
||||
// This class was originally splitted from google_breakpad::LinuxDumper.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class LinuxPtraceDumper : public LinuxDumper {
|
||||
public:
|
||||
// Constructs a dumper for extracting information of a given process
|
||||
// with a process ID of |pid|.
|
||||
explicit LinuxPtraceDumper(pid_t pid);
|
||||
|
||||
// Implements LinuxDumper::BuildProcPath().
|
||||
// Builds a proc path for a certain pid for a node (/proc/<pid>/<node>).
|
||||
// |path| is a character array of at least NAME_MAX bytes to return the
|
||||
// result. |node| is the final node without any slashes. Returns true on
|
||||
// success.
|
||||
virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const;
|
||||
|
||||
// Implements LinuxDumper::CopyFromProcess().
|
||||
// Copies content of |length| bytes from a given process |child|,
|
||||
// starting from |src|, into |dest|. This method uses ptrace to extract
|
||||
// the content from the target process.
|
||||
virtual void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length);
|
||||
|
||||
// Implements LinuxDumper::GetThreadInfoByIndex().
|
||||
// Reads information about the |index|-th thread of |threads_|.
|
||||
// Returns true on success. One must have called |ThreadsSuspend| first.
|
||||
virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info);
|
||||
|
||||
// Implements LinuxDumper::IsPostMortem().
|
||||
// Always returns false to indicate this dumper performs a dump of
|
||||
// a crashed process via ptrace.
|
||||
virtual bool IsPostMortem() const;
|
||||
|
||||
// Implements LinuxDumper::ThreadsSuspend().
|
||||
// Suspends all threads in the given process. Returns true on success.
|
||||
virtual bool ThreadsSuspend();
|
||||
|
||||
// Implements LinuxDumper::ThreadsResume().
|
||||
// Resumes all threads in the given process. Returns true on success.
|
||||
virtual bool ThreadsResume();
|
||||
|
||||
protected:
|
||||
// Implements LinuxDumper::EnumerateThreads().
|
||||
// Enumerates all threads of the given process into |threads_|.
|
||||
virtual bool EnumerateThreads();
|
||||
|
||||
private:
|
||||
// Set to true if all threads of the crashed process are suspended.
|
||||
bool threads_suspended_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_
|
@@ -27,7 +27,11 @@
|
||||
// (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 <string>
|
||||
// linux_ptrace_dumper_unittest.cc:
|
||||
// Unit tests for google_breakpad::LinuxPtraceDumoer.
|
||||
//
|
||||
// This file was renamed from linux_dumper_unittest.cc and modified due
|
||||
// to LinuxDumper being splitted into two classes.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
@@ -39,22 +43,26 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "common/memory.h"
|
||||
|
||||
using std::string;
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LinuxDumperTest;
|
||||
|
||||
typedef testing::Test LinuxPtraceDumperTest;
|
||||
|
||||
string GetHelperBinary() {
|
||||
// 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) {
|
||||
if (!SafeReadLink("/proc/self/exe", self_path)) {
|
||||
return "";
|
||||
}
|
||||
string helper_path(self_path);
|
||||
@@ -68,14 +76,14 @@ string GetHelperBinary() {
|
||||
return helper_path;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(LinuxPtraceDumperTest, Setup) {
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, Setup) {
|
||||
LinuxDumper dumper(getpid());
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, FindMappings) {
|
||||
LinuxDumper dumper(getpid());
|
||||
TEST(LinuxPtraceDumperTest, FindMappings) {
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
|
||||
@@ -83,8 +91,8 @@ TEST(LinuxDumperTest, FindMappings) {
|
||||
ASSERT_FALSE(dumper.FindMapping(NULL));
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, ThreadList) {
|
||||
LinuxDumper dumper(getpid());
|
||||
TEST(LinuxPtraceDumperTest, ThreadList) {
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_GE(dumper.threads().size(), (size_t)1);
|
||||
@@ -100,7 +108,7 @@ TEST(LinuxDumperTest, ThreadList) {
|
||||
// Helper stack class to close a file descriptor and unmap
|
||||
// a mmap'ed mapping.
|
||||
class StackHelper {
|
||||
public:
|
||||
public:
|
||||
StackHelper(int fd, char* mapping, size_t size)
|
||||
: fd_(fd), mapping_(mapping), size_(size) {}
|
||||
~StackHelper() {
|
||||
@@ -108,13 +116,13 @@ public:
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
int fd_;
|
||||
char* mapping_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
TEST(LinuxDumperTest, MergedMappings) {
|
||||
TEST(LinuxPtraceDumperTest, MergedMappings) {
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
@@ -136,24 +144,25 @@ TEST(LinuxDumperTest, MergedMappings) {
|
||||
0));
|
||||
ASSERT_TRUE(mapping);
|
||||
|
||||
const u_int64_t kMappingAddress = reinterpret_cast<u_int64_t>(mapping);
|
||||
const uintptr_t kMappingAddress = reinterpret_cast<uintptr_t>(mapping);
|
||||
|
||||
// Ensure that things get cleaned up.
|
||||
StackHelper helper(fd, mapping, kMappingSize);
|
||||
|
||||
// Carve a page out of the first mapping with different permissions.
|
||||
char* inside_mapping = reinterpret_cast<char*>(mmap(mapping + 2 *kPageSize,
|
||||
kPageSize,
|
||||
PROT_NONE,
|
||||
MAP_SHARED | MAP_FIXED,
|
||||
fd,
|
||||
// Map a different offset just to
|
||||
// better test real-world conditions.
|
||||
kPageSize));
|
||||
char* inside_mapping = reinterpret_cast<char*>(
|
||||
mmap(mapping + 2 *kPageSize,
|
||||
kPageSize,
|
||||
PROT_NONE,
|
||||
MAP_SHARED | MAP_FIXED,
|
||||
fd,
|
||||
// Map a different offset just to
|
||||
// better test real-world conditions.
|
||||
kPageSize));
|
||||
ASSERT_TRUE(inside_mapping);
|
||||
|
||||
// Now check that LinuxDumper interpreted the mappings properly.
|
||||
LinuxDumper dumper(getpid());
|
||||
// Now check that LinuxPtraceDumper interpreted the mappings properly.
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
int mapping_count = 0;
|
||||
for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
|
||||
@@ -170,7 +179,7 @@ TEST(LinuxDumperTest, MergedMappings) {
|
||||
EXPECT_EQ(1, mapping_count);
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 5;
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||
@@ -203,28 +212,36 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
exit(0);
|
||||
}
|
||||
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));
|
||||
// Wait for all child threads to indicate that they have started
|
||||
for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
|
||||
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;
|
||||
ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), sizeof(junk));
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
LinuxDumper dumper(child_pid);
|
||||
// There is a race here because we may stop a child thread before
|
||||
// it is actually running the busy loop. Empirically this sleep
|
||||
// is sufficient to avoid the race.
|
||||
usleep(100000);
|
||||
|
||||
// Children are ready now.
|
||||
LinuxPtraceDumper dumper(child_pid);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
|
||||
ThreadInfo one_thread;
|
||||
for(size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
EXPECT_TRUE(dumper.ThreadInfoGet(dumper.threads()[i], &one_thread));
|
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
|
||||
// In the helper program, we stored a pointer to the thread id in a
|
||||
// specific register. Check that we can recover its value.
|
||||
#if defined(__ARM_EABI__)
|
||||
@@ -243,42 +260,43 @@ TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
4);
|
||||
EXPECT_EQ(dumper.threads()[i], one_thread_id);
|
||||
}
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
kill(child_pid, SIGKILL);
|
||||
|
||||
// Reap child
|
||||
int status;
|
||||
ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(SIGKILL, WTERMSIG(status));
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, BuildProcPath) {
|
||||
TEST(LinuxPtraceDumperTest, BuildProcPath) {
|
||||
const pid_t pid = getpid();
|
||||
LinuxDumper dumper(pid);
|
||||
LinuxPtraceDumper dumper(pid);
|
||||
|
||||
char maps_path[256] = "dummymappath";
|
||||
char maps_path_expected[256];
|
||||
char maps_path[NAME_MAX] = "";
|
||||
char maps_path_expected[NAME_MAX];
|
||||
snprintf(maps_path_expected, sizeof(maps_path_expected),
|
||||
"/proc/%d/maps", pid);
|
||||
dumper.BuildProcPath(maps_path, pid, "maps");
|
||||
ASSERT_STREQ(maps_path, maps_path_expected);
|
||||
EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
|
||||
EXPECT_STREQ(maps_path_expected, maps_path);
|
||||
|
||||
// In release mode, we expect BuildProcPath to handle the invalid
|
||||
// parameters correctly and fill map_path with an empty
|
||||
// NULL-terminated string.
|
||||
#ifdef NDEBUG
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, 0, "maps");
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
|
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, getpid(), "");
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
|
||||
snprintf(maps_path, sizeof(maps_path), "dummymappath");
|
||||
dumper.BuildProcPath(maps_path, getpid(), NULL);
|
||||
EXPECT_STREQ(maps_path, "");
|
||||
#endif
|
||||
char long_node[NAME_MAX];
|
||||
size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
|
||||
memset(long_node, 'a', long_node_len);
|
||||
long_node[long_node_len] = '\0';
|
||||
EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
|
||||
}
|
||||
|
||||
#if !defined(__ARM_EABI__)
|
||||
// Ensure that the linux-gate VDSO is included in the mapping list.
|
||||
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||
LinuxDumper dumper(getpid());
|
||||
TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) {
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
|
||||
@@ -300,8 +318,8 @@ TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||
}
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingID) {
|
||||
LinuxDumper dumper(getpid());
|
||||
TEST(LinuxPtraceDumperTest, LinuxGateMappingID) {
|
||||
LinuxPtraceDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
@@ -328,7 +346,7 @@ TEST(LinuxDumperTest, LinuxGateMappingID) {
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
|
||||
// from a child process.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
|
||||
TEST(LinuxPtraceDumperTest, LinuxGateMappingIDChild) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
@@ -344,7 +362,7 @@ TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
LinuxPtraceDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
@@ -374,14 +392,12 @@ TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LinuxDumperTest, FileIDsMatch) {
|
||||
TEST(LinuxPtraceDumperTest, FileIDsMatch) {
|
||||
// Calculate the File ID of our binary using both
|
||||
// FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
|
||||
// and ensure that we get the same result from both.
|
||||
char exe_name[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_name, PATH_MAX - 1);
|
||||
ASSERT_NE(len, -1);
|
||||
exe_name[len] = '\0';
|
||||
ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
@@ -398,7 +414,7 @@ TEST(LinuxDumperTest, FileIDsMatch) {
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
LinuxPtraceDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
bool found_exe = false;
|
@@ -46,26 +46,24 @@
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "client/minidump_file_writer-inl.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <link.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/ucontext.h>
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "google_breakpad/common/minidump_cpu_amd64.h"
|
||||
#include "google_breakpad/common/minidump_cpu_x86.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include "client/linux/android_link.h"
|
||||
@@ -74,8 +72,11 @@
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_extension_linux.h"
|
||||
#include "client/minidump_file_writer.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
// Minidump defines register structures which are different from the raw
|
||||
@@ -368,32 +369,30 @@ namespace google_breakpad {
|
||||
class MinidumpWriter {
|
||||
public:
|
||||
MinidumpWriter(const char* filename,
|
||||
pid_t crashing_pid,
|
||||
const ExceptionHandler::CrashContext* context,
|
||||
const MappingList& mappings)
|
||||
const MappingList& mappings,
|
||||
LinuxDumper* dumper)
|
||||
: filename_(filename),
|
||||
siginfo_(&context->siginfo),
|
||||
ucontext_(&context->context),
|
||||
ucontext_(context ? &context->context : NULL),
|
||||
#if !defined(__ARM_EABI__)
|
||||
float_state_(&context->float_state),
|
||||
float_state_(context ? &context->float_state : NULL),
|
||||
#else
|
||||
// TODO: fix this after fixing ExceptionHandler
|
||||
float_state_(NULL),
|
||||
#endif
|
||||
crashing_tid_(context->tid),
|
||||
dumper_(crashing_pid),
|
||||
memory_blocks_(dumper_.allocator()),
|
||||
dumper_(dumper),
|
||||
memory_blocks_(dumper_->allocator()),
|
||||
mapping_list_(mappings) {
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
return dumper_.Init() && minidump_writer_.Open(filename_) &&
|
||||
dumper_.ThreadsSuspend();
|
||||
return dumper_->Init() && minidump_writer_.Open(filename_) &&
|
||||
dumper_->ThreadsSuspend();
|
||||
}
|
||||
|
||||
~MinidumpWriter() {
|
||||
minidump_writer_.Close();
|
||||
dumper_.ThreadsResume();
|
||||
dumper_->ThreadsResume();
|
||||
}
|
||||
|
||||
bool Dump() {
|
||||
@@ -403,17 +402,25 @@ class MinidumpWriter {
|
||||
struct r_debug* r_debug = NULL;
|
||||
uint32_t dynamic_length = 0;
|
||||
#if !defined(__ANDROID__)
|
||||
// The Android NDK is missing structure definitions for most of this.
|
||||
// For now, it's simpler just to skip it.
|
||||
for (int i = 0;;) {
|
||||
ElfW(Dyn) dyn;
|
||||
dynamic_length += sizeof(dyn);
|
||||
dumper_.CopyFromProcess(&dyn, crashing_tid_, _DYNAMIC+i++, sizeof(dyn));
|
||||
if (dyn.d_tag == DT_DEBUG) {
|
||||
r_debug = (struct r_debug*)dyn.d_un.d_ptr;
|
||||
continue;
|
||||
} else if (dyn.d_tag == DT_NULL) {
|
||||
break;
|
||||
// This code assumes the crashing process is the same as this process and
|
||||
// may hang or take a long time to complete if not so.
|
||||
// Thus, we skip this code for a post-mortem based dump.
|
||||
if (!dumper_->IsPostMortem()) {
|
||||
// The Android NDK is missing structure definitions for most of this.
|
||||
// For now, it's simpler just to skip it.
|
||||
for (int i = 0;;) {
|
||||
ElfW(Dyn) dyn;
|
||||
dynamic_length += sizeof(dyn);
|
||||
// NOTE: Use of _DYNAMIC assumes this is the same process as the
|
||||
// crashing process. This loop will go forever if it's out of bounds.
|
||||
dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++,
|
||||
sizeof(dyn));
|
||||
if (dyn.d_tag == DT_DEBUG) {
|
||||
r_debug = (struct r_debug*)dyn.d_un.d_ptr;
|
||||
continue;
|
||||
} else if (dyn.d_tag == DT_NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -467,7 +474,7 @@ class MinidumpWriter {
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_PROC_STATUS;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "status"))
|
||||
if (!WriteProcFile(&dirent.location, GetCrashThread(), "status"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
@@ -477,22 +484,22 @@ class MinidumpWriter {
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_CMD_LINE;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
|
||||
if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_ENVIRON;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
|
||||
if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_AUXV;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
|
||||
if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
dirent.stream_type = MD_LINUX_MAPS;
|
||||
if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
|
||||
if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps"))
|
||||
NullifyDirectoryEntry(&dirent);
|
||||
dir.CopyIndex(dir_index++, &dirent);
|
||||
|
||||
@@ -506,7 +513,7 @@ class MinidumpWriter {
|
||||
// If you add more directory entries, don't forget to update kNumWriters,
|
||||
// above.
|
||||
|
||||
dumper_.ThreadsResume();
|
||||
dumper_->ThreadsResume();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -629,7 +636,7 @@ class MinidumpWriter {
|
||||
|
||||
// Write information about the threads.
|
||||
bool WriteThreadListStream(MDRawDirectory* dirent) {
|
||||
const unsigned num_threads = dumper_.threads().size();
|
||||
const unsigned num_threads = dumper_->threads().size();
|
||||
|
||||
TypedMDRVA<uint32_t> list(&minidump_writer_);
|
||||
if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread)))
|
||||
@@ -643,21 +650,23 @@ class MinidumpWriter {
|
||||
for (unsigned i = 0; i < num_threads; ++i) {
|
||||
MDRawThread thread;
|
||||
my_memset(&thread, 0, sizeof(thread));
|
||||
thread.thread_id = dumper_.threads()[i];
|
||||
thread.thread_id = dumper_->threads()[i];
|
||||
// We have a different source of information for the crashing thread. If
|
||||
// we used the actual state of the thread we would find it running in the
|
||||
// signal handler with the alternative stack, which would be deeply
|
||||
// unhelpful.
|
||||
if ((pid_t)thread.thread_id == crashing_tid_) {
|
||||
if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() &&
|
||||
!dumper_->IsPostMortem()) {
|
||||
const void* stack;
|
||||
size_t stack_len;
|
||||
if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
|
||||
if (!dumper_->GetStackInfo(&stack, &stack_len, GetStackPointer()))
|
||||
return false;
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(stack_len))
|
||||
return false;
|
||||
uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
|
||||
dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
|
||||
uint8_t* stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len));
|
||||
dumper_->CopyFromProcess(stack_copy, thread.thread_id, stack,
|
||||
stack_len);
|
||||
memory.Copy(stack_copy, stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t) (stack);
|
||||
thread.stack.memory = memory.location();
|
||||
@@ -671,8 +680,8 @@ class MinidumpWriter {
|
||||
// don't bother trying to write it.
|
||||
bool ip_is_mapped = false;
|
||||
MDMemoryDescriptor ip_memory_d;
|
||||
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
for (unsigned j = 0; j < dumper_->mappings().size(); ++j) {
|
||||
const MappingInfo& mapping = *dumper_->mappings()[j];
|
||||
if (ip >= mapping.start_addr &&
|
||||
ip < mapping.start_addr + mapping.size) {
|
||||
ip_is_mapped = true;
|
||||
@@ -695,12 +704,12 @@ class MinidumpWriter {
|
||||
if (!ip_memory.Allocate(ip_memory_d.memory.data_size))
|
||||
return false;
|
||||
uint8_t* memory_copy =
|
||||
(uint8_t*) dumper_.allocator()->Alloc(ip_memory_d.memory.data_size);
|
||||
dumper_.CopyFromProcess(
|
||||
memory_copy,
|
||||
thread.thread_id,
|
||||
reinterpret_cast<void*>(ip_memory_d.start_of_memory_range),
|
||||
ip_memory_d.memory.data_size);
|
||||
reinterpret_cast<uint8_t*>(Alloc(ip_memory_d.memory.data_size));
|
||||
dumper_->CopyFromProcess(
|
||||
memory_copy,
|
||||
thread.thread_id,
|
||||
reinterpret_cast<void*>(ip_memory_d.start_of_memory_range),
|
||||
ip_memory_d.memory.data_size);
|
||||
ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size);
|
||||
ip_memory_d.memory = ip_memory.location();
|
||||
memory_blocks_.push_back(ip_memory_d);
|
||||
@@ -716,15 +725,14 @@ class MinidumpWriter {
|
||||
crashing_thread_context_ = cpu.location();
|
||||
} else {
|
||||
ThreadInfo info;
|
||||
if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
|
||||
if (!dumper_->GetThreadInfoByIndex(i, &info))
|
||||
return false;
|
||||
UntypedMDRVA memory(&minidump_writer_);
|
||||
if (!memory.Allocate(info.stack_len))
|
||||
return false;
|
||||
uint8_t* stack_copy =
|
||||
(uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
|
||||
dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
|
||||
info.stack_len);
|
||||
uint8_t* stack_copy = reinterpret_cast<uint8_t*>(Alloc(info.stack_len));
|
||||
dumper_->CopyFromProcess(stack_copy, thread.thread_id, info.stack,
|
||||
info.stack_len);
|
||||
memory.Copy(stack_copy, info.stack_len);
|
||||
thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
|
||||
thread.stack.memory = memory.location();
|
||||
@@ -737,6 +745,10 @@ class MinidumpWriter {
|
||||
CPUFillFromThreadInfo(cpu.get(), info);
|
||||
PopSeccompStackFrame(cpu.get(), thread, stack_copy);
|
||||
thread.thread_context = cpu.location();
|
||||
if (dumper_->threads()[i] == GetCrashThread()) {
|
||||
assert(dumper_->IsPostMortem());
|
||||
crashing_thread_context_ = cpu.location();
|
||||
}
|
||||
}
|
||||
|
||||
list.CopyIndexAfterObject(i, &thread, sizeof(thread));
|
||||
@@ -777,11 +789,11 @@ class MinidumpWriter {
|
||||
// Because of this, we also include the full, unparsed, /proc/$x/maps file in
|
||||
// another stream in the file.
|
||||
bool WriteMappings(MDRawDirectory* dirent) {
|
||||
const unsigned num_mappings = dumper_.mappings().size();
|
||||
const unsigned num_mappings = dumper_->mappings().size();
|
||||
unsigned num_output_mappings = mapping_list_.size();
|
||||
|
||||
for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper_.mappings()[i];
|
||||
for (unsigned i = 0; i < dumper_->mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper_->mappings()[i];
|
||||
if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping))
|
||||
num_output_mappings++;
|
||||
}
|
||||
@@ -797,7 +809,7 @@ class MinidumpWriter {
|
||||
// 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) || HaveMappingInfo(mapping))
|
||||
continue;
|
||||
|
||||
@@ -859,8 +871,8 @@ class MinidumpWriter {
|
||||
// GUID was provided by caller.
|
||||
memcpy(signature, identifier, sizeof(MDGUID));
|
||||
} else {
|
||||
dumper_.ElfFileIdentifierForMapping(mapping, member,
|
||||
mapping_id, signature);
|
||||
dumper_->ElfFileIdentifierForMapping(mapping, member,
|
||||
mapping_id, signature);
|
||||
}
|
||||
my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
|
||||
cv_ptr += sizeof(uint32_t);
|
||||
@@ -905,10 +917,9 @@ class MinidumpWriter {
|
||||
dirent->stream_type = MD_EXCEPTION_STREAM;
|
||||
dirent->location = exc.location();
|
||||
|
||||
exc.get()->thread_id = crashing_tid_;
|
||||
exc.get()->exception_record.exception_code = siginfo_->si_signo;
|
||||
exc.get()->exception_record.exception_address =
|
||||
(uintptr_t) siginfo_->si_addr;
|
||||
exc.get()->thread_id = GetCrashThread();
|
||||
exc.get()->exception_record.exception_code = dumper_->crash_signal();
|
||||
exc.get()->exception_record.exception_address = dumper_->crash_address();
|
||||
exc.get()->thread_context = crashing_thread_context_;
|
||||
|
||||
return true;
|
||||
@@ -945,11 +956,11 @@ class MinidumpWriter {
|
||||
// Count the number of loaded DSOs
|
||||
int dso_count = 0;
|
||||
struct r_debug debug_entry;
|
||||
dumper_.CopyFromProcess(&debug_entry, crashing_tid_, r_debug,
|
||||
sizeof(debug_entry));
|
||||
dumper_->CopyFromProcess(&debug_entry, GetCrashThread(), r_debug,
|
||||
sizeof(debug_entry));
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
dso_count++;
|
||||
}
|
||||
@@ -967,12 +978,12 @@ class MinidumpWriter {
|
||||
// Iterate over DSOs and write their information to mini dump
|
||||
for (struct link_map* ptr = debug_entry.r_map; ptr; ) {
|
||||
struct link_map map;
|
||||
dumper_.CopyFromProcess(&map, crashing_tid_, ptr, sizeof(map));
|
||||
dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map));
|
||||
ptr = map.l_next;
|
||||
char filename[257] = { 0 };
|
||||
if (map.l_name) {
|
||||
dumper_.CopyFromProcess(filename, crashing_tid_, map.l_name,
|
||||
sizeof(filename) - 1);
|
||||
dumper_->CopyFromProcess(filename, GetCrashThread(), map.l_name,
|
||||
sizeof(filename) - 1);
|
||||
}
|
||||
MDLocationDescriptor location;
|
||||
if (!minidump_writer_.WriteString(filename, 0, &location))
|
||||
@@ -1001,8 +1012,8 @@ class MinidumpWriter {
|
||||
debug.get()->dynamic = (void*)&_DYNAMIC;
|
||||
|
||||
char *dso_debug_data = new char[dynamic_length];
|
||||
dumper_.CopyFromProcess(dso_debug_data, crashing_tid_, &_DYNAMIC,
|
||||
dynamic_length);
|
||||
dumper_->CopyFromProcess(dso_debug_data, GetCrashThread(), &_DYNAMIC,
|
||||
dynamic_length);
|
||||
debug.CopyIndexAfterObject(0, dso_debug_data, dynamic_length);
|
||||
delete[] dso_debug_data;
|
||||
|
||||
@@ -1011,6 +1022,14 @@ class MinidumpWriter {
|
||||
}
|
||||
|
||||
private:
|
||||
void* Alloc(unsigned bytes) {
|
||||
return dumper_->allocator()->Alloc(bytes);
|
||||
}
|
||||
|
||||
pid_t GetCrashThread() const {
|
||||
return dumper_->crash_thread();
|
||||
}
|
||||
|
||||
#if defined(__i386)
|
||||
uintptr_t GetStackPointer() {
|
||||
return ucontext_->uc_mcontext.gregs[REG_ESP];
|
||||
@@ -1175,16 +1194,15 @@ class MinidumpWriter {
|
||||
// to read as much as we can into a buffer.
|
||||
static const unsigned kBufSize = 1024 - 2*sizeof(void*);
|
||||
struct Buffers {
|
||||
struct Buffers* next;
|
||||
Buffers* next;
|
||||
size_t len;
|
||||
uint8_t data[kBufSize];
|
||||
} *buffers =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
} *buffers = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
|
||||
buffers->next = NULL;
|
||||
buffers->len = 0;
|
||||
|
||||
size_t total = 0;
|
||||
for (struct Buffers* bufptr = buffers;;) {
|
||||
for (Buffers* bufptr = buffers;;) {
|
||||
ssize_t r;
|
||||
do {
|
||||
r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len);
|
||||
@@ -1196,8 +1214,7 @@ class MinidumpWriter {
|
||||
total += r;
|
||||
bufptr->len += r;
|
||||
if (bufptr->len == kBufSize) {
|
||||
bufptr->next =
|
||||
(struct Buffers*) dumper_.allocator()->Alloc(sizeof(struct Buffers));
|
||||
bufptr->next = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers)));
|
||||
bufptr = bufptr->next;
|
||||
bufptr->next = NULL;
|
||||
bufptr->len = 0;
|
||||
@@ -1277,16 +1294,15 @@ class MinidumpWriter {
|
||||
bool WriteProcFile(MDLocationDescriptor* result, pid_t pid,
|
||||
const char* filename) {
|
||||
char buf[NAME_MAX];
|
||||
dumper_.BuildProcPath(buf, pid, filename);
|
||||
if (!dumper_->BuildProcPath(buf, pid, filename))
|
||||
return false;
|
||||
return WriteFile(result, buf);
|
||||
}
|
||||
|
||||
const char* const filename_; // output filename
|
||||
const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
|
||||
const struct ucontext* const ucontext_; // also from the signal handler
|
||||
const struct _libc_fpstate* const float_state_; // ditto
|
||||
const pid_t crashing_tid_; // the process which actually crashed
|
||||
LinuxDumper dumper_;
|
||||
LinuxDumper* dumper_;
|
||||
MinidumpFileWriter minidump_writer_;
|
||||
MDLocationDescriptor crashing_thread_context_;
|
||||
// Blocks of memory written to the dump. These are all currently
|
||||
@@ -1310,7 +1326,21 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
return false;
|
||||
const ExceptionHandler::CrashContext* context =
|
||||
reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
|
||||
MinidumpWriter writer(filename, crashing_process, context, mappings);
|
||||
LinuxPtraceDumper dumper(crashing_process);
|
||||
dumper.set_crash_address(
|
||||
reinterpret_cast<uintptr_t>(context->siginfo.si_addr));
|
||||
dumper.set_crash_signal(context->siginfo.si_signo);
|
||||
dumper.set_crash_thread(context->tid);
|
||||
MinidumpWriter writer(filename, context, mappings, &dumper);
|
||||
if (!writer.Init())
|
||||
return false;
|
||||
return writer.Dump();
|
||||
}
|
||||
|
||||
bool WriteMinidump(const char* filename,
|
||||
const MappingList& mappings,
|
||||
LinuxDumper* dumper) {
|
||||
MinidumpWriter writer(filename, NULL, mappings, dumper);
|
||||
if (!writer.Init())
|
||||
return false;
|
||||
return writer.Dump();
|
||||
|
@@ -36,12 +36,19 @@
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ExceptionHandler;
|
||||
|
||||
struct MappingEntry {
|
||||
MappingInfo first;
|
||||
u_int8_t second[sizeof(MDGUID)];
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -62,6 +69,10 @@ bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings);
|
||||
|
||||
bool WriteMinidump(const char* filename,
|
||||
const MappingList& mappings,
|
||||
LinuxDumper* dumper);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
|
@@ -42,16 +42,12 @@
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "common/tests/auto_tempdir.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
// Length of a formatted GUID string =
|
||||
// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
|
||||
const int kGUIDStringSize = 37;
|
||||
@@ -77,15 +73,14 @@ TEST(MinidumpWriterTest, Setup) {
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
AutoTempDir temp_dir;
|
||||
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_EQ(stat(templ.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
|
||||
close(fds[1]);
|
||||
}
|
||||
@@ -126,7 +121,7 @@ TEST(MinidumpWriterTest, MappingInfo) {
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
const pid_t child = fork();
|
||||
@@ -143,8 +138,8 @@ TEST(MinidumpWriterTest, MappingInfo) {
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
AutoTempDir temp_dir;
|
||||
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
|
||||
|
||||
// Add information about the mapped memory.
|
||||
MappingInfo info;
|
||||
@@ -158,12 +153,13 @@ TEST(MinidumpWriterTest, MappingInfo) {
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), 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);
|
||||
Minidump minidump(templ.c_str());
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
@@ -177,7 +173,6 @@ TEST(MinidumpWriterTest, MappingInfo) {
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
@@ -211,11 +206,11 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
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);
|
||||
AutoTempDir temp_dir;
|
||||
std::string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp";
|
||||
int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0);
|
||||
ASSERT_NE(-1, fd);
|
||||
unlink(tempfile);
|
||||
unlink(tempfile.c_str());
|
||||
// fill with zeros
|
||||
char buffer[kMemorySize];
|
||||
memset(buffer, 0, kMemorySize);
|
||||
@@ -229,7 +224,7 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
MAP_PRIVATE,
|
||||
fd,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
close(fd);
|
||||
|
||||
@@ -247,8 +242,7 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(dumpfile);
|
||||
std::string dumpfile = temp_dir.path() + "/minidump-writer-unittest";
|
||||
|
||||
// Add information about the mapped memory. Report it as being larger than
|
||||
// it actually is.
|
||||
@@ -264,12 +258,13 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(
|
||||
WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
|
||||
WriteMinidump(dumpfile.c_str(), 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);
|
||||
Minidump minidump(dumpfile.c_str());
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
@@ -283,7 +278,6 @@ TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(dumpfile);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
@@ -294,8 +288,8 @@ TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
|
||||
// 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);
|
||||
if (!SafeReadLink("/proc/self/exe", self_path)) {
|
||||
FAIL() << "readlink failed";
|
||||
exit(1);
|
||||
}
|
||||
string helper_path(self_path);
|
||||
@@ -308,12 +302,13 @@ TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
helper_path += "linux_dumper_unittest_helper";
|
||||
|
||||
// Copy binary to a temp file.
|
||||
char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
|
||||
mktemp(binpath);
|
||||
AutoTempDir temp_dir;
|
||||
std::string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
|
||||
char cmdline[2 * PATH_MAX];
|
||||
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
|
||||
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(),
|
||||
binpath.c_str());
|
||||
ASSERT_EQ(0, system(cmdline));
|
||||
ASSERT_EQ(0, chmod(binpath, 0755));
|
||||
ASSERT_EQ(0, chmod(binpath.c_str(), 0755));
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
@@ -326,8 +321,8 @@ TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
// 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,
|
||||
execl(binpath.c_str(),
|
||||
binpath.c_str(),
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument,
|
||||
NULL);
|
||||
@@ -343,37 +338,38 @@ TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
ASSERT_EQ(1, r);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
uint8_t junk;
|
||||
read(fds[0], &junk, sizeof(junk));
|
||||
const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk)));
|
||||
ASSERT_EQ(sizeof(junk), nr);
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
// Unlink the test binary.
|
||||
unlink(binpath);
|
||||
unlink(binpath.c_str());
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
std::string templ = temp_dir.path() + "/minidump-writer-unittest";
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context,
|
||||
sizeof(context)));
|
||||
kill(child_pid, SIGKILL);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_EQ(stat(templ.c_str(), &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
|
||||
|
||||
Minidump minidump(templ);
|
||||
Minidump minidump(templ.c_str());
|
||||
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());
|
||||
EXPECT_STREQ(binpath.c_str(), module->code_file().c_str());
|
||||
// Check that the file ID is correct.
|
||||
FileID fileid(helper_path.c_str());
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
@@ -391,6 +387,4 @@ TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@
|
||||
|
||||
using std::string;
|
||||
|
||||
DEFINE_string(crash_server, "http://clients2.google.com/cr",
|
||||
DEFINE_string(crash_server, "https://clients2.google.com/cr",
|
||||
"The crash server to upload minidumps to.");
|
||||
DEFINE_string(product_name, "",
|
||||
"The product name that the minidump corresponds to.");
|
||||
|
Reference in New Issue
Block a user