mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-26 23:35:32 +02:00
* Added breakpad support for Linux.
This commit is contained in:
105
thirdparty/breakpad/client/linux/minidump_writer/directory_reader.h
vendored
Normal file
105
thirdparty/breakpad/client/linux/minidump_writer/directory_reader.h
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2009, 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_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A class for enumerating a directory without using diropen/readdir or other
|
||||
// functions which may allocate memory.
|
||||
class DirectoryReader {
|
||||
public:
|
||||
DirectoryReader(int fd)
|
||||
: fd_(fd),
|
||||
buf_used_(0) {
|
||||
}
|
||||
|
||||
// Return the next entry from the directory
|
||||
// name: (output) the NUL terminated entry name
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// After calling this, one must call |PopEntry| otherwise you'll get the same
|
||||
// entry over and over.
|
||||
bool GetNextEntry(const char** name) {
|
||||
struct kernel_dirent* const dent =
|
||||
reinterpret_cast<kernel_dirent*>(buf_);
|
||||
|
||||
if (buf_used_ == 0) {
|
||||
// need to read more entries.
|
||||
const int n = sys_getdents(fd_, dent, sizeof(buf_));
|
||||
if (n < 0) {
|
||||
return false;
|
||||
} else if (n == 0) {
|
||||
hit_eof_ = true;
|
||||
} else {
|
||||
buf_used_ += n;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf_used_ == 0 && hit_eof_)
|
||||
return false;
|
||||
|
||||
assert(buf_used_ > 0);
|
||||
|
||||
*name = dent->d_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PopEntry() {
|
||||
if (!buf_used_)
|
||||
return;
|
||||
|
||||
const struct kernel_dirent* const dent =
|
||||
reinterpret_cast<kernel_dirent*>(buf_);
|
||||
|
||||
buf_used_ -= dent->d_reclen;
|
||||
memmove(buf_, buf_ + dent->d_reclen, buf_used_);
|
||||
}
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
bool hit_eof_;
|
||||
unsigned buf_used_;
|
||||
uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
|
77
thirdparty/breakpad/client/linux/minidump_writer/directory_reader_unittest.cc
vendored
Normal file
77
thirdparty/breakpad/client/linux/minidump_writer/directory_reader_unittest.cc
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2009, 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 <set>
|
||||
#include <string>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test DirectoryReaderTest;
|
||||
}
|
||||
|
||||
TEST(DirectoryReaderTest, CompareResults) {
|
||||
std::set<std::string> dent_set;
|
||||
|
||||
DIR *const dir = opendir("/proc/self");
|
||||
ASSERT_TRUE(dir != NULL);
|
||||
|
||||
struct dirent* dent;
|
||||
while ((dent = readdir(dir)))
|
||||
dent_set.insert(dent->d_name);
|
||||
|
||||
closedir(dir);
|
||||
|
||||
const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
|
||||
ASSERT_GE(fd, 0);
|
||||
|
||||
DirectoryReader dir_reader(fd);
|
||||
unsigned seen = 0;
|
||||
|
||||
const char* name;
|
||||
while (dir_reader.GetNextEntry(&name)) {
|
||||
ASSERT_TRUE(dent_set.find(name) != dent_set.end());
|
||||
seen++;
|
||||
dir_reader.PopEntry();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(dent_set.find("status") != dent_set.end());
|
||||
ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
|
||||
ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
|
||||
|
||||
ASSERT_EQ(dent_set.size(), seen);
|
||||
close(fd);
|
||||
}
|
130
thirdparty/breakpad/client/linux/minidump_writer/line_reader.h
vendored
Normal file
130
thirdparty/breakpad/client/linux/minidump_writer/line_reader.h
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2009, 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_MINIDUMP_WRITER_LINE_READER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A class for reading a file, line by line, without using fopen/fgets or other
|
||||
// functions which may allocate memory.
|
||||
class LineReader {
|
||||
public:
|
||||
LineReader(int fd)
|
||||
: fd_(fd),
|
||||
hit_eof_(false),
|
||||
buf_used_(0) {
|
||||
}
|
||||
|
||||
// The maximum length of a line.
|
||||
static const size_t kMaxLineLen = 512;
|
||||
|
||||
// Return the next line from the file.
|
||||
// line: (output) a pointer to the start of the line. The line is NUL
|
||||
// terminated.
|
||||
// len: (output) the length of the line (not inc the NUL byte)
|
||||
//
|
||||
// Returns true iff successful (false on EOF).
|
||||
//
|
||||
// One must call |PopLine| after this function, otherwise you'll continue to
|
||||
// get the same line over and over.
|
||||
bool GetNextLine(const char **line, unsigned *len) {
|
||||
for (;;) {
|
||||
if (buf_used_ == 0 && hit_eof_)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < buf_used_; ++i) {
|
||||
if (buf_[i] == '\n' || buf_[i] == 0) {
|
||||
buf_[i] = 0;
|
||||
*len = i;
|
||||
*line = buf_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf_used_ == sizeof(buf_)) {
|
||||
// we scanned the whole buffer and didn't find an end-of-line marker.
|
||||
// This line is too long to process.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We didn't find any end-of-line terminators in the buffer. However, if
|
||||
// this is the last line in the file it might not have one:
|
||||
if (hit_eof_) {
|
||||
assert(buf_used_);
|
||||
// There's room for the NUL because of the buf_used_ == sizeof(buf_)
|
||||
// check above.
|
||||
buf_[buf_used_] = 0;
|
||||
*len = buf_used_;
|
||||
buf_used_ += 1; // since we appended the NUL.
|
||||
*line = buf_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, we should pull in more data from the file
|
||||
const ssize_t n = sys_read(fd_, buf_ + buf_used_,
|
||||
sizeof(buf_) - buf_used_);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
} else if (n == 0) {
|
||||
hit_eof_ = true;
|
||||
} else {
|
||||
buf_used_ += n;
|
||||
}
|
||||
|
||||
// At this point, we have either set the hit_eof_ flag, or we have more
|
||||
// data to process...
|
||||
}
|
||||
}
|
||||
|
||||
void PopLine(unsigned len) {
|
||||
// len doesn't include the NUL byte at the end.
|
||||
|
||||
assert(buf_used_ >= len + 1);
|
||||
buf_used_ -= len + 1;
|
||||
memmove(buf_, buf_ + len + 1, buf_used_);
|
||||
}
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
|
||||
bool hit_eof_;
|
||||
unsigned buf_used_;
|
||||
char buf_[kMaxLineLen];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
|
190
thirdparty/breakpad/client/linux/minidump_writer/line_reader_unittest.cc
vendored
Normal file
190
thirdparty/breakpad/client/linux/minidump_writer/line_reader_unittest.cc
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright (c) 2009, 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 <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#define TEMPDIR "/tmp"
|
||||
#else
|
||||
#define TEMPDIR "/data/local/tmp"
|
||||
#endif
|
||||
|
||||
static int TemporaryFile() {
|
||||
static const char templ[] = TEMPDIR "/line-reader-unittest-XXXXXX";
|
||||
char templ_copy[sizeof(templ)];
|
||||
memcpy(templ_copy, templ, sizeof(templ));
|
||||
const int fd = mkstemp(templ_copy);
|
||||
if (fd >= 0)
|
||||
unlink(templ_copy);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LineReaderTest;
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, EmptyFile) {
|
||||
const int fd = TemporaryFile();
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\n", 2);
|
||||
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);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLine) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a", 1);
|
||||
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);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb\n", 4);
|
||||
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);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLines) {
|
||||
const int fd = TemporaryFile();
|
||||
write(fd, "a\nb", 3);
|
||||
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);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(len, (unsigned)1);
|
||||
ASSERT_EQ(line[0], 'b');
|
||||
ASSERT_EQ(line[1], 0);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, MaxLength) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen - 1];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
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_TRUE(memcmp(l, line, sizeof(l)) == 0);
|
||||
ASSERT_EQ(line[len], 0);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TooLong) {
|
||||
const int fd = TemporaryFile();
|
||||
char l[LineReader::kMaxLineLen];
|
||||
memset(l, 'a', sizeof(l));
|
||||
write(fd, l, sizeof(l));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
LineReader reader(fd);
|
||||
|
||||
const char *line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
|
||||
close(fd);
|
||||
}
|
571
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper.cc
vendored
Normal file
571
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper.cc
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
// Copyright (c) 2010, 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.
|
||||
|
||||
// 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_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 "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,
|
||||
// because the semantics of the open may be driver-specific so we'd risk
|
||||
// hanging the crash dumper. And a file in /dev/ almost certainly has no
|
||||
// ELF file identifier anyways.
|
||||
return my_strncmp(mapping.name,
|
||||
kMappedFileUnsafePrefix,
|
||||
sizeof(kMappedFileUnsafePrefix) - 1) == 0;
|
||||
}
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxDumper::LinuxDumper(int pid)
|
||||
: pid_(pid),
|
||||
threads_suspended_(false),
|
||||
threads_(&allocator_, 8),
|
||||
mappings_(&allocator_) {
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
uint8_t identifier[sizeof(MDGUID)])
|
||||
{
|
||||
assert(!member || mapping_id < mappings_.size());
|
||||
my_memset(identifier, 0, sizeof(MDGUID));
|
||||
if (IsMappedFileOpenUnsafe(mapping))
|
||||
return false;
|
||||
|
||||
// Special-case linux-gate because it's not a real file.
|
||||
if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
|
||||
const uintptr_t kPageSize = getpagesize();
|
||||
void* linux_gate = NULL;
|
||||
if (pid_ == sys_getpid()) {
|
||||
linux_gate = reinterpret_cast<void*>(mapping.start_addr);
|
||||
} else {
|
||||
linux_gate = allocator_.Alloc(kPageSize);
|
||||
CopyFromProcess(linux_gate, pid_,
|
||||
reinterpret_cast<const void*>(mapping.start_addr),
|
||||
kPageSize);
|
||||
}
|
||||
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
|
||||
}
|
||||
|
||||
char filename[NAME_MAX];
|
||||
size_t filename_len = my_strlen(mapping.name);
|
||||
assert(filename_len < NAME_MAX);
|
||||
if (filename_len >= NAME_MAX)
|
||||
return false;
|
||||
memcpy(filename, mapping.name, filename_len);
|
||||
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)
|
||||
return false;
|
||||
|
||||
bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
|
||||
sys_munmap(base, st.st_size);
|
||||
if (success && member && filename_modified) {
|
||||
mappings_[mapping_id]->name[filename_len -
|
||||
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void*
|
||||
LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const 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.
|
||||
|
||||
// Find the AT_SYSINFO_EHDR entry for linux-gate.so
|
||||
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||
// information.
|
||||
int fd = sys_open(auxv_path, O_RDONLY, 0);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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_SYSINFO_EHDR) {
|
||||
close(fd);
|
||||
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
|
||||
char maps_path[NAME_MAX];
|
||||
BuildProcPath(maps_path, pid_, "maps");
|
||||
|
||||
// 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
|
||||
// maps list as a filename, so we use the aux vector to find it's
|
||||
// load location and special case it's entry when creating the list
|
||||
// of mappings.
|
||||
const void* linux_gate_loc;
|
||||
linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
|
||||
|
||||
const int fd = sys_open(maps_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
LineReader* const line_reader = new(allocator_) LineReader(fd);
|
||||
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
while (line_reader->GetNextLine(&line, &line_len)) {
|
||||
uintptr_t start_addr, end_addr, offset;
|
||||
|
||||
const char* i1 = my_read_hex_ptr(&start_addr, line);
|
||||
if (*i1 == '-') {
|
||||
const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
|
||||
if (*i2 == ' ') {
|
||||
const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
|
||||
if (*i3 == ' ') {
|
||||
const char* name = NULL;
|
||||
// Only copy name if the name is a valid path name, or if
|
||||
// it's the VDSO image.
|
||||
if (((name = my_strchr(line, '/')) == NULL) &&
|
||||
linux_gate_loc &&
|
||||
reinterpret_cast<void*>(start_addr) == linux_gate_loc) {
|
||||
name = kLinuxGateLibraryName;
|
||||
offset = 0;
|
||||
}
|
||||
// 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 ((start_addr == module->start_addr + module->size) &&
|
||||
(my_strlen(name) == my_strlen(module->name)) &&
|
||||
(my_strncmp(name, module->name, my_strlen(name)) == 0)) {
|
||||
module->size = end_addr - module->start_addr;
|
||||
line_reader->PopLine(line_len);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MappingInfo* const module = new(allocator_) MappingInfo;
|
||||
memset(module, 0, sizeof(MappingInfo));
|
||||
module->start_addr = start_addr;
|
||||
module->size = end_addr - start_addr;
|
||||
module->offset = offset;
|
||||
if (name != NULL) {
|
||||
const unsigned l = my_strlen(name);
|
||||
if (l < sizeof(module->name))
|
||||
memcpy(module->name, name, l);
|
||||
}
|
||||
result->push_back(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Get information about the stack, given the stack pointer. We don't try to
|
||||
// walk the stack since we might not have all the information needed to do
|
||||
// unwind. So we just grab, up to, 32k of stack.
|
||||
bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
|
||||
uintptr_t int_stack_pointer) {
|
||||
// Move the stack pointer to the bottom of the page that it's in.
|
||||
const uintptr_t page_size = getpagesize();
|
||||
|
||||
uint8_t* const stack_pointer =
|
||||
reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
|
||||
|
||||
// The number of bytes of stack which we try to capture.
|
||||
static const ptrdiff_t kStackToCapture = 32 * 1024;
|
||||
|
||||
const MappingInfo* mapping = FindMapping(stack_pointer);
|
||||
if (!mapping)
|
||||
return false;
|
||||
const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
|
||||
const ptrdiff_t distance_to_end =
|
||||
static_cast<ptrdiff_t>(mapping->size) - offset;
|
||||
*stack_len = distance_to_end > kStackToCapture ?
|
||||
kStackToCapture : distance_to_end;
|
||||
*stack = stack_pointer;
|
||||
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;
|
||||
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
|
||||
if (addr >= start && addr - start < mappings_[i]->size)
|
||||
return mappings_[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool LinuxDumper::HandleDeletedFileInMapping(char* path) const {
|
||||
static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
|
||||
|
||||
// Check for ' (deleted)' in |path|.
|
||||
// |path| has to be at least as long as "/x (deleted)".
|
||||
const size_t path_len = my_strlen(path);
|
||||
if (path_len < kDeletedSuffixLen + 2)
|
||||
return false;
|
||||
if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix,
|
||||
kDeletedSuffixLen) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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)
|
||||
return false;
|
||||
new_path[new_path_len] = '\0';
|
||||
if (my_strcmp(path, new_path) != 0)
|
||||
return false;
|
||||
|
||||
// Check to see if someone actually named their executable 'foo (deleted)'.
|
||||
struct kernel_stat exe_stat;
|
||||
struct kernel_stat new_path_stat;
|
||||
if (sys_stat(exe_link, &exe_stat) == 0 &&
|
||||
sys_stat(new_path, &new_path_stat) == 0 &&
|
||||
exe_stat.st_dev == new_path_stat.st_dev &&
|
||||
exe_stat.st_ino == new_path_stat.st_ino) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(path, exe_link, NAME_MAX);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
193
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper.h
vendored
Normal file
193
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper.h
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2010, 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_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
|
||||
|
||||
#include <elf.h>
|
||||
#include <linux/limits.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#if !defined(__ANDROID__)
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
|
||||
#include "common/memory.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
|
||||
#endif
|
||||
|
||||
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||
#if defined(__i386) || defined(__ARM_EABI__)
|
||||
#if !defined(__ANDROID__)
|
||||
typedef Elf32_auxv_t elf_aux_entry;
|
||||
#else
|
||||
// Android is missing this structure definition
|
||||
typedef struct
|
||||
{
|
||||
uint32_t a_type; /* Entry type */
|
||||
union
|
||||
{
|
||||
uint32_t a_val; /* Integer value */
|
||||
} a_un;
|
||||
} elf_aux_entry;
|
||||
|
||||
#if !defined(AT_SYSINFO_EHDR)
|
||||
#define AT_SYSINFO_EHDR 33
|
||||
#endif
|
||||
#endif // __ANDROID__
|
||||
#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
|
||||
// is the name we use for it when writing it to the minidump.
|
||||
// This should always be less than NAME_MAX!
|
||||
const char kLinuxGateLibraryName[] = "linux-gate.so";
|
||||
|
||||
// We produce one of these structures for each thread in the crashed process.
|
||||
struct ThreadInfo {
|
||||
pid_t tgid; // thread group id
|
||||
pid_t ppid; // parent process
|
||||
|
||||
// Even on platforms where the stack grows down, the following will point to
|
||||
// the smallest address in the stack.
|
||||
const void* stack; // pointer to the stack area
|
||||
size_t stack_len; // length of the stack to copy
|
||||
|
||||
|
||||
#if defined(__i386) || defined(__x86_64)
|
||||
user_regs_struct regs;
|
||||
user_fpregs_struct fpregs;
|
||||
static const unsigned kNumDebugRegisters = 8;
|
||||
debugreg_t dregs[8];
|
||||
#if defined(__i386)
|
||||
user_fpxregs_struct fpxregs;
|
||||
#endif // defined(__i386)
|
||||
|
||||
#elif defined(__ARM_EABI__)
|
||||
// Mimicking how strace does this(see syscall.c, search for GETREGS)
|
||||
#if defined(__ANDROID__)
|
||||
struct pt_regs regs;
|
||||
#else
|
||||
struct user_regs regs;
|
||||
struct user_fpregs fpregs;
|
||||
#endif // __ANDROID__
|
||||
#endif
|
||||
};
|
||||
|
||||
// One of these is produced for each mapping in the process (i.e. line in
|
||||
// /proc/$x/maps).
|
||||
struct MappingInfo {
|
||||
uintptr_t start_addr;
|
||||
size_t size;
|
||||
size_t offset; // offset into the backed file.
|
||||
char name[NAME_MAX];
|
||||
};
|
||||
|
||||
class LinuxDumper {
|
||||
public:
|
||||
explicit LinuxDumper(pid_t pid);
|
||||
|
||||
// Parse the data for |threads| and |mappings|.
|
||||
bool Init();
|
||||
|
||||
// Suspend/resume all threads in the given process.
|
||||
bool ThreadsSuspend();
|
||||
bool ThreadsResume();
|
||||
|
||||
// Read information about the given thread. Returns true on success. One must
|
||||
// have called |ThreadsSuspend| first.
|
||||
bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
|
||||
|
||||
// These are only valid after a call to |Init|.
|
||||
const wasteful_vector<pid_t> &threads() { return threads_; }
|
||||
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
|
||||
const MappingInfo* FindMapping(const void* address) const;
|
||||
|
||||
// Find a block of memory to take as the stack given the top of stack pointer.
|
||||
// stack: (output) the lowest address in the memory area
|
||||
// stack_len: (output) the length of the memory area
|
||||
// stack_top: the current top of the stack
|
||||
bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
|
||||
|
||||
PageAllocator* allocator() { return &allocator_; }
|
||||
|
||||
// memcpy from a remote process.
|
||||
static void CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length);
|
||||
|
||||
// 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;
|
||||
|
||||
// Generate a File ID from the .text section of a mapped entry.
|
||||
// If not a member, mapping_id is ignored.
|
||||
bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
uint8_t identifier[sizeof(MDGUID)]);
|
||||
|
||||
// Utility method to find the location of where the kernel has
|
||||
// mapped linux-gate.so in memory(shows up in /proc/pid/maps as
|
||||
// [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;
|
||||
|
||||
// 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
|
||||
// see if '/path/to/program (deleted)' matches /proc/pid/exe and return
|
||||
// /proc/pid/exe in |path| so ELF identifier generation works correctly. This
|
||||
// also checks to see if '/path/to/program (deleted)' exists, so it does not
|
||||
// get fooled by a poorly named binary.
|
||||
// For programs that don't end with ' (deleted)', this is a no-op.
|
||||
// This assumes |path| is a buffer with length NAME_MAX.
|
||||
// Returns true if |path| is modified.
|
||||
bool HandleDeletedFileInMapping(char* path) const;
|
||||
|
||||
const pid_t pid_;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
|
429
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper_unittest.cc
vendored
Normal file
429
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper_unittest.cc
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
// Copyright (c) 2009, 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 <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/memory.h"
|
||||
|
||||
using std::string;
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test LinuxDumperTest;
|
||||
|
||||
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) {
|
||||
return "";
|
||||
}
|
||||
string helper_path(self_path);
|
||||
size_t pos = helper_path.rfind('/');
|
||||
if (pos == string::npos) {
|
||||
return "";
|
||||
}
|
||||
helper_path.erase(pos + 1);
|
||||
helper_path += "linux_dumper_unittest_helper";
|
||||
|
||||
return helper_path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, Setup) {
|
||||
LinuxDumper dumper(getpid());
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, FindMappings) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
|
||||
ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
|
||||
ASSERT_FALSE(dumper.FindMapping(NULL));
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, ThreadList) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
ASSERT_GE(dumper.threads().size(), (size_t)1);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
if (dumper.threads()[i] == getpid()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper stack class to close a file descriptor and unmap
|
||||
// a mmap'ed mapping.
|
||||
class StackHelper {
|
||||
public:
|
||||
StackHelper(int fd, char* mapping, size_t size)
|
||||
: fd_(fd), mapping_(mapping), size_(size) {}
|
||||
~StackHelper() {
|
||||
munmap(mapping_, size_);
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
char* mapping_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
TEST(LinuxDumperTest, MergedMappings) {
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// mmap two segments out of the helper binary, one
|
||||
// enclosed in the other, but with different protections.
|
||||
const size_t kPageSize = sysconf(_SC_PAGESIZE);
|
||||
const size_t kMappingSize = 3 * kPageSize;
|
||||
int fd = open(helper_path.c_str(), O_RDONLY);
|
||||
ASSERT_NE(-1, fd);
|
||||
char* mapping =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMappingSize,
|
||||
PROT_READ,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0));
|
||||
ASSERT_TRUE(mapping);
|
||||
|
||||
const u_int64_t kMappingAddress = reinterpret_cast<u_int64_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));
|
||||
ASSERT_TRUE(inside_mapping);
|
||||
|
||||
// Now check that LinuxDumper interpreted the mappings properly.
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
int mapping_count = 0;
|
||||
for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
|
||||
const MappingInfo& mapping = *dumper.mappings()[i];
|
||||
if (strcmp(mapping.name, helper_path.c_str()) == 0) {
|
||||
// This mapping should encompass the entire original mapped
|
||||
// range.
|
||||
EXPECT_EQ(kMappingAddress, mapping.start_addr);
|
||||
EXPECT_EQ(kMappingSize, mapping.size);
|
||||
EXPECT_EQ(0, mapping.offset);
|
||||
mapping_count++;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(1, mapping_count);
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 5;
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
// In child process.
|
||||
close(fds[0]);
|
||||
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8];
|
||||
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||
execl(helper_path.c_str(),
|
||||
"linux_dumper_unittest_helper",
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument,
|
||||
NULL);
|
||||
// Kill if we get here.
|
||||
printf("Errno from exec: %d", errno);
|
||||
FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
|
||||
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));
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
LinuxDumper 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));
|
||||
// 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__)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.uregs[3]);
|
||||
#elif defined(__i386)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.ecx);
|
||||
#elif defined(__x86_64)
|
||||
pid_t *process_tid_location = (pid_t *)(one_thread.regs.rcx);
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
pid_t one_thread_id;
|
||||
dumper.CopyFromProcess(&one_thread_id,
|
||||
dumper.threads()[i],
|
||||
process_tid_location,
|
||||
4);
|
||||
EXPECT_EQ(dumper.threads()[i], one_thread_id);
|
||||
}
|
||||
kill(child_pid, SIGKILL);
|
||||
}
|
||||
|
||||
TEST(LinuxDumperTest, BuildProcPath) {
|
||||
const pid_t pid = getpid();
|
||||
LinuxDumper dumper(pid);
|
||||
|
||||
char maps_path[256] = "dummymappath";
|
||||
char maps_path_expected[256];
|
||||
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);
|
||||
|
||||
// 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, "");
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#if !defined(__ARM_EABI__)
|
||||
// Ensure that the linux-gate VDSO is included in the mapping list.
|
||||
TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
|
||||
ASSERT_TRUE(linux_gate_loc);
|
||||
bool found_linux_gate = false;
|
||||
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
const MappingInfo* mapping;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found_linux_gate);
|
||||
EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
|
||||
EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
|
||||
}
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingID) {
|
||||
LinuxDumper dumper(getpid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_linux_gate);
|
||||
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
|
||||
true,
|
||||
index,
|
||||
identifier));
|
||||
uint8_t empty_identifier[sizeof(MDGUID)];
|
||||
memset(empty_identifier, 0, sizeof(empty_identifier));
|
||||
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
|
||||
}
|
||||
|
||||
// Ensure that the linux-gate VDSO can generate a non-zeroed File ID
|
||||
// from a child process.
|
||||
TEST(LinuxDumperTest, LinuxGateMappingIDChild) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// Fork a child so ptrace works.
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
// Now wait forever for the parent.
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
bool found_linux_gate = false;
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < mappings.size(); ++i) {
|
||||
if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
|
||||
found_linux_gate = true;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_linux_gate);
|
||||
|
||||
// Need to suspend the child so ptrace actually works.
|
||||
ASSERT_TRUE(dumper.ThreadsSuspend());
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
|
||||
true,
|
||||
index,
|
||||
identifier));
|
||||
uint8_t empty_identifier[sizeof(MDGUID)];
|
||||
memset(empty_identifier, 0, sizeof(empty_identifier));
|
||||
EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
close(fds[1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LinuxDumperTest, 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';
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// Fork a child so ptrace works.
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
// Now wait forever for the parent.
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
LinuxDumper dumper(child);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
|
||||
bool found_exe = false;
|
||||
unsigned i;
|
||||
for (i = 0; i < mappings.size(); ++i) {
|
||||
const MappingInfo* mapping = mappings[i];
|
||||
if (!strcmp(mapping->name, exe_name)) {
|
||||
found_exe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_exe);
|
||||
|
||||
uint8_t identifier1[sizeof(MDGUID)];
|
||||
uint8_t identifier2[sizeof(MDGUID)];
|
||||
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
|
||||
identifier1));
|
||||
FileID fileid(exe_name);
|
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
|
||||
char identifier_string1[37];
|
||||
char identifier_string2[37];
|
||||
FileID::ConvertIdentifierToString(identifier1, identifier_string1,
|
||||
37);
|
||||
FileID::ConvertIdentifierToString(identifier2, identifier_string2,
|
||||
37);
|
||||
EXPECT_STREQ(identifier_string1, identifier_string2);
|
||||
close(fds[1]);
|
||||
}
|
85
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
vendored
Normal file
85
thirdparty/breakpad/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2010, 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.
|
||||
//
|
||||
// Helper program for the linux_dumper class, which creates a bunch of
|
||||
// threads. The first word of each thread's stack is set to the thread
|
||||
// id.
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
#if defined(__ARM_EABI__)
|
||||
#define TID_PTR_REGISTER "r3"
|
||||
#elif defined(__i386)
|
||||
#define TID_PTR_REGISTER "ecx"
|
||||
#elif defined(__x86_64)
|
||||
#define TID_PTR_REGISTER "rcx"
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
|
||||
void *thread_function(void *data) {
|
||||
volatile pid_t thread_id = syscall(__NR_gettid);
|
||||
register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = &thread_id;
|
||||
while (true)
|
||||
asm volatile ("" : : "r" (thread_id_ptr));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr,
|
||||
"usage: linux_dumper_unittest_helper <pipe fd> <# of threads\n");
|
||||
return 1;
|
||||
}
|
||||
int pipefd = atoi(argv[1]);
|
||||
int num_threads = atoi(argv[2]);
|
||||
if (num_threads < 1) {
|
||||
fprintf(stderr, "ERROR: number of threads is 0");
|
||||
return 1;
|
||||
}
|
||||
pthread_t threads[num_threads];
|
||||
pthread_attr_t thread_attributes;
|
||||
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);
|
||||
}
|
||||
// Signal parent that this process has started all threads.
|
||||
uint8_t byte = 1;
|
||||
write(pipefd, &byte, sizeof(byte));
|
||||
thread_function(NULL);
|
||||
return 0;
|
||||
}
|
75
thirdparty/breakpad/client/linux/minidump_writer/minidump_extension_linux.h
vendored
Normal file
75
thirdparty/breakpad/client/linux/minidump_writer/minidump_extension_linux.h
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2010, 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. */
|
||||
|
||||
/* minidump_extension_linux.h: A definition of exception codes for
|
||||
* Linux
|
||||
*
|
||||
* (This is C99 source, please don't corrupt it with C++.)
|
||||
*
|
||||
* Author: Adam Langley
|
||||
* Split into its own file: Markus Gutschke */
|
||||
|
||||
|
||||
#ifndef SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
||||
#define SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "google_breakpad/common/breakpad_types.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
// These are additional minidump stream values which are specific to the linux
|
||||
// breakpad implementation.
|
||||
enum {
|
||||
MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
|
||||
MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
|
||||
MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
|
||||
MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
|
||||
MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
|
||||
MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
|
||||
MD_LINUX_MAPS = 0x47670009, /* /proc/$x/maps */
|
||||
MD_LINUX_DSO_DEBUG = 0x4767000A /* DSO data */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
void* addr;
|
||||
MDRVA name;
|
||||
void* ld;
|
||||
} MDRawLinkMap;
|
||||
|
||||
typedef struct {
|
||||
u_int32_t version;
|
||||
MDRVA map;
|
||||
u_int32_t dso_count;
|
||||
void* brk;
|
||||
void* ldbase;
|
||||
void* dynamic;
|
||||
} MDRawDebug;
|
||||
|
||||
#endif // SRC_CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_EXTENSION_LINUX_H_
|
1319
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer.cc
vendored
Normal file
1319
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer.cc
vendored
Normal file
File diff suppressed because it is too large
Load Diff
67
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer.h
vendored
Normal file
67
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer.h
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2009, 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_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A list of <MappingInfo, GUID>
|
||||
typedef std::pair<struct MappingInfo, u_int8_t[sizeof(MDGUID)]> MappingEntry;
|
||||
typedef std::list<MappingEntry> MappingList;
|
||||
|
||||
// Write a minidump to the filesystem. This function does not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
// filename: the filename to write to. This is opened O_EXCL and fails if
|
||||
// open fails.
|
||||
// crashing_process: the pid of the crashing process. This must be trusted.
|
||||
// blob: a blob of data from the crashing process. See exception_handler.h
|
||||
// blob_size: the length of |blob|, in bytes
|
||||
//
|
||||
// Returns true iff successful.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size);
|
||||
|
||||
// This overload also allows passing a list of known mappings.
|
||||
bool WriteMinidump(const char* filename, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
396
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer_unittest.cc
vendored
Normal file
396
thirdparty/breakpad/client/linux/minidump_writer/minidump_writer_unittest.cc
vendored
Normal file
@@ -0,0 +1,396 @@
|
||||
// Copyright (c) 2011 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 <fcntl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.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;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test MinidumpWriterTest;
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, Setup) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
unlink(templ);
|
||||
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
// Test that mapping info can be specified when writing a minidump,
|
||||
// and that it ends up in the module list of the minidump.
|
||||
TEST(MinidumpWriterTest, MappingInfo) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
char module_identifier_buffer[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||
module_identifier_buffer,
|
||||
sizeof(module_identifier_buffer));
|
||||
string module_identifier(module_identifier_buffer);
|
||||
// Strip out dashes
|
||||
size_t pos;
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
|
||||
// Get some memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
|
||||
// Add information about the mapped memory.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress;
|
||||
info.size = kMemorySize;
|
||||
info.offset = 0;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module =
|
||||
module_list->GetModuleForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(module);
|
||||
|
||||
EXPECT_EQ(kMemoryAddress, module->base_address());
|
||||
EXPECT_EQ(kMemorySize, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
// Test that mapping info can be specified, and that it overrides
|
||||
// existing mappings that are wholly contained within the specified
|
||||
// range.
|
||||
TEST(MinidumpWriterTest, MappingInfoContained) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
// These are defined here so the parent can use them to check the
|
||||
// data from the minidump afterwards.
|
||||
const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
char module_identifier_buffer[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(kModuleGUID,
|
||||
module_identifier_buffer,
|
||||
sizeof(module_identifier_buffer));
|
||||
string module_identifier(module_identifier_buffer);
|
||||
// Strip out dashes
|
||||
size_t pos;
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
|
||||
// mmap a file
|
||||
char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
|
||||
mktemp(tempfile);
|
||||
int fd = open(tempfile, O_RDWR | O_CREAT, 0);
|
||||
ASSERT_NE(-1, fd);
|
||||
unlink(tempfile);
|
||||
// fill with zeros
|
||||
char buffer[kMemorySize];
|
||||
memset(buffer, 0, kMemorySize);
|
||||
ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMemorySize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE,
|
||||
fd,
|
||||
0));
|
||||
const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
close(fd);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(dumpfile);
|
||||
|
||||
// Add information about the mapped memory. Report it as being larger than
|
||||
// it actually is.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress - kMemorySize;
|
||||
info.size = kMemorySize * 3;
|
||||
info.offset = 0;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(
|
||||
WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the mmap'ed |memory| is listed with the given module name
|
||||
// and debug ID.
|
||||
Minidump minidump(dumpfile);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module =
|
||||
module_list->GetModuleForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(module);
|
||||
|
||||
EXPECT_EQ(info.start_addr, module->base_address());
|
||||
EXPECT_EQ(info.size, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(dumpfile);
|
||||
close(fds[1]);
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 1;
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
|
||||
|
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX];
|
||||
if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
|
||||
FAIL() << "readlink failed: " << strerror(errno);
|
||||
exit(1);
|
||||
}
|
||||
string helper_path(self_path);
|
||||
size_t pos = helper_path.rfind('/');
|
||||
if (pos == string::npos) {
|
||||
FAIL() << "no trailing slash in path: " << helper_path;
|
||||
exit(1);
|
||||
}
|
||||
helper_path.erase(pos + 1);
|
||||
helper_path += "linux_dumper_unittest_helper";
|
||||
|
||||
// Copy binary to a temp file.
|
||||
char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
|
||||
mktemp(binpath);
|
||||
char cmdline[2 * PATH_MAX];
|
||||
sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
|
||||
ASSERT_EQ(0, system(cmdline));
|
||||
ASSERT_EQ(0, chmod(binpath, 0755));
|
||||
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (child_pid == 0) {
|
||||
// In child process.
|
||||
close(fds[0]);
|
||||
|
||||
// Pass the pipe fd and the number of threads as arguments.
|
||||
char pipe_fd_string[8];
|
||||
sprintf(pipe_fd_string, "%d", fds[1]);
|
||||
execl(binpath,
|
||||
binpath,
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument,
|
||||
NULL);
|
||||
}
|
||||
close(fds[1]);
|
||||
// Wait for the child process to signal that it's ready.
|
||||
struct pollfd pfd;
|
||||
memset(&pfd, 0, sizeof(pfd));
|
||||
pfd.fd = fds[0];
|
||||
pfd.events = POLLIN | POLLERR;
|
||||
|
||||
const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
|
||||
ASSERT_EQ(1, r);
|
||||
ASSERT_TRUE(pfd.revents & POLLIN);
|
||||
uint8_t junk;
|
||||
read(fds[0], &junk, sizeof(junk));
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
// Unlink the test binary.
|
||||
unlink(binpath);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
|
||||
mktemp(templ);
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = 1;
|
||||
ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
|
||||
kill(child_pid, SIGKILL);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(stat(templ, &st), 0);
|
||||
ASSERT_GT(st.st_size, 0u);
|
||||
|
||||
|
||||
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
// Check that the main module filename is correct.
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module = module_list->GetMainModule();
|
||||
EXPECT_STREQ(binpath, module->code_file().c_str());
|
||||
// Check that the file ID is correct.
|
||||
FileID fileid(helper_path.c_str());
|
||||
uint8_t identifier[sizeof(MDGUID)];
|
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
|
||||
char identifier_string[kGUIDStringSize];
|
||||
FileID::ConvertIdentifierToString(identifier,
|
||||
identifier_string,
|
||||
kGUIDStringSize);
|
||||
string module_identifier(identifier_string);
|
||||
// Strip out dashes
|
||||
while ((pos = module_identifier.find('-')) != string::npos) {
|
||||
module_identifier.erase(pos, 1);
|
||||
}
|
||||
// And append a zero, because module IDs include an "age" field
|
||||
// which is always zero on Linux.
|
||||
module_identifier += "0";
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
unlink(templ);
|
||||
}
|
Reference in New Issue
Block a user