mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-23 22:35:45 +00:00
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
parent
c0cceff365
commit
6c655321e6
4081 changed files with 1185566 additions and 45 deletions
143
externals/breakpad/src/client/linux/minidump_writer/cpu_set.h
vendored
Normal file
143
externals/breakpad/src/client/linux/minidump_writer/cpu_set.h
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// 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 LLC 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_CPU_SET_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Helper class used to model a set of CPUs, as read from sysfs
|
||||
// files like /sys/devices/system/cpu/present
|
||||
// See See http://www.kernel.org/doc/Documentation/cputopology.txt
|
||||
class CpuSet {
|
||||
public:
|
||||
// The maximum number of supported CPUs.
|
||||
static const size_t kMaxCpus = 1024;
|
||||
|
||||
CpuSet() {
|
||||
my_memset(mask_, 0, sizeof(mask_));
|
||||
}
|
||||
|
||||
// Parse a sysfs file to extract the corresponding CPU set.
|
||||
bool ParseSysFile(int fd) {
|
||||
char buffer[512];
|
||||
int ret = sys_read(fd, buffer, sizeof(buffer)-1);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
buffer[ret] = '\0';
|
||||
|
||||
// Expected format: comma-separated list of items, where each
|
||||
// item can be a decimal integer, or two decimal integers separated
|
||||
// by a dash.
|
||||
// E.g.:
|
||||
// 0
|
||||
// 0,1,2,3
|
||||
// 0-3
|
||||
// 1,10-23
|
||||
const char* p = buffer;
|
||||
const char* p_end = p + ret;
|
||||
while (p < p_end) {
|
||||
// Skip leading space, if any
|
||||
while (p < p_end && my_isspace(*p))
|
||||
p++;
|
||||
|
||||
// Find start and size of current item.
|
||||
const char* item = p;
|
||||
size_t item_len = static_cast<size_t>(p_end - p);
|
||||
const char* item_next =
|
||||
static_cast<const char*>(my_memchr(p, ',', item_len));
|
||||
if (item_next != NULL) {
|
||||
p = item_next + 1;
|
||||
item_len = static_cast<size_t>(item_next - item);
|
||||
} else {
|
||||
p = p_end;
|
||||
item_next = p_end;
|
||||
}
|
||||
|
||||
// Ignore trailing spaces.
|
||||
while (item_next > item && my_isspace(item_next[-1]))
|
||||
item_next--;
|
||||
|
||||
// skip empty items.
|
||||
if (item_next == item)
|
||||
continue;
|
||||
|
||||
// read first decimal value.
|
||||
uintptr_t start = 0;
|
||||
const char* next = my_read_decimal_ptr(&start, item);
|
||||
uintptr_t end = start;
|
||||
if (*next == '-')
|
||||
my_read_decimal_ptr(&end, next+1);
|
||||
|
||||
while (start <= end)
|
||||
SetBit(start++);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intersect this CPU set with another one.
|
||||
void IntersectWith(const CpuSet& other) {
|
||||
for (size_t nn = 0; nn < kMaskWordCount; ++nn)
|
||||
mask_[nn] &= other.mask_[nn];
|
||||
}
|
||||
|
||||
// Return the number of CPUs in this set.
|
||||
int GetCount() {
|
||||
int result = 0;
|
||||
for (size_t nn = 0; nn < kMaskWordCount; ++nn) {
|
||||
result += __builtin_popcount(mask_[nn]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetBit(uintptr_t index) {
|
||||
size_t nn = static_cast<size_t>(index);
|
||||
if (nn < kMaxCpus)
|
||||
mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits));
|
||||
}
|
||||
|
||||
typedef uint32_t MaskWordType;
|
||||
static const size_t kMaskWordBits = 8*sizeof(MaskWordType);
|
||||
static const size_t kMaskWordCount =
|
||||
(kMaxCpus + kMaskWordBits - 1) / kMaskWordBits;
|
||||
|
||||
MaskWordType mask_[kMaskWordCount];
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_
|
163
externals/breakpad/src/client/linux/minidump_writer/cpu_set_unittest.cc
vendored
Normal file
163
externals/breakpad/src/client/linux/minidump_writer/cpu_set_unittest.cc
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/cpu_set.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
|
||||
typedef testing::Test CpuSetTest;
|
||||
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, EmptyCount) {
|
||||
CpuSet set;
|
||||
ASSERT_EQ(0, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, OneCpu) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("10"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(1, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, OneCpuTerminated) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("10\n"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(1, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, TwoCpusWithComma) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("1,10"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(2, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, TwoCpusWithRange) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("1-2"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(2, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, TenCpusWithRange) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("9-18"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(10, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, MultiItems) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("0, 2-4, 128"));
|
||||
|
||||
CpuSet set;
|
||||
ASSERT_TRUE(set.ParseSysFile(file.GetFd()));
|
||||
ASSERT_EQ(5, set.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, IntersectWith) {
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("9-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(11, set1.GetCount());
|
||||
|
||||
ScopedTmpFile file2;
|
||||
ASSERT_TRUE(file2.InitString("16-24"));
|
||||
|
||||
CpuSet set2;
|
||||
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
|
||||
ASSERT_EQ(9, set2.GetCount());
|
||||
|
||||
set1.IntersectWith(set2);
|
||||
ASSERT_EQ(4, set1.GetCount());
|
||||
ASSERT_EQ(9, set2.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, SelfIntersection) {
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("9-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(11, set1.GetCount());
|
||||
|
||||
set1.IntersectWith(set1);
|
||||
ASSERT_EQ(11, set1.GetCount());
|
||||
}
|
||||
|
||||
TEST(CpuSetTest, EmptyIntersection) {
|
||||
ScopedTmpFile file1;
|
||||
ASSERT_TRUE(file1.InitString("0-19"));
|
||||
|
||||
CpuSet set1;
|
||||
ASSERT_TRUE(set1.ParseSysFile(file1.GetFd()));
|
||||
ASSERT_EQ(20, set1.GetCount());
|
||||
|
||||
ScopedTmpFile file2;
|
||||
ASSERT_TRUE(file2.InitString("20-39"));
|
||||
|
||||
CpuSet set2;
|
||||
ASSERT_TRUE(set2.ParseSysFile(file2.GetFd()));
|
||||
ASSERT_EQ(20, set2.GetCount());
|
||||
|
||||
set1.IntersectWith(set2);
|
||||
ASSERT_EQ(0, set1.GetCount());
|
||||
|
||||
ASSERT_EQ(20, set2.GetCount());
|
||||
}
|
||||
|
105
externals/breakpad/src/client/linux/minidump_writer/directory_reader.h
vendored
Normal file
105
externals/breakpad/src/client/linux/minidump_writer/directory_reader.h
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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 "common/linux/linux_libc_support.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;
|
||||
my_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_
|
81
externals/breakpad/src/client/linux/minidump_writer/directory_reader_unittest.cc
vendored
Normal file
81
externals/breakpad/src/client/linux/minidump_writer/directory_reader_unittest.cc
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/directory_reader.h"
|
||||
#include "common/using_std_string.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
typedef testing::Test DirectoryReaderTest;
|
||||
}
|
||||
|
||||
TEST(DirectoryReaderTest, CompareResults) {
|
||||
std::set<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
externals/breakpad/src/client/linux/minidump_writer/line_reader.h
vendored
Normal file
130
externals/breakpad/src/client/linux/minidump_writer/line_reader.h
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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 "common/linux/linux_libc_support.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;
|
||||
my_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_
|
161
externals/breakpad/src/client/linux/minidump_writer/line_reader_unittest.cc
vendored
Normal file
161
externals/breakpad/src/client/linux/minidump_writer/line_reader_unittest.cc
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
|
||||
typedef testing::Test LineReaderTest;
|
||||
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, EmptyFile) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(""));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLineTerminated) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\n"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned int len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, OneLine) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ((unsigned)1, len);
|
||||
ASSERT_EQ('a', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLinesTerminated) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\nb\n"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
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((unsigned)1, len);
|
||||
ASSERT_EQ('b', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TwoLines) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("a\nb"));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
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((unsigned)1, len);
|
||||
ASSERT_EQ('b', line[0]);
|
||||
ASSERT_EQ('\0', line[1]);
|
||||
reader.PopLine(len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, MaxLength) {
|
||||
char l[LineReader::kMaxLineLen-1];
|
||||
memset(l, 'a', sizeof(l));
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitData(l, sizeof(l)));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_TRUE(reader.GetNextLine(&line, &len));
|
||||
ASSERT_EQ(sizeof(l), len);
|
||||
ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
|
||||
ASSERT_EQ('\0', line[len]);
|
||||
}
|
||||
|
||||
TEST(LineReaderTest, TooLong) {
|
||||
// Note: this writes kMaxLineLen 'a' chars in the test file.
|
||||
char l[LineReader::kMaxLineLen];
|
||||
memset(l, 'a', sizeof(l));
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitData(l, sizeof(l)));
|
||||
LineReader reader(file.GetFd());
|
||||
|
||||
const char* line;
|
||||
unsigned len;
|
||||
ASSERT_FALSE(reader.GetNextLine(&line, &len));
|
||||
}
|
321
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper.cc
vendored
Normal file
321
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper.cc
vendored
Normal file
|
@ -0,0 +1,321 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#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>
|
||||
#if defined(__mips__) && defined(__ANDROID__)
|
||||
// To get register definitions.
|
||||
#include <asm/reg.h>
|
||||
#endif
|
||||
|
||||
#include "common/linux/elf_gnu_compat.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
LinuxCoreDumper::LinuxCoreDumper(pid_t pid,
|
||||
const char* core_path,
|
||||
const char* procfs_path,
|
||||
const char* root_prefix)
|
||||
: LinuxDumper(pid, root_prefix),
|
||||
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;
|
||||
}
|
||||
|
||||
bool 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);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
#elif defined(__aarch64__)
|
||||
memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
|
||||
#elif defined(__mips__)
|
||||
stack_pointer =
|
||||
reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
|
||||
#elif defined(__riscv)
|
||||
stack_pointer = reinterpret_cast<uint8_t*>(
|
||||
info->mcontext.__gregs[MD_CONTEXT_RISCV_REG_SP]);
|
||||
#else
|
||||
# error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
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_, 0)) {
|
||||
fprintf(stderr, "Could not map core dump file into memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
char proc_mem_path[NAME_MAX];
|
||||
if (BuildProcPath(proc_mem_path, pid_, "mem")) {
|
||||
int fd = open(proc_mem_path, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
|
||||
if (fd != -1) {
|
||||
core_.SetProcMem(fd);
|
||||
} else {
|
||||
fprintf(stderr, "Cannot open %s (%s)\n", proc_mem_path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
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_SIGINFO
|
||||
// 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;
|
||||
#if defined(__mips__)
|
||||
# if defined(__ANDROID__)
|
||||
for (int i = EF_R0; i <= EF_R31; i++)
|
||||
info.mcontext.gregs[i - EF_R0] = status->pr_reg[i];
|
||||
# else // __ANDROID__
|
||||
for (int i = EF_REG0; i <= EF_REG31; i++)
|
||||
info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i];
|
||||
# endif // __ANDROID__
|
||||
info.mcontext.mdlo = status->pr_reg[EF_LO];
|
||||
info.mcontext.mdhi = status->pr_reg[EF_HI];
|
||||
info.mcontext.pc = status->pr_reg[EF_CP0_EPC];
|
||||
#elif defined(__riscv)
|
||||
memcpy(&info.mcontext.__gregs, status->pr_reg,
|
||||
sizeof(info.mcontext.__gregs));
|
||||
#else // __riscv
|
||||
memcpy(&info.regs, status->pr_reg, sizeof(info.regs));
|
||||
#endif
|
||||
if (first_thread) {
|
||||
crash_thread_ = pid;
|
||||
crash_signal_ = status->pr_info.si_signo;
|
||||
crash_signal_code_ = status->pr_info.si_code;
|
||||
}
|
||||
first_thread = false;
|
||||
threads_.push_back(pid);
|
||||
thread_infos_.push_back(info);
|
||||
break;
|
||||
}
|
||||
case NT_SIGINFO: {
|
||||
if (description.length() != sizeof(siginfo_t)) {
|
||||
fprintf(stderr, "Found NT_SIGINFO descriptor of unexpected size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const siginfo_t* info =
|
||||
reinterpret_cast<const siginfo_t*>(description.data());
|
||||
|
||||
// Set crash_address when si_addr is valid for the signal.
|
||||
switch (info->si_signo) {
|
||||
case MD_EXCEPTION_CODE_LIN_SIGBUS:
|
||||
case MD_EXCEPTION_CODE_LIN_SIGFPE:
|
||||
case MD_EXCEPTION_CODE_LIN_SIGILL:
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSEGV:
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSYS:
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTRAP:
|
||||
crash_address_ = reinterpret_cast<uintptr_t>(info->si_addr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set crash_exception_info for common signals. Since exception info is
|
||||
// unsigned, but some of these fields might be signed, we always cast.
|
||||
switch (info->si_signo) {
|
||||
case MD_EXCEPTION_CODE_LIN_SIGKILL:
|
||||
set_crash_exception_info({
|
||||
static_cast<uint64_t>(info->si_pid),
|
||||
static_cast<uint64_t>(info->si_uid),
|
||||
});
|
||||
break;
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSYS:
|
||||
#ifdef si_syscall
|
||||
set_crash_exception_info({
|
||||
static_cast<uint64_t>(info->si_syscall),
|
||||
static_cast<uint64_t>(info->si_arch),
|
||||
});
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
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
|
124
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper.h
vendored
Normal file
124
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper.h
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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
|
||||
// See LinuxDumper for the purpose of |root_prefix|.
|
||||
LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path,
|
||||
const char* root_prefix = "");
|
||||
|
||||
// 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. Returns true if
|
||||
// the expected data is found in the core dump.
|
||||
virtual bool 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_
|
195
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
vendored
Normal file
195
externals/breakpad/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_core_dumper.h"
|
||||
#include "common/linux/tests/crash_generator.h"
|
||||
#include "common/using_std_string.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
TEST(LinuxCoreDumperTest, GetMappingAbsolutePath) {
|
||||
const LinuxCoreDumper dumper(getpid(), "core", "/tmp", "/mnt/root");
|
||||
const MappingInfo mapping = {0, 0, {0, 0}, 0, false, "/usr/lib/libc.so"};
|
||||
|
||||
char path[PATH_MAX];
|
||||
dumper.GetMappingAbsolutePath(mapping, path);
|
||||
|
||||
EXPECT_STREQ("/mnt/root/usr/lib/libc.so", path);
|
||||
}
|
||||
|
||||
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;
|
||||
ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
|
||||
kCrashSignal, &child_pid));
|
||||
|
||||
const string core_file = crash_generator.GetCoreFilePath();
|
||||
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
struct stat st;
|
||||
if (stat(core_file.c_str(), &st) != 0) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
|
||||
"skipped due to no core file being generated\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
|
||||
|
||||
EXPECT_TRUE(dumper.Init());
|
||||
|
||||
EXPECT_TRUE(dumper.IsPostMortem());
|
||||
|
||||
// These are no-ops and should always return true.
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
|
||||
// Linux does not set the crash address with SIGABRT, so make sure it always
|
||||
// sets the crash address to 0.
|
||||
EXPECT_EQ(0U, dumper.crash_address());
|
||||
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
|
||||
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
|
||||
dumper.crash_thread());
|
||||
|
||||
#if defined(THREAD_SANITIZER)
|
||||
EXPECT_GE(dumper.threads().size(), kNumOfThreads);
|
||||
#else
|
||||
EXPECT_EQ(dumper.threads().size(), kNumOfThreads);
|
||||
#endif
|
||||
for (unsigned i = 0; i < kNumOfThreads; ++i) {
|
||||
ThreadInfo info;
|
||||
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info));
|
||||
const void* stack;
|
||||
size_t stack_len;
|
||||
EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, info.stack_pointer));
|
||||
EXPECT_EQ(getpid(), info.ppid);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LinuxCoreDumperTest, VerifyExceptionDetails) {
|
||||
CrashGenerator crash_generator;
|
||||
if (!crash_generator.HasDefaultCorePattern()) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test "
|
||||
"is skipped due to non-default core pattern\n");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef si_syscall
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is "
|
||||
"skipped due to old kernel/C library headers\n");
|
||||
return;
|
||||
#endif
|
||||
|
||||
const unsigned kNumOfThreads = 2;
|
||||
const unsigned kCrashThread = 1;
|
||||
const int kCrashSignal = SIGSYS;
|
||||
pid_t child_pid;
|
||||
ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread,
|
||||
kCrashSignal, &child_pid));
|
||||
|
||||
const string core_file = crash_generator.GetCoreFilePath();
|
||||
const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy();
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
struct stat st;
|
||||
if (stat(core_file.c_str(), &st) != 0) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
|
||||
"skipped due to no core file being generated\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str());
|
||||
|
||||
EXPECT_TRUE(dumper.Init());
|
||||
|
||||
EXPECT_TRUE(dumper.IsPostMortem());
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// TODO: For some reason, Android doesn't seem to pass this.
|
||||
if (!dumper.crash_address()) {
|
||||
fprintf(stderr, "LinuxCoreDumperTest.VerifyExceptionDetails test is "
|
||||
"skipped due to missing signal details on Android\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check the exception details.
|
||||
EXPECT_NE(0U, dumper.crash_address());
|
||||
EXPECT_EQ(kCrashSignal, dumper.crash_signal());
|
||||
EXPECT_EQ(crash_generator.GetThreadId(kCrashThread),
|
||||
dumper.crash_thread());
|
||||
|
||||
// We check the length, but not the actual fields. We sent SIGSYS ourselves
|
||||
// instead of the kernel, so the extended fields are garbage.
|
||||
const std::vector<uint64_t> info(dumper.crash_exception_info());
|
||||
EXPECT_EQ(2U, info.size());
|
||||
}
|
974
externals/breakpad/src/client/linux/minidump_writer/linux_dumper.cc
vendored
Normal file
974
externals/breakpad/src/client/linux/minidump_writer/linux_dumper.cc
vendored
Normal file
|
@ -0,0 +1,974 @@
|
|||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// 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 LLC 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_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
|
||||
// use the alternative allocator.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/elfutils.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 "google_breakpad/common/minidump_exception_linux.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
using google_breakpad::elf::FileID;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
// Android packed relocations definitions are not yet available from the
|
||||
// NDK header files, so we have to provide them manually here.
|
||||
#ifndef DT_LOOS
|
||||
#define DT_LOOS 0x6000000d
|
||||
#endif
|
||||
#ifndef DT_ANDROID_REL
|
||||
static const int DT_ANDROID_REL = DT_LOOS + 2;
|
||||
#endif
|
||||
#ifndef DT_ANDROID_RELA
|
||||
static const int DT_ANDROID_RELA = DT_LOOS + 4;
|
||||
#endif
|
||||
|
||||
#endif // __ANDROID __
|
||||
|
||||
static const char kMappedFileUnsafePrefix[] = "/dev/";
|
||||
static const char kDeletedSuffix[] = " (deleted)";
|
||||
|
||||
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 {
|
||||
|
||||
namespace {
|
||||
|
||||
bool MappingContainsAddress(const MappingInfo& mapping, uintptr_t address) {
|
||||
return mapping.system_mapping_info.start_addr <= address &&
|
||||
address < mapping.system_mapping_info.end_addr;
|
||||
}
|
||||
|
||||
#if defined(__CHROMEOS__)
|
||||
|
||||
// Recover memory mappings before writing dump on ChromeOS
|
||||
//
|
||||
// On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from
|
||||
// addresses. ChromeOS' hugepage implementation replaces some segments with
|
||||
// anonymous private pages, which is a restriction of current implementation
|
||||
// in Linux kernel at the time of writing. Thus, breakpad can no longer
|
||||
// symbolize addresses from those text segments replaced with hugepages.
|
||||
//
|
||||
// This postprocess tries to recover the mappings. Because hugepages are always
|
||||
// inserted in between some .text sections, it tries to infer the names and
|
||||
// offsets of the segments, by looking at segments immediately precede and
|
||||
// succeed them.
|
||||
//
|
||||
// For example, a text segment before hugepage optimization
|
||||
// 02001000-03002000 r-xp /opt/google/chrome/chrome
|
||||
//
|
||||
// can be broken into
|
||||
// 02001000-02200000 r-xp /opt/google/chrome/chrome
|
||||
// 02200000-03000000 r-xp
|
||||
// 03000000-03002000 r-xp /opt/google/chrome/chrome
|
||||
//
|
||||
// For more details, see:
|
||||
// crbug.com/628040 ChromeOS' use of hugepages confuses crash symbolization
|
||||
|
||||
// Copied from CrOS' hugepage implementation, which is unlikely to change.
|
||||
// The hugepage size is 2M.
|
||||
const unsigned int kHpageShift = 21;
|
||||
const size_t kHpageSize = (1 << kHpageShift);
|
||||
const size_t kHpageMask = (~(kHpageSize - 1));
|
||||
|
||||
// Find and merge anonymous r-xp segments with surrounding named segments.
|
||||
// There are two cases:
|
||||
|
||||
// Case 1: curr, next
|
||||
// curr is anonymous
|
||||
// curr is r-xp
|
||||
// curr.size >= 2M
|
||||
// curr.size is a multiple of 2M.
|
||||
// next is backed by some file.
|
||||
// curr and next are contiguous.
|
||||
// offset(next) == sizeof(curr)
|
||||
void TryRecoverMappings(MappingInfo* curr, MappingInfo* next) {
|
||||
// Merged segments are marked with size = 0.
|
||||
if (curr->size == 0 || next->size == 0)
|
||||
return;
|
||||
|
||||
if (curr->size >= kHpageSize &&
|
||||
curr->exec &&
|
||||
(curr->size & kHpageMask) == curr->size &&
|
||||
(curr->start_addr & kHpageMask) == curr->start_addr &&
|
||||
curr->name[0] == '\0' &&
|
||||
next->name[0] != '\0' &&
|
||||
curr->start_addr + curr->size == next->start_addr &&
|
||||
curr->size == next->offset) {
|
||||
|
||||
// matched
|
||||
my_strlcpy(curr->name, next->name, NAME_MAX);
|
||||
if (next->exec) {
|
||||
// (curr, next)
|
||||
curr->size += next->size;
|
||||
next->size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: prev, curr, next
|
||||
// curr is anonymous
|
||||
// curr is r-xp
|
||||
// curr.size >= 2M
|
||||
// curr.size is a multiple of 2M.
|
||||
// next and prev are backed by the same file.
|
||||
// prev, curr and next are contiguous.
|
||||
// offset(next) == offset(prev) + sizeof(prev) + sizeof(curr)
|
||||
void TryRecoverMappings(MappingInfo* prev, MappingInfo* curr,
|
||||
MappingInfo* next) {
|
||||
// Merged segments are marked with size = 0.
|
||||
if (prev->size == 0 || curr->size == 0 || next->size == 0)
|
||||
return;
|
||||
|
||||
if (curr->size >= kHpageSize &&
|
||||
curr->exec &&
|
||||
(curr->size & kHpageMask) == curr->size &&
|
||||
(curr->start_addr & kHpageMask) == curr->start_addr &&
|
||||
curr->name[0] == '\0' &&
|
||||
next->name[0] != '\0' &&
|
||||
curr->start_addr + curr->size == next->start_addr &&
|
||||
prev->start_addr + prev->size == curr->start_addr &&
|
||||
my_strncmp(prev->name, next->name, NAME_MAX) == 0 &&
|
||||
next->offset == prev->offset + prev->size + curr->size) {
|
||||
|
||||
// matched
|
||||
my_strlcpy(curr->name, prev->name, NAME_MAX);
|
||||
if (prev->exec) {
|
||||
curr->offset = prev->offset;
|
||||
curr->start_addr = prev->start_addr;
|
||||
if (next->exec) {
|
||||
// (prev, curr, next)
|
||||
curr->size += prev->size + next->size;
|
||||
prev->size = 0;
|
||||
next->size = 0;
|
||||
} else {
|
||||
// (prev, curr), next
|
||||
curr->size += prev->size;
|
||||
prev->size = 0;
|
||||
}
|
||||
} else {
|
||||
curr->offset = prev->offset + prev->size;
|
||||
if (next->exec) {
|
||||
// prev, (curr, next)
|
||||
curr->size += next->size;
|
||||
next->size = 0;
|
||||
} else {
|
||||
// prev, curr, next
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mappings_ is sorted excepted for the first entry.
|
||||
// This function tries to merge segemnts into the first entry,
|
||||
// then check for other sorted entries.
|
||||
// See LinuxDumper::EnumerateMappings().
|
||||
void CrOSPostProcessMappings(wasteful_vector<MappingInfo*>& mappings) {
|
||||
// Find the candidate "next" to first segment, which is the only one that
|
||||
// could be out-of-order.
|
||||
size_t l = 1;
|
||||
size_t r = mappings.size();
|
||||
size_t next = mappings.size();
|
||||
while (l < r) {
|
||||
int m = (l + r) / 2;
|
||||
if (mappings[m]->start_addr > mappings[0]->start_addr)
|
||||
r = next = m;
|
||||
else
|
||||
l = m + 1;
|
||||
}
|
||||
|
||||
// Shows the range that contains the entry point is
|
||||
// [first_start_addr, first_end_addr)
|
||||
size_t first_start_addr = mappings[0]->start_addr;
|
||||
size_t first_end_addr = mappings[0]->start_addr + mappings[0]->size;
|
||||
|
||||
// Put the out-of-order segment in order.
|
||||
std::rotate(mappings.begin(), mappings.begin() + 1, mappings.begin() + next);
|
||||
|
||||
// Iterate through normal, sorted cases.
|
||||
// Normal case 1.
|
||||
for (size_t i = 0; i < mappings.size() - 1; i++)
|
||||
TryRecoverMappings(mappings[i], mappings[i + 1]);
|
||||
|
||||
// Normal case 2.
|
||||
for (size_t i = 0; i < mappings.size() - 2; i++)
|
||||
TryRecoverMappings(mappings[i], mappings[i + 1], mappings[i + 2]);
|
||||
|
||||
// Collect merged (size == 0) segments.
|
||||
size_t f, e;
|
||||
for (f = e = 0; e < mappings.size(); e++)
|
||||
if (mappings[e]->size > 0)
|
||||
mappings[f++] = mappings[e];
|
||||
mappings.resize(f);
|
||||
|
||||
// The entry point is in the first mapping. We want to find the location
|
||||
// of the entry point after merging segment. To do this, we want to find
|
||||
// the mapping that covers the first mapping from the original mapping list.
|
||||
// If the mapping is not in the beginning, we move it to the begining via
|
||||
// a right rotate by using reverse iterators.
|
||||
for (l = 0; l < mappings.size(); l++) {
|
||||
if (mappings[l]->start_addr <= first_start_addr
|
||||
&& (mappings[l]->start_addr + mappings[l]->size >= first_end_addr))
|
||||
break;
|
||||
}
|
||||
if (l > 0) {
|
||||
r = mappings.size();
|
||||
std::rotate(mappings.rbegin() + r - l - 1, mappings.rbegin() + r - l,
|
||||
mappings.rend());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __CHROMEOS__
|
||||
|
||||
} // namespace
|
||||
|
||||
// All interesting auvx entry types are below AT_SYSINFO_EHDR
|
||||
#define AT_MAX AT_SYSINFO_EHDR
|
||||
|
||||
LinuxDumper::LinuxDumper(pid_t pid, const char* root_prefix)
|
||||
: pid_(pid),
|
||||
root_prefix_(root_prefix),
|
||||
crash_address_(0),
|
||||
crash_signal_(0),
|
||||
crash_signal_code_(0),
|
||||
crash_thread_(pid),
|
||||
threads_(&allocator_, 8),
|
||||
mappings_(&allocator_),
|
||||
auxv_(&allocator_, AT_MAX + 1) {
|
||||
assert(root_prefix_ && my_strlen(root_prefix_) < PATH_MAX);
|
||||
// The passed-in size to the constructor (above) is only a hint.
|
||||
// Must call .resize() to do actual initialization of the elements.
|
||||
auxv_.resize(AT_MAX + 1);
|
||||
}
|
||||
|
||||
LinuxDumper::~LinuxDumper() {
|
||||
}
|
||||
|
||||
bool LinuxDumper::Init() {
|
||||
return ReadAuxv() && EnumerateThreads() && EnumerateMappings();
|
||||
}
|
||||
|
||||
bool LinuxDumper::LateInit() {
|
||||
#if defined(__ANDROID__)
|
||||
LatePostprocessMappings();
|
||||
#endif
|
||||
|
||||
#if defined(__CHROMEOS__)
|
||||
CrOSPostProcessMappings(mappings_);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
wasteful_vector<uint8_t>& identifier) {
|
||||
assert(!member || mapping_id < mappings_.size());
|
||||
if (IsMappedFileOpenUnsafe(mapping))
|
||||
return false;
|
||||
|
||||
// Special-case linux-gate because it's not a real file.
|
||||
if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) {
|
||||
void* linux_gate = NULL;
|
||||
if (pid_ == sys_getpid()) {
|
||||
linux_gate = reinterpret_cast<void*>(mapping.start_addr);
|
||||
} else {
|
||||
linux_gate = allocator_.Alloc(mapping.size);
|
||||
CopyFromProcess(linux_gate, pid_,
|
||||
reinterpret_cast<const void*>(mapping.start_addr),
|
||||
mapping.size);
|
||||
}
|
||||
return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier);
|
||||
}
|
||||
|
||||
char filename[PATH_MAX];
|
||||
if (!GetMappingAbsolutePath(mapping, filename))
|
||||
return false;
|
||||
bool filename_modified = HandleDeletedFileInMapping(filename);
|
||||
|
||||
MemoryMappedFile mapped_file(filename, 0);
|
||||
if (!mapped_file.data() || mapped_file.size() < SELFMAG)
|
||||
return false;
|
||||
|
||||
bool success =
|
||||
FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier);
|
||||
if (success && member && filename_modified) {
|
||||
mappings_[mapping_id]->name[my_strlen(mapping.name) -
|
||||
sizeof(kDeletedSuffix) + 1] = '\0';
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void LinuxDumper::SetCrashInfoFromSigInfo(const siginfo_t& siginfo) {
|
||||
set_crash_address(reinterpret_cast<uintptr_t>(siginfo.si_addr));
|
||||
set_crash_signal(siginfo.si_signo);
|
||||
set_crash_signal_code(siginfo.si_code);
|
||||
}
|
||||
|
||||
const char* LinuxDumper::GetCrashSignalString() const {
|
||||
switch (static_cast<unsigned int>(crash_signal_)) {
|
||||
case MD_EXCEPTION_CODE_LIN_SIGHUP:
|
||||
return "SIGHUP";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGINT:
|
||||
return "SIGINT";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGQUIT:
|
||||
return "SIGQUIT";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGILL:
|
||||
return "SIGILL";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTRAP:
|
||||
return "SIGTRAP";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGABRT:
|
||||
return "SIGABRT";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGBUS:
|
||||
return "SIGBUS";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGFPE:
|
||||
return "SIGFPE";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGKILL:
|
||||
return "SIGKILL";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGUSR1:
|
||||
return "SIGUSR1";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSEGV:
|
||||
return "SIGSEGV";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGUSR2:
|
||||
return "SIGUSR2";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGPIPE:
|
||||
return "SIGPIPE";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGALRM:
|
||||
return "SIGALRM";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTERM:
|
||||
return "SIGTERM";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSTKFLT:
|
||||
return "SIGSTKFLT";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGCHLD:
|
||||
return "SIGCHLD";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGCONT:
|
||||
return "SIGCONT";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSTOP:
|
||||
return "SIGSTOP";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTSTP:
|
||||
return "SIGTSTP";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTTIN:
|
||||
return "SIGTTIN";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGTTOU:
|
||||
return "SIGTTOU";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGURG:
|
||||
return "SIGURG";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGXCPU:
|
||||
return "SIGXCPU";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGXFSZ:
|
||||
return "SIGXFSZ";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGVTALRM:
|
||||
return "SIGVTALRM";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGPROF:
|
||||
return "SIGPROF";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGWINCH:
|
||||
return "SIGWINCH";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGIO:
|
||||
return "SIGIO";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGPWR:
|
||||
return "SIGPWR";
|
||||
case MD_EXCEPTION_CODE_LIN_SIGSYS:
|
||||
return "SIGSYS";
|
||||
case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
|
||||
return "DUMP_REQUESTED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxDumper::GetMappingAbsolutePath(const MappingInfo& mapping,
|
||||
char path[PATH_MAX]) const {
|
||||
return my_strlcpy(path, root_prefix_, PATH_MAX) < PATH_MAX &&
|
||||
my_strlcat(path, mapping.name, PATH_MAX) < PATH_MAX;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Find the shared object name (SONAME) by examining the ELF information
|
||||
// for |mapping|. If the SONAME is found copy it into the passed buffer
|
||||
// |soname| and return true. The size of the buffer is |soname_size|.
|
||||
// The SONAME will be truncated if it is too long to fit in the buffer.
|
||||
bool ElfFileSoName(const LinuxDumper& dumper,
|
||||
const MappingInfo& mapping, char* soname, size_t soname_size) {
|
||||
if (IsMappedFileOpenUnsafe(mapping)) {
|
||||
// Not safe
|
||||
return false;
|
||||
}
|
||||
|
||||
char filename[PATH_MAX];
|
||||
if (!dumper.GetMappingAbsolutePath(mapping, filename))
|
||||
return false;
|
||||
|
||||
MemoryMappedFile mapped_file(filename, 0);
|
||||
if (!mapped_file.data() || mapped_file.size() < SELFMAG) {
|
||||
// mmap failed
|
||||
return false;
|
||||
}
|
||||
|
||||
return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
||||
char* file_path,
|
||||
size_t file_path_size,
|
||||
char* file_name,
|
||||
size_t file_name_size) {
|
||||
my_strlcpy(file_path, mapping.name, file_path_size);
|
||||
|
||||
// Tools such as minidump_stackwalk use the name of the module to look up
|
||||
// symbols produced by dump_syms. dump_syms will prefer to use a module's
|
||||
// DT_SONAME as the module name, if one exists, and will fall back to the
|
||||
// filesystem name of the module.
|
||||
|
||||
// Just use the filesystem name if no SONAME is present.
|
||||
if (!ElfFileSoName(*this, mapping, file_name, file_name_size)) {
|
||||
// file_path := /path/to/libname.so
|
||||
// file_name := libname.so
|
||||
const char* basename = my_strrchr(file_path, '/');
|
||||
basename = basename == NULL ? file_path : (basename + 1);
|
||||
my_strlcpy(file_name, basename, file_name_size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapping.exec && mapping.offset != 0) {
|
||||
// If an executable is mapped from a non-zero offset, this is likely because
|
||||
// the executable was loaded directly from inside an archive file (e.g., an
|
||||
// apk on Android).
|
||||
// In this case, we append the file_name to the mapped archive path:
|
||||
// file_name := libname.so
|
||||
// file_path := /path/to/ARCHIVE.APK/libname.so
|
||||
if (my_strlen(file_path) + 1 + my_strlen(file_name) < file_path_size) {
|
||||
my_strlcat(file_path, "/", file_path_size);
|
||||
my_strlcat(file_path, file_name, file_path_size);
|
||||
}
|
||||
} else {
|
||||
// Otherwise, replace the basename with the SONAME.
|
||||
char* basename = const_cast<char*>(my_strrchr(file_path, '/'));
|
||||
if (basename) {
|
||||
my_strlcpy(basename + 1, file_name,
|
||||
file_path_size - my_strlen(file_path) +
|
||||
my_strlen(basename + 1));
|
||||
} else {
|
||||
my_strlcpy(file_path, file_name, file_path_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxDumper::ReadAuxv() {
|
||||
char auxv_path[NAME_MAX];
|
||||
if (!BuildProcPath(auxv_path, pid_, "auxv")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = sys_open(auxv_path, O_RDONLY, 0);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elf_aux_entry one_aux_entry;
|
||||
bool res = false;
|
||||
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_MAX) {
|
||||
auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val;
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
sys_close(fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool LinuxDumper::EnumerateMappings() {
|
||||
char maps_path[NAME_MAX];
|
||||
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
|
||||
// maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
|
||||
// aux vector entry, which gives the information necessary to special
|
||||
// case its entry when creating the list of mappings.
|
||||
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
|
||||
// information.
|
||||
const void* linux_gate_loc =
|
||||
reinterpret_cast<void*>(auxv_[AT_SYSINFO_EHDR]);
|
||||
// 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 = reinterpret_cast<void*>(auxv_[AT_ENTRY]);
|
||||
|
||||
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 == ' ') {
|
||||
bool exec = (*(i2 + 3) == 'x');
|
||||
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 into one module, assuming they're a single
|
||||
// library mapped by the dynamic linker. Do this only if their name
|
||||
// matches and either they have the same +x protection flag, or if the
|
||||
// previous mapping is not executable and the new one is, to handle
|
||||
// lld's output (see crbug.com/716484).
|
||||
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) &&
|
||||
((exec == module->exec) || (!module->exec && exec))) {
|
||||
module->system_mapping_info.end_addr = end_addr;
|
||||
module->size = end_addr - module->start_addr;
|
||||
module->exec |= exec;
|
||||
line_reader->PopLine(line_len);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
MappingInfo* const module = new(allocator_) MappingInfo;
|
||||
mappings_.push_back(module);
|
||||
my_memset(module, 0, sizeof(MappingInfo));
|
||||
module->system_mapping_info.start_addr = start_addr;
|
||||
module->system_mapping_info.end_addr = end_addr;
|
||||
module->start_addr = start_addr;
|
||||
module->size = end_addr - start_addr;
|
||||
module->offset = offset;
|
||||
module->exec = exec;
|
||||
if (name != NULL) {
|
||||
const unsigned l = my_strlen(name);
|
||||
if (l < sizeof(module->name))
|
||||
my_memcpy(module->name, name, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
line_reader->PopLine(line_len);
|
||||
}
|
||||
|
||||
if (entry_point_loc) {
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
MappingInfo* module = mappings_[i];
|
||||
|
||||
// If this module contains the entry-point, 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 >= reinterpret_cast<void*>(module->start_addr)) &&
|
||||
(entry_point_loc <
|
||||
reinterpret_cast<void*>(module->start_addr + module->size))) {
|
||||
for (size_t j = i; j > 0; j--)
|
||||
mappings_[j] = mappings_[j - 1];
|
||||
mappings_[0] = module;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
|
||||
return !mappings_.empty();
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
bool LinuxDumper::GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr) {
|
||||
CopyFromProcess(ehdr, pid_,
|
||||
reinterpret_cast<const void*>(start_addr),
|
||||
sizeof(*ehdr));
|
||||
return my_memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) == 0;
|
||||
}
|
||||
|
||||
void LinuxDumper::ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
|
||||
uintptr_t start_addr,
|
||||
uintptr_t* min_vaddr_ptr,
|
||||
uintptr_t* dyn_vaddr_ptr,
|
||||
size_t* dyn_count_ptr) {
|
||||
uintptr_t phdr_addr = start_addr + ehdr->e_phoff;
|
||||
|
||||
const uintptr_t max_addr = UINTPTR_MAX;
|
||||
uintptr_t min_vaddr = max_addr;
|
||||
uintptr_t dyn_vaddr = 0;
|
||||
size_t dyn_count = 0;
|
||||
|
||||
for (size_t i = 0; i < ehdr->e_phnum; ++i) {
|
||||
ElfW(Phdr) phdr;
|
||||
CopyFromProcess(&phdr, pid_,
|
||||
reinterpret_cast<const void*>(phdr_addr),
|
||||
sizeof(phdr));
|
||||
if (phdr.p_type == PT_LOAD && phdr.p_vaddr < min_vaddr) {
|
||||
min_vaddr = phdr.p_vaddr;
|
||||
}
|
||||
if (phdr.p_type == PT_DYNAMIC) {
|
||||
dyn_vaddr = phdr.p_vaddr;
|
||||
dyn_count = phdr.p_memsz / sizeof(ElfW(Dyn));
|
||||
}
|
||||
phdr_addr += sizeof(phdr);
|
||||
}
|
||||
|
||||
*min_vaddr_ptr = min_vaddr;
|
||||
*dyn_vaddr_ptr = dyn_vaddr;
|
||||
*dyn_count_ptr = dyn_count;
|
||||
}
|
||||
|
||||
bool LinuxDumper::HasAndroidPackedRelocations(uintptr_t load_bias,
|
||||
uintptr_t dyn_vaddr,
|
||||
size_t dyn_count) {
|
||||
uintptr_t dyn_addr = load_bias + dyn_vaddr;
|
||||
for (size_t i = 0; i < dyn_count; ++i) {
|
||||
ElfW(Dyn) dyn;
|
||||
CopyFromProcess(&dyn, pid_,
|
||||
reinterpret_cast<const void*>(dyn_addr),
|
||||
sizeof(dyn));
|
||||
if (dyn.d_tag == DT_ANDROID_REL || dyn.d_tag == DT_ANDROID_RELA) {
|
||||
return true;
|
||||
}
|
||||
dyn_addr += sizeof(dyn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uintptr_t LinuxDumper::GetEffectiveLoadBias(ElfW(Ehdr)* ehdr,
|
||||
uintptr_t start_addr) {
|
||||
uintptr_t min_vaddr = 0;
|
||||
uintptr_t dyn_vaddr = 0;
|
||||
size_t dyn_count = 0;
|
||||
ParseLoadedElfProgramHeaders(ehdr, start_addr,
|
||||
&min_vaddr, &dyn_vaddr, &dyn_count);
|
||||
// If |min_vaddr| is non-zero and we find Android packed relocation tags,
|
||||
// return the effective load bias.
|
||||
if (min_vaddr != 0) {
|
||||
const uintptr_t load_bias = start_addr - min_vaddr;
|
||||
if (HasAndroidPackedRelocations(load_bias, dyn_vaddr, dyn_count)) {
|
||||
return load_bias;
|
||||
}
|
||||
}
|
||||
// Either |min_vaddr| is zero, or it is non-zero but we did not find the
|
||||
// expected Android packed relocations tags.
|
||||
return start_addr;
|
||||
}
|
||||
|
||||
void LinuxDumper::LatePostprocessMappings() {
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
// Only consider exec mappings that indicate a file path was mapped, and
|
||||
// where the ELF header indicates a mapped shared library.
|
||||
MappingInfo* mapping = mappings_[i];
|
||||
if (!(mapping->exec && mapping->name[0] == '/')) {
|
||||
continue;
|
||||
}
|
||||
ElfW(Ehdr) ehdr;
|
||||
if (!GetLoadedElfHeader(mapping->start_addr, &ehdr)) {
|
||||
continue;
|
||||
}
|
||||
if (ehdr.e_type == ET_DYN) {
|
||||
// Compute the effective load bias for this mapped library, and update
|
||||
// the mapping to hold that rather than |start_addr|, at the same time
|
||||
// adjusting |size| to account for the change in |start_addr|. Where
|
||||
// the library does not contain Android packed relocations,
|
||||
// GetEffectiveLoadBias() returns |start_addr| and the mapping entry
|
||||
// is not changed.
|
||||
const uintptr_t load_bias = GetEffectiveLoadBias(&ehdr,
|
||||
mapping->start_addr);
|
||||
mapping->size += mapping->start_addr - load_bias;
|
||||
mapping->start_addr = load_bias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __ANDROID__
|
||||
|
||||
// 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 -
|
||||
reinterpret_cast<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;
|
||||
}
|
||||
|
||||
void LinuxDumper::SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
|
||||
uintptr_t stack_pointer,
|
||||
uintptr_t sp_offset) {
|
||||
// We optimize the search for containing mappings in three ways:
|
||||
// 1) We expect that pointers into the stack mapping will be common, so
|
||||
// we cache that address range.
|
||||
// 2) The last referenced mapping is a reasonable predictor for the next
|
||||
// referenced mapping, so we test that first.
|
||||
// 3) We precompute a bitfield based upon bits 32:32-n of the start and
|
||||
// stop addresses, and use that to short circuit any values that can
|
||||
// not be pointers. (n=11)
|
||||
const uintptr_t defaced =
|
||||
#if defined(__LP64__)
|
||||
0x0defaced0defaced;
|
||||
#else
|
||||
0x0defaced;
|
||||
#endif
|
||||
// the bitfield length is 2^test_bits long.
|
||||
const unsigned int test_bits = 11;
|
||||
// byte length of the corresponding array.
|
||||
const unsigned int array_size = 1 << (test_bits - 3);
|
||||
const unsigned int array_mask = array_size - 1;
|
||||
// The amount to right shift pointers by. This captures the top bits
|
||||
// on 32 bit architectures. On 64 bit architectures this would be
|
||||
// uninformative so we take the same range of bits.
|
||||
const unsigned int shift = 32 - 11;
|
||||
const MappingInfo* last_hit_mapping = nullptr;
|
||||
const MappingInfo* hit_mapping = nullptr;
|
||||
const MappingInfo* stack_mapping = FindMappingNoBias(stack_pointer);
|
||||
// The magnitude below which integers are considered to be to be
|
||||
// 'small', and not constitute a PII risk. These are included to
|
||||
// avoid eliding useful register values.
|
||||
const ssize_t small_int_magnitude = 4096;
|
||||
|
||||
char could_hit_mapping[array_size];
|
||||
my_memset(could_hit_mapping, 0, array_size);
|
||||
|
||||
// Initialize the bitfield such that if the (pointer >> shift)'th
|
||||
// bit, modulo the bitfield size, is not set then there does not
|
||||
// exist a mapping in mappings_ that would contain that pointer.
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
if (!mappings_[i]->exec) continue;
|
||||
// For each mapping, work out the (unmodulo'ed) range of bits to
|
||||
// set.
|
||||
uintptr_t start = mappings_[i]->start_addr;
|
||||
uintptr_t end = start + mappings_[i]->size;
|
||||
start >>= shift;
|
||||
end >>= shift;
|
||||
for (size_t bit = start; bit <= end; ++bit) {
|
||||
// Set each bit in the range, applying the modulus.
|
||||
could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
|
||||
}
|
||||
}
|
||||
|
||||
// Zero memory that is below the current stack pointer.
|
||||
const uintptr_t offset =
|
||||
(sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
|
||||
if (offset) {
|
||||
my_memset(stack_copy, 0, offset);
|
||||
}
|
||||
|
||||
// Apply sanitization to each complete pointer-aligned word in the
|
||||
// stack.
|
||||
uint8_t* sp;
|
||||
for (sp = stack_copy + offset;
|
||||
sp <= stack_copy + stack_len - sizeof(uintptr_t);
|
||||
sp += sizeof(uintptr_t)) {
|
||||
uintptr_t addr;
|
||||
my_memcpy(&addr, sp, sizeof(uintptr_t));
|
||||
if (static_cast<intptr_t>(addr) <= small_int_magnitude &&
|
||||
static_cast<intptr_t>(addr) >= -small_int_magnitude) {
|
||||
continue;
|
||||
}
|
||||
if (stack_mapping && MappingContainsAddress(*stack_mapping, addr)) {
|
||||
continue;
|
||||
}
|
||||
if (last_hit_mapping && MappingContainsAddress(*last_hit_mapping, addr)) {
|
||||
continue;
|
||||
}
|
||||
uintptr_t test = addr >> shift;
|
||||
if (could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) &&
|
||||
(hit_mapping = FindMappingNoBias(addr)) != nullptr &&
|
||||
hit_mapping->exec) {
|
||||
last_hit_mapping = hit_mapping;
|
||||
continue;
|
||||
}
|
||||
my_memcpy(sp, &defaced, sizeof(uintptr_t));
|
||||
}
|
||||
// Zero any partial word at the top of the stack, if alignment is
|
||||
// such that that is required.
|
||||
if (sp < stack_copy + stack_len) {
|
||||
my_memset(sp, 0, stack_copy + stack_len - sp);
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxDumper::StackHasPointerToMapping(const uint8_t* stack_copy,
|
||||
size_t stack_len,
|
||||
uintptr_t sp_offset,
|
||||
const MappingInfo& mapping) {
|
||||
// Loop over all stack words that would have been on the stack in
|
||||
// the target process (i.e. are word aligned, and at addresses >=
|
||||
// the stack pointer). Regardless of the alignment of |stack_copy|,
|
||||
// the memory starting at |stack_copy| + |offset| represents an
|
||||
// aligned word in the target process.
|
||||
const uintptr_t low_addr = mapping.system_mapping_info.start_addr;
|
||||
const uintptr_t high_addr = mapping.system_mapping_info.end_addr;
|
||||
const uintptr_t offset =
|
||||
(sp_offset + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);
|
||||
|
||||
for (const uint8_t* sp = stack_copy + offset;
|
||||
sp <= stack_copy + stack_len - sizeof(uintptr_t);
|
||||
sp += sizeof(uintptr_t)) {
|
||||
uintptr_t addr;
|
||||
my_memcpy(&addr, sp, sizeof(uintptr_t));
|
||||
if (low_addr <= addr && addr <= high_addr)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Find the mapping which the given memory address falls in. Uses the
|
||||
// unadjusted mapping address range from the kernel, rather than the
|
||||
// biased range.
|
||||
const MappingInfo* LinuxDumper::FindMappingNoBias(uintptr_t address) const {
|
||||
for (size_t i = 0; i < mappings_.size(); ++i) {
|
||||
if (address >= mappings_[i]->system_mapping_info.start_addr &&
|
||||
address < mappings_[i]->system_mapping_info.end_addr) {
|
||||
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];
|
||||
if (!BuildProcPath(exe_link, pid_, "exe"))
|
||||
return false;
|
||||
MappingInfo new_mapping = {0};
|
||||
if (!SafeReadLink(exe_link, new_mapping.name))
|
||||
return false;
|
||||
char new_path[PATH_MAX];
|
||||
if (!GetMappingAbsolutePath(new_mapping, new_path))
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
my_memcpy(path, exe_link, NAME_MAX);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
327
externals/breakpad/src/client/linux/minidump_writer/linux_dumper.h
vendored
Normal file
327
externals/breakpad/src/client/linux/minidump_writer/linux_dumper.h
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// 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 LLC 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_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_
|
||||
|
||||
#include <assert.h>
|
||||
#include <elf.h>
|
||||
#if defined(__ANDROID__)
|
||||
#include <link.h>
|
||||
#endif
|
||||
#include <linux/limits.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "client/linux/dump_writer_common/mapping_info.h"
|
||||
#include "client/linux/dump_writer_common/thread_info.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/memory_allocator.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
|
||||
#if defined(__i386) || defined(__ARM_EABI__) || \
|
||||
(defined(__mips__) && _MIPS_SIM == _ABIO32) || \
|
||||
(defined(__riscv) && __riscv_xlen == 32)
|
||||
typedef Elf32_auxv_t elf_aux_entry;
|
||||
#elif defined(__x86_64) || defined(__aarch64__) || \
|
||||
(defined(__mips__) && _MIPS_SIM != _ABIO32) || \
|
||||
(defined(__riscv) && __riscv_xlen == 64)
|
||||
typedef Elf64_auxv_t elf_aux_entry;
|
||||
#endif
|
||||
|
||||
typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t;
|
||||
|
||||
// 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";
|
||||
|
||||
class LinuxDumper {
|
||||
public:
|
||||
// The |root_prefix| is prepended to mapping paths before opening them, which
|
||||
// is useful if the crash originates from a chroot.
|
||||
explicit LinuxDumper(pid_t pid, const char* root_prefix = "");
|
||||
|
||||
virtual ~LinuxDumper();
|
||||
|
||||
// Parse the data for |threads| and |mappings|.
|
||||
virtual bool Init();
|
||||
|
||||
// Take any actions that could not be taken in Init(). LateInit() is
|
||||
// called after all other caller's initialization is complete, and in
|
||||
// particular after it has called ThreadsSuspend(), so that ptrace is
|
||||
// available.
|
||||
virtual bool LateInit();
|
||||
|
||||
// Return true if the dumper performs a post-mortem dump.
|
||||
virtual bool IsPostMortem() const = 0;
|
||||
|
||||
// Suspend/resume all threads in the given process.
|
||||
virtual bool ThreadsSuspend() = 0;
|
||||
virtual bool ThreadsResume() = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
size_t GetMainThreadIndex() const {
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
if (threads_[i] == pid_) return i;
|
||||
}
|
||||
return -1u;
|
||||
}
|
||||
|
||||
// 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 the mapping which the given memory address falls in. Unlike
|
||||
// FindMapping, this method uses the unadjusted mapping address
|
||||
// ranges from the kernel, rather than the ranges that have had the
|
||||
// load bias applied.
|
||||
const MappingInfo* FindMappingNoBias(uintptr_t address) const;
|
||||
const wasteful_vector<elf_aux_val_t>& auxv() { return auxv_; }
|
||||
|
||||
// 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);
|
||||
|
||||
// Sanitize a copy of the stack by overwriting words that are not
|
||||
// pointers with a sentinel (0x0defaced).
|
||||
// stack_copy: a copy of the stack to sanitize. |stack_copy| might
|
||||
// not be word aligned, but it represents word aligned
|
||||
// data copied from another location.
|
||||
// stack_len: the length of the allocation pointed to by |stack_copy|.
|
||||
// stack_pointer: the address of the stack pointer (used to locate
|
||||
// the stack mapping, as an optimization).
|
||||
// sp_offset: the offset relative to stack_copy that reflects the
|
||||
// current value of the stack pointer.
|
||||
void SanitizeStackCopy(uint8_t* stack_copy, size_t stack_len,
|
||||
uintptr_t stack_pointer, uintptr_t sp_offset);
|
||||
|
||||
// Test whether |stack_copy| contains a pointer-aligned word that
|
||||
// could be an address within a given mapping.
|
||||
// stack_copy: a copy of the stack to check. |stack_copy| might
|
||||
// not be word aligned, but it represents word aligned
|
||||
// data copied from another location.
|
||||
// stack_len: the length of the allocation pointed to by |stack_copy|.
|
||||
// sp_offset: the offset relative to stack_copy that reflects the
|
||||
// current value of the stack pointer.
|
||||
// mapping: the mapping against which to test stack words.
|
||||
bool StackHasPointerToMapping(const uint8_t* stack_copy, size_t stack_len,
|
||||
uintptr_t sp_offset,
|
||||
const MappingInfo& mapping);
|
||||
|
||||
PageAllocator* allocator() { return &allocator_; }
|
||||
|
||||
// Copy content of |length| bytes from a given process |child|,
|
||||
// starting from |src|, into |dest|. Returns true on success.
|
||||
virtual bool CopyFromProcess(void* dest, pid_t child, const void* src,
|
||||
size_t length) = 0;
|
||||
|
||||
// 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. This method can also manipulate the
|
||||
// |mapping|.name to truncate "(deleted)" from the file name if necessary.
|
||||
bool ElfFileIdentifierForMapping(const MappingInfo& mapping,
|
||||
bool member,
|
||||
unsigned int mapping_id,
|
||||
wasteful_vector<uint8_t>& identifier);
|
||||
|
||||
void SetCrashInfoFromSigInfo(const siginfo_t& siginfo);
|
||||
|
||||
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; }
|
||||
const char* GetCrashSignalString() const;
|
||||
|
||||
void set_crash_signal_code(int code) { crash_signal_code_ = code; }
|
||||
int crash_signal_code() const { return crash_signal_code_; }
|
||||
|
||||
void set_crash_exception_info(const std::vector<uint64_t>& exception_info) {
|
||||
assert(exception_info.size() <= MD_EXCEPTION_MAXIMUM_PARAMETERS);
|
||||
crash_exception_info_ = exception_info;
|
||||
}
|
||||
const std::vector<uint64_t>& crash_exception_info() const {
|
||||
return crash_exception_info_;
|
||||
}
|
||||
|
||||
pid_t crash_thread() const { return crash_thread_; }
|
||||
void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; }
|
||||
|
||||
// Concatenates the |root_prefix_| and |mapping| path. Writes into |path| and
|
||||
// returns true unless the string is too long.
|
||||
bool GetMappingAbsolutePath(const MappingInfo& mapping,
|
||||
char path[PATH_MAX]) const;
|
||||
|
||||
// Extracts the effective path and file name of from |mapping|. In most cases
|
||||
// the effective name/path are just the mapping's path and basename. In some
|
||||
// other cases, however, a library can be mapped from an archive (e.g., when
|
||||
// loading .so libs from an apk on Android) and this method is able to
|
||||
// reconstruct the original file name.
|
||||
void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
|
||||
char* file_path,
|
||||
size_t file_path_size,
|
||||
char* file_name,
|
||||
size_t file_name_size);
|
||||
|
||||
protected:
|
||||
bool ReadAuxv();
|
||||
|
||||
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
|
||||
// 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;
|
||||
|
||||
// ID of the crashed process.
|
||||
const pid_t pid_;
|
||||
|
||||
// Path of the root directory to which mapping paths are relative.
|
||||
const char* const root_prefix_;
|
||||
|
||||
// Virtual address at which the process crashed.
|
||||
uintptr_t crash_address_;
|
||||
|
||||
// Signal that terminated the crashed process.
|
||||
int crash_signal_;
|
||||
|
||||
// The code associated with |crash_signal_|.
|
||||
int crash_signal_code_;
|
||||
|
||||
// The additional fields associated with |crash_signal_|.
|
||||
std::vector<uint64_t> crash_exception_info_;
|
||||
|
||||
// ID of the crashed thread.
|
||||
pid_t crash_thread_;
|
||||
|
||||
mutable PageAllocator allocator_;
|
||||
|
||||
// IDs of all the threads.
|
||||
wasteful_vector<pid_t> threads_;
|
||||
|
||||
// Info from /proc/<pid>/maps.
|
||||
wasteful_vector<MappingInfo*> mappings_;
|
||||
|
||||
// Info from /proc/<pid>/auxv
|
||||
wasteful_vector<elf_aux_val_t> auxv_;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
private:
|
||||
// Android M and later support packed ELF relocations in shared libraries.
|
||||
// Packing relocations changes the vaddr of the LOAD segments, such that
|
||||
// the effective load bias is no longer the same as the start address of
|
||||
// the memory mapping containing the executable parts of the library. The
|
||||
// packing is applied to the stripped library run on the target, but not to
|
||||
// any other library, and in particular not to the library used to generate
|
||||
// breakpad symbols. As a result, we need to adjust the |start_addr| for
|
||||
// any mapping that results from a shared library that contains Android
|
||||
// packed relocations, so that it properly represents the effective library
|
||||
// load bias. The following functions support this adjustment.
|
||||
|
||||
// Check that a given mapping at |start_addr| is for an ELF shared library.
|
||||
// If it is, place the ELF header in |ehdr| and return true.
|
||||
// The first LOAD segment in an ELF shared library has offset zero, so the
|
||||
// ELF file header is at the start of this map entry, and in already mapped
|
||||
// memory.
|
||||
bool GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr);
|
||||
|
||||
// For the ELF file mapped at |start_addr|, iterate ELF program headers to
|
||||
// find the min vaddr of all program header LOAD segments, the vaddr for
|
||||
// the DYNAMIC segment, and a count of DYNAMIC entries. Return values in
|
||||
// |min_vaddr_ptr|, |dyn_vaddr_ptr|, and |dyn_count_ptr|.
|
||||
// The program header table is also in already mapped memory.
|
||||
void ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr,
|
||||
uintptr_t start_addr,
|
||||
uintptr_t* min_vaddr_ptr,
|
||||
uintptr_t* dyn_vaddr_ptr,
|
||||
size_t* dyn_count_ptr);
|
||||
|
||||
// Search the DYNAMIC tags for the ELF file with the given |load_bias|, and
|
||||
// return true if the tags indicate that the file contains Android packed
|
||||
// relocations. Dynamic tags are found at |dyn_vaddr| past the |load_bias|.
|
||||
bool HasAndroidPackedRelocations(uintptr_t load_bias,
|
||||
uintptr_t dyn_vaddr,
|
||||
size_t dyn_count);
|
||||
|
||||
// If the ELF file mapped at |start_addr| contained Android packed
|
||||
// relocations, return the load bias that the system linker (or Chromium
|
||||
// crazy linker) will have used. If the file did not contain Android
|
||||
// packed relocations, returns |start_addr|, indicating that no adjustment
|
||||
// is necessary.
|
||||
// The effective load bias is |start_addr| adjusted downwards by the
|
||||
// min vaddr in the library LOAD segments.
|
||||
uintptr_t GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, uintptr_t start_addr);
|
||||
|
||||
// Called from LateInit(). Iterates |mappings_| and rewrites the |start_addr|
|
||||
// field of any that represent ELF shared libraries with Android packed
|
||||
// relocations, so that |start_addr| is the load bias that the system linker
|
||||
// (or Chromium crazy linker) used. This value matches the addresses produced
|
||||
// when the non-relocation-packed library is used for breakpad symbol
|
||||
// generation.
|
||||
void LatePostprocessMappings();
|
||||
#endif // __ANDROID__
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
|
100
externals/breakpad/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
vendored
Normal file
100
externals/breakpad/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2010 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
#if defined(__ARM_EABI__)
|
||||
#define TID_PTR_REGISTER "r3"
|
||||
#elif defined(__aarch64__)
|
||||
#define TID_PTR_REGISTER "x3"
|
||||
#elif defined(__i386)
|
||||
#define TID_PTR_REGISTER "ecx"
|
||||
#elif defined(__x86_64)
|
||||
#define TID_PTR_REGISTER "rcx"
|
||||
#elif defined(__mips__)
|
||||
#define TID_PTR_REGISTER "$1"
|
||||
#elif defined(__riscv)
|
||||
#define TID_PTR_REGISTER "x4"
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
|
||||
void* thread_function(void* data) {
|
||||
int pipefd = *static_cast<int*>(data);
|
||||
volatile pid_t* thread_id = new 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));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
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;
|
||||
}
|
||||
google_breakpad::scoped_array<pthread_t> threads(new pthread_t[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, &pipefd);
|
||||
}
|
||||
thread_function(&pipefd);
|
||||
return 0;
|
||||
}
|
390
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
vendored
Normal file
390
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
vendored
Normal file
|
@ -0,0 +1,390 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#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/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#if defined(__i386)
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
|
||||
#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_uint_len(pid);
|
||||
const size_t total_length = 6 + pid_len + 1 + node_len;
|
||||
if (total_length >= NAME_MAX)
|
||||
return false;
|
||||
|
||||
my_memcpy(path, "/proc/", 6);
|
||||
my_uitos(path + 6, pid, pid_len);
|
||||
path[6 + pid_len] = '/';
|
||||
my_memcpy(path + 6 + pid_len + 1, node, node_len);
|
||||
path[total_length] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool 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;
|
||||
}
|
||||
my_memcpy(local + done, &tmp, l);
|
||||
done += l;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::ReadRegisterSet(ThreadInfo* info, pid_t tid)
|
||||
{
|
||||
#ifdef PTRACE_GETREGSET
|
||||
struct iovec io;
|
||||
info->GetGeneralPurposeRegisters(&io.iov_base, &io.iov_len);
|
||||
if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, (void*)&io) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len);
|
||||
if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LinuxPtraceDumper::ReadRegisters(ThreadInfo* info, pid_t tid) {
|
||||
#ifdef PTRACE_GETREGS
|
||||
void* gp_addr;
|
||||
info->GetGeneralPurposeRegisters(&gp_addr, NULL);
|
||||
if (sys_ptrace(PTRACE_GETREGS, tid, NULL, gp_addr) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When running on arm processors the binary may be built with softfp or
|
||||
// hardfp. If built with softfp we have no hardware registers to read from,
|
||||
// so the following read will always fail. gcc defines __SOFTFP__ macro,
|
||||
// clang13 does not do so. see: https://reviews.llvm.org/D135680.
|
||||
// If you are using clang and the macro is NOT defined, please include the
|
||||
// macro define for applicable targets.
|
||||
#if !defined(__SOFTFP__)
|
||||
#if !(defined(__ANDROID__) && defined(__ARM_EABI__))
|
||||
// When running an arm build on an arm64 device, attempting to get the
|
||||
// floating point registers fails. On Android, the floating point registers
|
||||
// aren't written to the cpu context anyway, so just don't get them here.
|
||||
// See http://crbug.com/508324
|
||||
void* fp_addr;
|
||||
info->GetFloatingPointRegisters(&fp_addr, NULL);
|
||||
if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) {
|
||||
return false;
|
||||
}
|
||||
#endif // !(defined(__ANDROID__) && defined(__ARM_EABI__))
|
||||
#endif // !defined(__SOFTFP__)
|
||||
return true;
|
||||
#else // PTRACE_GETREGS
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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 (!ReadRegisterSet(info, tid)) {
|
||||
if (!ReadRegisters(info, tid)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__i386)
|
||||
#if !defined(bit_FXSAVE) // e.g. Clang
|
||||
#define bit_FXSAVE bit_FXSR
|
||||
#endif
|
||||
// Detect if the CPU supports the FXSAVE/FXRSTOR instructions
|
||||
int eax, ebx, ecx, edx;
|
||||
__cpuid(1, eax, ebx, ecx, edx);
|
||||
if (edx & bit_FXSAVE) {
|
||||
if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
memset(&info->fpxregs, 0, sizeof(info->fpxregs));
|
||||
}
|
||||
#endif // defined(__i386)
|
||||
|
||||
#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
|
||||
|
||||
#if defined(__mips__)
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(PC), &info->mcontext.pc);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE), &info->mcontext.hi1);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE + 1), &info->mcontext.lo1);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE + 2), &info->mcontext.hi2);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE + 3), &info->mcontext.lo2);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE + 4), &info->mcontext.hi3);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_BASE + 5), &info->mcontext.lo3);
|
||||
sys_ptrace(PTRACE_PEEKUSER, tid,
|
||||
reinterpret_cast<void*>(DSP_CONTROL), &info->mcontext.dsp);
|
||||
#endif
|
||||
|
||||
const uint8_t* stack_pointer;
|
||||
#if defined(__i386)
|
||||
my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
|
||||
#elif defined(__x86_64)
|
||||
my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
|
||||
#elif defined(__ARM_EABI__)
|
||||
my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
|
||||
#elif defined(__aarch64__)
|
||||
my_memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp));
|
||||
#elif defined(__mips__)
|
||||
stack_pointer =
|
||||
reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]);
|
||||
#elif defined(__riscv)
|
||||
stack_pointer = reinterpret_cast<uint8_t*>(
|
||||
info->mcontext.__gregs[MD_CONTEXT_RISCV_REG_SP]);
|
||||
#else
|
||||
# error "This code hasn't been ported to your platform yet."
|
||||
#endif
|
||||
info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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.
|
||||
if (i < threads_.size() - 1) {
|
||||
my_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
|
100
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.h
vendored
Normal file
100
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.h
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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. Always returns true.
|
||||
virtual bool 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_;
|
||||
|
||||
// Read the tracee's registers on kernel with PTRACE_GETREGSET support.
|
||||
// Returns false if PTRACE_GETREGSET is not defined.
|
||||
// Returns true on success.
|
||||
bool ReadRegisterSet(ThreadInfo* info, pid_t tid);
|
||||
|
||||
// Read the tracee's registers on kernel with PTRACE_GETREGS support.
|
||||
// Returns true on success.
|
||||
bool ReadRegisters(ThreadInfo* info, pid_t tid);
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_
|
590
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
vendored
Normal file
590
externals/breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc
vendored
Normal file
|
@ -0,0 +1,590 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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_unittest.cc:
|
||||
// Unit tests for google_breakpad::LinuxPtraceDumper.
|
||||
//
|
||||
// This file was renamed from linux_dumper_unittest.cc and modified due
|
||||
// to LinuxDumper being splitted into two classes.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
|
||||
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/ignore_ret.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "common/memory_allocator.h"
|
||||
#include "common/using_std_string.h"
|
||||
|
||||
#ifndef PR_SET_PTRACER
|
||||
#define PR_SET_PTRACER 0x59616d61
|
||||
#endif
|
||||
|
||||
using namespace google_breakpad;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
|
||||
namespace {
|
||||
|
||||
pid_t SetupChildProcess(int number_of_threads) {
|
||||
char kNumberOfThreadsArgument[2];
|
||||
sprintf(kNumberOfThreadsArgument, "%d", number_of_threads);
|
||||
|
||||
int fds[2];
|
||||
EXPECT_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()) {
|
||||
fprintf(stderr, "Couldn't find helper binary\n");
|
||||
_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);
|
||||
std::string err_str = "Exec of " + helper_path + " failed";
|
||||
perror(err_str.c_str());
|
||||
_exit(1);
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
// Wait for all child threads to indicate that they have started
|
||||
for (int threads = 0; threads < number_of_threads; 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));
|
||||
EXPECT_EQ(1, r);
|
||||
EXPECT_TRUE(pfd.revents & POLLIN);
|
||||
uint8_t junk;
|
||||
EXPECT_EQ(read(fds[0], &junk, sizeof(junk)),
|
||||
static_cast<ssize_t>(sizeof(junk)));
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
// 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);
|
||||
return child_pid;
|
||||
}
|
||||
|
||||
typedef wasteful_vector<uint8_t> id_vector;
|
||||
typedef testing::Test LinuxPtraceDumperTest;
|
||||
|
||||
/* Fixture for running tests in a child process. */
|
||||
class LinuxPtraceDumperChildTest : public testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
child_pid_ = fork();
|
||||
#ifndef __ANDROID__
|
||||
prctl(PR_SET_PTRACER, child_pid_);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Gtest is calling TestBody from this class, which sets up a child
|
||||
* process in which the RealTestBody virtual member is called.
|
||||
* As such, TestBody is not supposed to be overridden in derived classes.
|
||||
*/
|
||||
virtual void TestBody() /* final */ {
|
||||
if (child_pid_ == 0) {
|
||||
// child process
|
||||
RealTestBody();
|
||||
_exit(HasFatalFailure() ? kFatalFailure :
|
||||
(HasNonfatalFailure() ? kNonFatalFailure : 0));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(child_pid_ > 0);
|
||||
int status;
|
||||
waitpid(child_pid_, &status, 0);
|
||||
if (WEXITSTATUS(status) == kFatalFailure) {
|
||||
GTEST_FATAL_FAILURE_("Test failed in child process");
|
||||
} else if (WEXITSTATUS(status) == kNonFatalFailure) {
|
||||
GTEST_NONFATAL_FAILURE_("Test failed in child process");
|
||||
}
|
||||
}
|
||||
|
||||
/* Gtest defines TestBody functions through its macros, but classes
|
||||
* derived from this one need to define RealTestBody instead.
|
||||
* This is achieved by defining a TestBody macro further below.
|
||||
*/
|
||||
virtual void RealTestBody() = 0;
|
||||
|
||||
id_vector make_vector() {
|
||||
return id_vector(&allocator, kDefaultBuildIdSize);
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kFatalFailure = 1;
|
||||
static const int kNonFatalFailure = 2;
|
||||
|
||||
pid_t child_pid_;
|
||||
PageAllocator allocator;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/* Replace TestBody declarations within TEST*() with RealTestBody
|
||||
* declarations */
|
||||
#define TestBody RealTestBody
|
||||
|
||||
TEST_F(LinuxPtraceDumperChildTest, Setup) {
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
}
|
||||
|
||||
TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
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_F(LinuxPtraceDumperChildTest, ThreadList) {
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
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] == getppid()) {
|
||||
ASSERT_FALSE(found);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found);
|
||||
}
|
||||
|
||||
// Helper stack class to close a file descriptor and unmap
|
||||
// a mmap'ed mapping.
|
||||
class StackHelper {
|
||||
public:
|
||||
StackHelper()
|
||||
: fd_(-1), mapping_(NULL), size_(0) {}
|
||||
~StackHelper() {
|
||||
if (size_)
|
||||
munmap(mapping_, size_);
|
||||
if (fd_ >= 0)
|
||||
close(fd_);
|
||||
}
|
||||
void Init(int fd, char* mapping, size_t size) {
|
||||
fd_ = fd;
|
||||
mapping_ = mapping;
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
char* mapping() const { return mapping_; }
|
||||
size_t size() const { return size_; }
|
||||
|
||||
private:
|
||||
int fd_;
|
||||
char* mapping_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
|
||||
protected:
|
||||
virtual void SetUp();
|
||||
|
||||
string helper_path_;
|
||||
size_t page_size_;
|
||||
StackHelper helper_;
|
||||
};
|
||||
|
||||
void LinuxPtraceDumperMappingsTest::SetUp() {
|
||||
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.
|
||||
page_size_ = sysconf(_SC_PAGESIZE);
|
||||
const size_t kMappingSize = 3 * page_size_;
|
||||
int fd = open(helper_path_.c_str(), O_RDONLY);
|
||||
ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
|
||||
<< ", Error: " << strerror(errno);
|
||||
char* mapping =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
kMappingSize,
|
||||
PROT_READ,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0));
|
||||
ASSERT_TRUE(mapping);
|
||||
|
||||
// Ensure that things get cleaned up.
|
||||
helper_.Init(fd, mapping, kMappingSize);
|
||||
|
||||
// Carve a page out of the first mapping with different permissions.
|
||||
char* inside_mapping = reinterpret_cast<char*>(
|
||||
mmap(mapping + 2 * page_size_,
|
||||
page_size_,
|
||||
PROT_NONE,
|
||||
MAP_SHARED | MAP_FIXED,
|
||||
fd,
|
||||
// Map a different offset just to
|
||||
// better test real-world conditions.
|
||||
page_size_));
|
||||
ASSERT_TRUE(inside_mapping);
|
||||
|
||||
LinuxPtraceDumperChildTest::SetUp();
|
||||
}
|
||||
|
||||
TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
|
||||
// Now check that LinuxPtraceDumper interpreted the mappings properly.
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
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, this->helper_path_.c_str()) == 0) {
|
||||
// This mapping should encompass the entire original mapped
|
||||
// range.
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
|
||||
mapping.start_addr);
|
||||
EXPECT_EQ(this->helper_.size(), mapping.size);
|
||||
EXPECT_EQ(0U, mapping.offset);
|
||||
mapping_count++;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(1, mapping_count);
|
||||
}
|
||||
|
||||
TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
|
||||
const pid_t pid = getppid();
|
||||
LinuxPtraceDumper dumper(pid);
|
||||
|
||||
char maps_path[NAME_MAX] = "";
|
||||
char maps_path_expected[NAME_MAX];
|
||||
snprintf(maps_path_expected, sizeof(maps_path_expected),
|
||||
"/proc/%d/maps", pid);
|
||||
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, 0, "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("/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__) && !defined(__mips__)
|
||||
// Ensure that the linux-gate VDSO is included in the mapping list.
|
||||
TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
|
||||
void* linux_gate_loc =
|
||||
reinterpret_cast<void*>(dumper.auxv()[AT_SYSINFO_EHDR]);
|
||||
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_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
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());
|
||||
id_vector identifier(make_vector());
|
||||
ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
|
||||
true,
|
||||
index,
|
||||
identifier));
|
||||
|
||||
id_vector empty_identifier(make_vector());
|
||||
empty_identifier.resize(kDefaultBuildIdSize, 0);
|
||||
EXPECT_NE(empty_identifier, identifier);
|
||||
EXPECT_TRUE(dumper.ThreadsResume());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(LinuxPtraceDumperChildTest, 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];
|
||||
ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
|
||||
|
||||
LinuxPtraceDumper dumper(getppid());
|
||||
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);
|
||||
|
||||
id_vector identifier1(make_vector());
|
||||
id_vector identifier2(make_vector());
|
||||
EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
|
||||
identifier1));
|
||||
FileID fileid(exe_name);
|
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
|
||||
|
||||
string identifier_string1 =
|
||||
FileID::ConvertIdentifierToUUIDString(identifier1);
|
||||
string identifier_string2 =
|
||||
FileID::ConvertIdentifierToUUIDString(identifier2);
|
||||
EXPECT_EQ(identifier_string1, identifier_string2);
|
||||
}
|
||||
|
||||
/* Get back to normal behavior of TEST*() macros wrt TestBody. */
|
||||
#undef TestBody
|
||||
|
||||
TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
|
||||
static const size_t kNumberOfThreadsInHelperProgram = 5;
|
||||
|
||||
pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
|
||||
ASSERT_NE(child_pid, -1);
|
||||
|
||||
// Children are ready now.
|
||||
LinuxPtraceDumper dumper(child_pid);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
#if defined(THREAD_SANITIZER)
|
||||
EXPECT_GE(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
|
||||
#else
|
||||
EXPECT_EQ(dumper.threads().size(), (size_t)kNumberOfThreadsInHelperProgram);
|
||||
#endif
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
|
||||
ThreadInfo one_thread;
|
||||
size_t matching_threads = 0;
|
||||
for (size_t i = 0; i < dumper.threads().size(); ++i) {
|
||||
EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
|
||||
const void* stack;
|
||||
size_t stack_len;
|
||||
EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
|
||||
one_thread.stack_pointer));
|
||||
// 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(__aarch64__)
|
||||
pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[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);
|
||||
#elif defined(__mips__)
|
||||
pid_t* process_tid_location =
|
||||
reinterpret_cast<pid_t*>(one_thread.mcontext.gregs[1]);
|
||||
#elif defined(__riscv)
|
||||
pid_t* process_tid_location =
|
||||
reinterpret_cast<pid_t*>(one_thread.mcontext.__gregs[4]);
|
||||
#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);
|
||||
matching_threads += (dumper.threads()[i] == one_thread_id) ? 1 : 0;
|
||||
}
|
||||
EXPECT_EQ(matching_threads, kNumberOfThreadsInHelperProgram);
|
||||
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_F(LinuxPtraceDumperTest, SanitizeStackCopy) {
|
||||
static const size_t kNumberOfThreadsInHelperProgram = 1;
|
||||
|
||||
pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram);
|
||||
ASSERT_NE(child_pid, -1);
|
||||
|
||||
LinuxPtraceDumper dumper(child_pid);
|
||||
ASSERT_TRUE(dumper.Init());
|
||||
EXPECT_TRUE(dumper.ThreadsSuspend());
|
||||
|
||||
ThreadInfo thread_info;
|
||||
EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info));
|
||||
|
||||
const uintptr_t defaced =
|
||||
#if defined(__LP64__)
|
||||
0x0defaced0defaced;
|
||||
#else
|
||||
0x0defaced;
|
||||
#endif
|
||||
|
||||
uintptr_t simulated_stack[2];
|
||||
|
||||
// Pointers into the stack shouldn't be sanitized.
|
||||
memset(simulated_stack, 0xff, sizeof(simulated_stack));
|
||||
simulated_stack[1] = thread_info.stack_pointer;
|
||||
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
|
||||
sizeof(simulated_stack), thread_info.stack_pointer,
|
||||
sizeof(uintptr_t));
|
||||
ASSERT_NE(simulated_stack[1], defaced);
|
||||
|
||||
// Memory prior to the stack pointer should be cleared.
|
||||
ASSERT_EQ(simulated_stack[0], 0u);
|
||||
|
||||
// Small integers should not be sanitized.
|
||||
for (int i = -4096; i <= 4096; ++i) {
|
||||
memset(simulated_stack, 0, sizeof(simulated_stack));
|
||||
simulated_stack[0] = static_cast<uintptr_t>(i);
|
||||
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
|
||||
sizeof(simulated_stack), thread_info.stack_pointer,
|
||||
0u);
|
||||
ASSERT_NE(simulated_stack[0], defaced);
|
||||
}
|
||||
|
||||
// The instruction pointer definitely should point into an executable mapping.
|
||||
const MappingInfo* mapping_info = dumper.FindMappingNoBias(
|
||||
reinterpret_cast<uintptr_t>(thread_info.GetInstructionPointer()));
|
||||
ASSERT_NE(mapping_info, nullptr);
|
||||
ASSERT_TRUE(mapping_info->exec);
|
||||
|
||||
// Pointers to code shouldn't be sanitized.
|
||||
memset(simulated_stack, 0, sizeof(simulated_stack));
|
||||
simulated_stack[1] = thread_info.GetInstructionPointer();
|
||||
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
|
||||
sizeof(simulated_stack), thread_info.stack_pointer,
|
||||
0u);
|
||||
ASSERT_NE(simulated_stack[0], defaced);
|
||||
|
||||
// String fragments should be sanitized.
|
||||
memcpy(simulated_stack, "abcdefghijklmnop", sizeof(simulated_stack));
|
||||
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
|
||||
sizeof(simulated_stack), thread_info.stack_pointer,
|
||||
0u);
|
||||
ASSERT_EQ(simulated_stack[0], defaced);
|
||||
ASSERT_EQ(simulated_stack[1], defaced);
|
||||
|
||||
// Heap pointers should be sanititzed.
|
||||
#if defined(__ARM_EABI__)
|
||||
uintptr_t heap_addr = thread_info.regs.uregs[3];
|
||||
#elif defined(__aarch64__)
|
||||
uintptr_t heap_addr = thread_info.regs.regs[3];
|
||||
#elif defined(__i386)
|
||||
uintptr_t heap_addr = thread_info.regs.ecx;
|
||||
#elif defined(__x86_64)
|
||||
uintptr_t heap_addr = thread_info.regs.rcx;
|
||||
#elif defined(__mips__)
|
||||
uintptr_t heap_addr = thread_info.mcontext.gregs[1];
|
||||
#elif defined(__riscv)
|
||||
uintptr_t heap_addr = thread_info.mcontext.__gregs[4];
|
||||
#else
|
||||
#error This test has not been ported to this platform.
|
||||
#endif
|
||||
memset(simulated_stack, 0, sizeof(simulated_stack));
|
||||
simulated_stack[0] = heap_addr;
|
||||
dumper.SanitizeStackCopy(reinterpret_cast<uint8_t*>(&simulated_stack),
|
||||
sizeof(simulated_stack), thread_info.stack_pointer,
|
||||
0u);
|
||||
ASSERT_EQ(simulated_stack[0], defaced);
|
||||
|
||||
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));
|
||||
}
|
1617
externals/breakpad/src/client/linux/minidump_writer/minidump_writer.cc
vendored
Normal file
1617
externals/breakpad/src/client/linux/minidump_writer/minidump_writer.cc
vendored
Normal file
File diff suppressed because it is too large
Load diff
142
externals/breakpad/src/client/linux/minidump_writer/minidump_writer.h
vendored
Normal file
142
externals/breakpad/src/client/linux/minidump_writer/minidump_writer.h
vendored
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2009 Google LLC
|
||||
//
|
||||
// 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 LLC 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 <sys/types.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <list>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "client/linux/minidump_writer/linux_dumper.h"
|
||||
#include "google_breakpad/common/minidump_format.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class ExceptionHandler;
|
||||
|
||||
#if defined(__aarch64__)
|
||||
typedef struct fpsimd_context fpstate_t;
|
||||
#elif !defined(__ARM_EABI__) && !defined(__mips__)
|
||||
typedef std::remove_pointer<fpregset_t>::type fpstate_t;
|
||||
#endif
|
||||
|
||||
// These entries store a list of memory regions that the client wants included
|
||||
// in the minidump.
|
||||
struct AppMemory {
|
||||
void* ptr;
|
||||
size_t length;
|
||||
|
||||
bool operator==(const struct AppMemory& other) const {
|
||||
return ptr == other.ptr;
|
||||
}
|
||||
|
||||
bool operator==(const void* other) const {
|
||||
return ptr == other;
|
||||
}
|
||||
};
|
||||
typedef std::list<AppMemory> AppMemoryList;
|
||||
|
||||
// Writes a minidump to the filesystem. These functions do not malloc nor use
|
||||
// libc functions which may. Thus, it can be used in contexts where the state
|
||||
// of the heap may be corrupt.
|
||||
// minidump_path: the path to the file to write to. This is opened O_EXCL and
|
||||
// fails 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* minidump_path, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
// Same as above but takes an open file descriptor instead of a path.
|
||||
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
|
||||
// Alternate form of WriteMinidump() that works with processes that
|
||||
// are not expected to have crashed. If |process_blamed_thread| is
|
||||
// meaningful, it will be the one from which a crash signature is
|
||||
// extracted. It is not expected that this function will be called
|
||||
// from a compromised context, but it is safe to do so.
|
||||
bool WriteMinidump(const char* minidump_path, pid_t process,
|
||||
pid_t process_blamed_thread);
|
||||
|
||||
// These overloads also allow passing a list of known mappings and
|
||||
// a list of additional memory regions to be included in the minidump.
|
||||
bool WriteMinidump(const char* minidump_path, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings,
|
||||
const AppMemoryList& appdata,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
bool WriteMinidump(int minidump_fd, pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings,
|
||||
const AppMemoryList& appdata,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
|
||||
// These overloads also allow passing a file size limit for the minidump.
|
||||
bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit,
|
||||
pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings,
|
||||
const AppMemoryList& appdata,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
bool WriteMinidump(int minidump_fd, off_t minidump_size_limit,
|
||||
pid_t crashing_process,
|
||||
const void* blob, size_t blob_size,
|
||||
const MappingList& mappings,
|
||||
const AppMemoryList& appdata,
|
||||
bool skip_stacks_if_mapping_unreferenced = false,
|
||||
uintptr_t principal_mapping_address = 0,
|
||||
bool sanitize_stacks = false);
|
||||
|
||||
bool WriteMinidump(const char* filename,
|
||||
const MappingList& mappings,
|
||||
const AppMemoryList& appdata,
|
||||
LinuxDumper* dumper);
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
|
942
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc
vendored
Normal file
942
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc
vendored
Normal file
|
@ -0,0 +1,942 @@
|
|||
// Copyright 2011 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <ucontext.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 "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
|
||||
#include "common/linux/breakpad_getcontext.h"
|
||||
#include "common/linux/eintr_wrapper.h"
|
||||
#include "common/linux/file_id.h"
|
||||
#include "common/linux/ignore_ret.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "common/scoped_ptr.h"
|
||||
#include "common/tests/auto_tempdir.h"
|
||||
#include "common/tests/file_utils.h"
|
||||
#include "common/using_std_string.h"
|
||||
#include "google_breakpad/processor/minidump.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
using google_breakpad::elf::FileID;
|
||||
using google_breakpad::elf::kDefaultBuildIdSize;
|
||||
|
||||
namespace {
|
||||
|
||||
typedef testing::Test MinidumpWriterTest;
|
||||
|
||||
const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest";
|
||||
|
||||
TEST(MinidumpWriterTest, SetupWithPath) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = child;
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(templ.c_str(), &st));
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, SetupWithFD) {
|
||||
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_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU);
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = child;
|
||||
ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context)));
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(templ.c_str(), &st));
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// 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 uint32_t memory_size = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
|
||||
|
||||
// Get some memory.
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
memory_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0));
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
|
||||
// Add information about the mapped memory.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress;
|
||||
info.size = memory_size;
|
||||
info.offset = 0;
|
||||
info.exec = false;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
AppMemoryList memory_list;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
|
||||
mappings, memory_list, false, 0, false));
|
||||
|
||||
// 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(memory_size, module->size());
|
||||
EXPECT_EQ(kMemoryName, module->code_file());
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
uint32_t len;
|
||||
// These streams are expected to be there
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len));
|
||||
EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len));
|
||||
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that minidumping is skipped while writing minidumps if principal mapping
|
||||
// is not referenced.
|
||||
TEST(MinidumpWriterTest, MinidumpSkippedIfRequested) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
|
||||
// pass an invalid principal mapping address, which will force
|
||||
// WriteMinidump to not write a minidump.
|
||||
ASSERT_FALSE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
|
||||
true, static_cast<uintptr_t>(0x0102030405060708ull),
|
||||
false));
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that minidumping is skipped while writing minidumps if principal mapping
|
||||
// is not referenced.
|
||||
TEST(MinidumpWriterTest, MinidumpStacksSkippedIfRequested) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
|
||||
// Create a thread that does not return, and only references libc (not the
|
||||
// current executable). This thread should not be captured in the minidump.
|
||||
pthread_t thread;
|
||||
pthread_attr_t thread_attributes;
|
||||
pthread_attr_init(&thread_attributes);
|
||||
pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED);
|
||||
sigset_t sigset;
|
||||
sigemptyset(&sigset);
|
||||
pthread_create(&thread, &thread_attributes,
|
||||
reinterpret_cast<void* (*)(void*)>(&sigsuspend), &sigset);
|
||||
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
|
||||
// Pass an invalid principal mapping address, which will force
|
||||
// WriteMinidump to not dump any thread stacks.
|
||||
ASSERT_TRUE(WriteMinidump(
|
||||
templ.c_str(), child, &context, sizeof(context), true,
|
||||
reinterpret_cast<uintptr_t>(google_breakpad::WriteFile), false));
|
||||
|
||||
// Read the minidump. And ensure that thread memory was dumped only for the
|
||||
// main thread.
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpThreadList* threads = minidump.GetThreadList();
|
||||
int threads_with_stacks = 0;
|
||||
for (unsigned int i = 0; i < threads->thread_count(); ++i) {
|
||||
MinidumpThread* thread = threads->GetThreadAtIndex(i);
|
||||
if (thread->GetMemory()) {
|
||||
++threads_with_stacks;
|
||||
}
|
||||
}
|
||||
#if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER)
|
||||
ASSERT_GE(threads_with_stacks, 1);
|
||||
#else
|
||||
ASSERT_EQ(threads_with_stacks, 1);
|
||||
#endif
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that stacks can be sanitized while writing minidumps.
|
||||
TEST(MinidumpWriterTest, StacksAreSanitizedIfRequested) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
// pass an invalid principal mapping address, which will force
|
||||
// WriteMinidump to not dump any thread stacks.
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
|
||||
false, 0, true));
|
||||
|
||||
// Read the minidump. And ensure that thread memory contains a defaced value.
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
const uintptr_t defaced =
|
||||
#if defined(__LP64__)
|
||||
0x0defaced0defaced;
|
||||
#else
|
||||
0x0defaced;
|
||||
#endif
|
||||
MinidumpThreadList* threads = minidump.GetThreadList();
|
||||
for (unsigned int i = 0; i < threads->thread_count(); ++i) {
|
||||
MinidumpThread* thread = threads->GetThreadAtIndex(i);
|
||||
MinidumpMemoryRegion* mem = thread->GetMemory();
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
uint32_t sz = mem->GetSize();
|
||||
const uint8_t* data = mem->GetMemory();
|
||||
ASSERT_TRUE(memmem(data, sz, &defaced, sizeof(defaced)) != nullptr);
|
||||
}
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that a binary with a longer-than-usual build id note
|
||||
// makes its way all the way through to the minidump unscathed.
|
||||
// The linux_client_unittest is linked with an explicit --build-id
|
||||
// in Makefile.am.
|
||||
TEST(MinidumpWriterTest, BuildIDLong) {
|
||||
int fds[2];
|
||||
ASSERT_NE(-1, pipe(fds));
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
const string dump_path = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
|
||||
EXPECT_TRUE(WriteMinidump(dump_path.c_str(),
|
||||
child, &context, sizeof(context)));
|
||||
close(fds[1]);
|
||||
|
||||
// Read the minidump. Load the module list, and ensure that
|
||||
// the main module has the correct debug id and code id.
|
||||
Minidump minidump(dump_path);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpModuleList* module_list = minidump.GetModuleList();
|
||||
ASSERT_TRUE(module_list);
|
||||
const MinidumpModule* module = module_list->GetMainModule();
|
||||
ASSERT_TRUE(module);
|
||||
const string module_identifier = "030201000504070608090A0B0C0D0E0F0";
|
||||
// This is passed explicitly to the linker in Makefile.am
|
||||
const string build_id =
|
||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
EXPECT_EQ(build_id, module->code_identifier());
|
||||
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// 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 int32_t memory_size = sysconf(_SC_PAGESIZE);
|
||||
const char* kMemoryName = "a fake module";
|
||||
const uint8_t kModuleGUID[sizeof(MDGUID)] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
||||
};
|
||||
const string module_identifier = "33221100554477668899AABBCCDDEEFF0";
|
||||
|
||||
// mmap a file
|
||||
AutoTempDir temp_dir;
|
||||
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.c_str());
|
||||
// fill with zeros
|
||||
google_breakpad::scoped_array<char> buffer(new char[memory_size]);
|
||||
memset(buffer.get(), 0, memory_size);
|
||||
ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
char* memory =
|
||||
reinterpret_cast<char*>(mmap(NULL,
|
||||
memory_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE,
|
||||
fd,
|
||||
0));
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
close(fd);
|
||||
|
||||
const pid_t child = fork();
|
||||
if (child == 0) {
|
||||
close(fds[1]);
|
||||
char b;
|
||||
IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b))));
|
||||
close(fds[0]);
|
||||
syscall(__NR_exit_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.tid = 1;
|
||||
|
||||
string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
|
||||
// Add information about the mapped memory. Report it as being larger than
|
||||
// it actually is.
|
||||
MappingInfo info;
|
||||
info.start_addr = kMemoryAddress - memory_size;
|
||||
info.size = memory_size * 3;
|
||||
info.offset = 0;
|
||||
info.exec = false;
|
||||
strcpy(info.name, kMemoryName);
|
||||
|
||||
MappingList mappings;
|
||||
AppMemoryList memory_list;
|
||||
MappingEntry mapping;
|
||||
mapping.first = info;
|
||||
memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
|
||||
mappings.push_back(mapping);
|
||||
ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context),
|
||||
mappings, memory_list));
|
||||
|
||||
// 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());
|
||||
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
TEST(MinidumpWriterTest, DeletedBinary) {
|
||||
const string kNumberOfThreadsArgument = "1";
|
||||
const string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Copy binary to a temp file.
|
||||
AutoTempDir temp_dir;
|
||||
string binpath = temp_dir.path() + "/linux-dumper-unittest-helper";
|
||||
ASSERT_TRUE(CopyFile(helper_path, binpath))
|
||||
<< "Failed to copy " << helper_path << " to " << binpath;
|
||||
ASSERT_EQ(0, chmod(binpath.c_str(), 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.c_str(),
|
||||
binpath.c_str(),
|
||||
pipe_fd_string,
|
||||
kNumberOfThreadsArgument.c_str(),
|
||||
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;
|
||||
const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk)));
|
||||
ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr);
|
||||
close(fds[0]);
|
||||
|
||||
// Child is ready now.
|
||||
// Unlink the test binary.
|
||||
unlink(binpath.c_str());
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
// Set a non-zero tid to avoid tripping asserts.
|
||||
context.tid = child_pid;
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context,
|
||||
sizeof(context)));
|
||||
kill(child_pid, SIGKILL);
|
||||
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(templ.c_str(), &st));
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
|
||||
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.c_str(), module->code_file().c_str());
|
||||
// Check that the file ID is correct.
|
||||
FileID fileid(helper_path.c_str());
|
||||
PageAllocator allocator;
|
||||
wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize);
|
||||
EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
|
||||
string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier);
|
||||
string module_identifier(identifier_string);
|
||||
// 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";
|
||||
EXPECT_EQ(module_identifier, module->debug_identifier());
|
||||
|
||||
IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that an additional memory region can be added to the minidump.
|
||||
TEST(MinidumpWriterTest, AdditionalMemory) {
|
||||
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 uint32_t kMemorySize = sysconf(_SC_PAGESIZE);
|
||||
|
||||
// Get some heap memory.
|
||||
uint8_t* memory = new uint8_t[kMemorySize];
|
||||
const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
|
||||
ASSERT_TRUE(memory);
|
||||
|
||||
// Stick some data into the memory so the contents can be verified.
|
||||
for (uint32_t i = 0; i < kMemorySize; ++i) {
|
||||
memory[i] = i % 255;
|
||||
}
|
||||
|
||||
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_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
|
||||
// This needs a valid context for minidump writing to work, but getting
|
||||
// a useful one from the child is too much work, so just use one from
|
||||
// the parent since the child is just a forked copy anyway.
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
unlink(templ.c_str());
|
||||
|
||||
MappingList mappings;
|
||||
AppMemoryList memory_list;
|
||||
|
||||
// Add the memory region to the list of memory to be included.
|
||||
AppMemory app_memory;
|
||||
app_memory.ptr = memory;
|
||||
app_memory.length = kMemorySize;
|
||||
memory_list.push_back(app_memory);
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context),
|
||||
mappings, memory_list));
|
||||
|
||||
// Read the minidump. Ensure that the memory region is present
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
|
||||
ASSERT_TRUE(dump_memory_list);
|
||||
const MinidumpMemoryRegion* region =
|
||||
dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
|
||||
ASSERT_TRUE(region);
|
||||
|
||||
EXPECT_EQ(kMemoryAddress, region->GetBase());
|
||||
EXPECT_EQ(kMemorySize, region->GetSize());
|
||||
|
||||
// Verify memory contents.
|
||||
EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
|
||||
|
||||
delete[] memory;
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that an invalid thread stack pointer still results in a minidump.
|
||||
TEST(MinidumpWriterTest, InvalidStackPointer) {
|
||||
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_group);
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
ExceptionHandler::CrashContext context;
|
||||
|
||||
// This needs a valid context for minidump writing to work, but getting
|
||||
// a useful one from the child is too much work, so just use one from
|
||||
// the parent since the child is just a forked copy anyway.
|
||||
ASSERT_EQ(0, getcontext(&context.context));
|
||||
context.tid = child;
|
||||
|
||||
// Fake the child's stack pointer for its crashing thread. NOTE: This must
|
||||
// be an invalid memory address for the child process (stack or otherwise).
|
||||
// Try 1MB below the current stack.
|
||||
uintptr_t invalid_stack_pointer =
|
||||
reinterpret_cast<uintptr_t>(&context) - 1024*1024;
|
||||
#if defined(__i386)
|
||||
context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer;
|
||||
#elif defined(__x86_64)
|
||||
context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer;
|
||||
#elif defined(__ARM_EABI__)
|
||||
context.context.uc_mcontext.arm_sp = invalid_stack_pointer;
|
||||
#elif defined(__aarch64__)
|
||||
context.context.uc_mcontext.sp = invalid_stack_pointer;
|
||||
#elif defined(__mips__)
|
||||
context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] =
|
||||
invalid_stack_pointer;
|
||||
#elif defined(__riscv)
|
||||
context.context.uc_mcontext.__gregs[MD_CONTEXT_RISCV_REG_SP] =
|
||||
invalid_stack_pointer;
|
||||
#else
|
||||
# error "This code has not been ported to your platform yet."
|
||||
#endif
|
||||
|
||||
AutoTempDir temp_dir;
|
||||
string templ = temp_dir.path() + kMDWriterUnitTestFileName;
|
||||
// NOTE: In previous versions of Breakpad, WriteMinidump() would fail if
|
||||
// presented with an invalid stack pointer.
|
||||
ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context)));
|
||||
|
||||
// Read the minidump. Ensure that the memory region is present
|
||||
Minidump minidump(templ);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
|
||||
// TODO(ted.mielczarek,mkrebs): Enable this part of the test once
|
||||
// https://breakpad.appspot.com/413002/ is committed.
|
||||
#if 0
|
||||
// Make sure there's a thread without a stack. NOTE: It's okay if
|
||||
// GetThreadList() shows the error: "ERROR: MinidumpThread has a memory
|
||||
// region problem".
|
||||
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
|
||||
ASSERT_TRUE(dump_thread_list);
|
||||
bool found_empty_stack = false;
|
||||
for (int i = 0; i < dump_thread_list->thread_count(); i++) {
|
||||
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
|
||||
ASSERT_TRUE(thread->thread() != NULL);
|
||||
// When the stack size is zero bytes, GetMemory() returns NULL.
|
||||
if (thread->GetMemory() == NULL) {
|
||||
found_empty_stack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// NOTE: If you fail this, first make sure that "invalid_stack_pointer"
|
||||
// above is indeed set to an invalid address.
|
||||
ASSERT_TRUE(found_empty_stack);
|
||||
#endif
|
||||
|
||||
close(fds[1]);
|
||||
IGNORE_EINTR(waitpid(child, nullptr, 0));
|
||||
}
|
||||
|
||||
// Test that limiting the size of the minidump works.
|
||||
TEST(MinidumpWriterTest, MinidumpSizeLimit) {
|
||||
static const int kNumberOfThreadsInHelperProgram = 40;
|
||||
|
||||
char number_of_threads_arg[3];
|
||||
sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram);
|
||||
|
||||
string helper_path(GetHelperBinary());
|
||||
if (helper_path.empty()) {
|
||||
FAIL() << "Couldn't find helper binary";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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(helper_path.c_str(),
|
||||
helper_path.c_str(),
|
||||
pipe_fd_string,
|
||||
number_of_threads_arg,
|
||||
NULL);
|
||||
}
|
||||
close(fds[1]);
|
||||
|
||||
// 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)),
|
||||
static_cast<ssize_t>(sizeof(junk)));
|
||||
}
|
||||
close(fds[0]);
|
||||
|
||||
// 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);
|
||||
|
||||
// Child and its threads are ready now.
|
||||
|
||||
|
||||
off_t normal_file_size;
|
||||
int total_normal_stack_size = 0;
|
||||
AutoTempDir temp_dir;
|
||||
|
||||
// First, write a minidump with no size limit.
|
||||
{
|
||||
string normal_dump = temp_dir.path() +
|
||||
"/minidump-writer-unittest.dmp";
|
||||
ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1,
|
||||
child_pid, NULL, 0,
|
||||
MappingList(), AppMemoryList()));
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(normal_dump.c_str(), &st));
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
normal_file_size = st.st_size;
|
||||
|
||||
Minidump minidump(normal_dump);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
|
||||
ASSERT_TRUE(dump_thread_list);
|
||||
for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
|
||||
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
|
||||
ASSERT_TRUE(thread->thread() != NULL);
|
||||
// When the stack size is zero bytes, GetMemory() returns NULL.
|
||||
MinidumpMemoryRegion* memory = thread->GetMemory();
|
||||
ASSERT_TRUE(memory != NULL);
|
||||
total_normal_stack_size += memory->GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
// Second, write a minidump with a size limit big enough to not trigger
|
||||
// anything.
|
||||
{
|
||||
// Set size limit arbitrarily 1MB larger than the normal file size -- such
|
||||
// that the limiting code will not kick in.
|
||||
const off_t minidump_size_limit = normal_file_size + 1024*1024;
|
||||
|
||||
string same_dump = temp_dir.path() +
|
||||
"/minidump-writer-unittest-same.dmp";
|
||||
ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit,
|
||||
child_pid, NULL, 0,
|
||||
MappingList(), AppMemoryList()));
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(same_dump.c_str(), &st));
|
||||
// Make sure limiting wasn't actually triggered. NOTE: If you fail this,
|
||||
// first make sure that "minidump_size_limit" above is indeed set to a
|
||||
// large enough value -- the limit-checking code in minidump_writer.cc
|
||||
// does just a rough estimate.
|
||||
ASSERT_EQ(normal_file_size, st.st_size);
|
||||
}
|
||||
|
||||
// Third, write a minidump with a size limit small enough to be triggered.
|
||||
{
|
||||
// Set size limit to some arbitrary amount, such that the limiting code
|
||||
// will kick in. The equation used to set this value was determined by
|
||||
// simply reversing the size-limit logic a little bit in order to pick a
|
||||
// size we know will trigger it. The definition of
|
||||
// kLimitAverageThreadStackLength here was copied from class
|
||||
// MinidumpWriter in minidump_writer.cc.
|
||||
static const unsigned kLimitAverageThreadStackLength = 8 * 1024;
|
||||
off_t minidump_size_limit = kNumberOfThreadsInHelperProgram *
|
||||
kLimitAverageThreadStackLength;
|
||||
// If, in reality, each of the threads' stack is *smaller* than
|
||||
// kLimitAverageThreadStackLength, the normal file size could very well be
|
||||
// smaller than the arbitrary limit that was just set. In that case,
|
||||
// either of these numbers should trigger the size-limiting code, but we
|
||||
// might as well pick the smallest.
|
||||
if (normal_file_size < minidump_size_limit)
|
||||
minidump_size_limit = normal_file_size;
|
||||
|
||||
string limit_dump = temp_dir.path() +
|
||||
"/minidump-writer-unittest-limit.dmp";
|
||||
ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit,
|
||||
child_pid, NULL, 0,
|
||||
MappingList(), AppMemoryList()));
|
||||
struct stat st;
|
||||
ASSERT_EQ(0, stat(limit_dump.c_str(), &st));
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
// Make sure the file size is at least smaller than the original. If this
|
||||
// fails because it's the same size, then the size-limit logic didn't kick
|
||||
// in like it was supposed to.
|
||||
EXPECT_LT(st.st_size, normal_file_size);
|
||||
|
||||
Minidump minidump(limit_dump);
|
||||
ASSERT_TRUE(minidump.Read());
|
||||
MinidumpThreadList* dump_thread_list = minidump.GetThreadList();
|
||||
ASSERT_TRUE(dump_thread_list);
|
||||
int total_limit_stack_size = 0;
|
||||
for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) {
|
||||
MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i);
|
||||
ASSERT_TRUE(thread->thread() != NULL);
|
||||
// When the stack size is zero bytes, GetMemory() returns NULL.
|
||||
MinidumpMemoryRegion* memory = thread->GetMemory();
|
||||
ASSERT_TRUE(memory != NULL);
|
||||
total_limit_stack_size += memory->GetSize();
|
||||
}
|
||||
|
||||
// Make sure stack size shrunk by at least 1KB per extra thread. The
|
||||
// definition of kLimitBaseThreadCount here was copied from class
|
||||
// MinidumpWriter in minidump_writer.cc.
|
||||
// Note: The 1KB is arbitrary, and assumes that the thread stacks are big
|
||||
// enough to shrink by that much. For example, if each thread stack was
|
||||
// originally only 2KB, the current size-limit logic wouldn't actually
|
||||
// shrink them because that's the size to which it tries to shrink. If
|
||||
// you fail this part of the test due to something like that, the test
|
||||
// logic should probably be improved to account for your situation.
|
||||
const unsigned kLimitBaseThreadCount = 20;
|
||||
const unsigned kMinPerExtraThreadStackReduction = 1024;
|
||||
const int min_expected_reduction = (kNumberOfThreadsInHelperProgram -
|
||||
kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction;
|
||||
EXPECT_LT(total_limit_stack_size,
|
||||
total_normal_stack_size - min_expected_reduction);
|
||||
}
|
||||
|
||||
// Kill the helper program.
|
||||
kill(child_pid, SIGKILL);
|
||||
IGNORE_EINTR(waitpid(child_pid, nullptr, 0));
|
||||
}
|
||||
|
||||
} // namespace
|
69
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc
vendored
Normal file
69
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2011 Google LLC
|
||||
//
|
||||
// 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 LLC 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_writer_unittest_utils.cc:
|
||||
// Shared routines used by unittests under client/linux/minidump_writer.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
|
||||
#include "common/linux/safe_readlink.h"
|
||||
#include "common/using_std_string.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
string GetHelperBinary() {
|
||||
string helper_path;
|
||||
char* bindir = getenv("bindir");
|
||||
if (bindir) {
|
||||
helper_path = string(bindir) + "/";
|
||||
} else {
|
||||
// Locate helper binary next to the current binary.
|
||||
char self_path[PATH_MAX];
|
||||
if (!SafeReadLink("/proc/self/exe", self_path)) {
|
||||
return "";
|
||||
}
|
||||
helper_path = string(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;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
48
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.h
vendored
Normal file
48
externals/breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.h
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2012 Google LLC
|
||||
//
|
||||
// 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 LLC 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_writer_unittest_utils.h:
|
||||
// Shared routines used by unittests under client/linux/minidump_writer.
|
||||
|
||||
#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/using_std_string.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// Returns the full path to linux_dumper_unittest_helper. The full path is
|
||||
// discovered either by using the environment variable "bindir" or by using
|
||||
// the location of the main module of the currently running process.
|
||||
string GetHelperBinary();
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_
|
151
externals/breakpad/src/client/linux/minidump_writer/pe_file.cc
vendored
Normal file
151
externals/breakpad/src/client/linux/minidump_writer/pe_file.cc
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "client/linux/minidump_writer/pe_file.h"
|
||||
#include "client/linux/minidump_writer/pe_structs.h"
|
||||
#include "common/linux/memory_mapped_file.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
PEFileFormat PEFile::TryGetDebugInfo(const char* filename,
|
||||
PRSDS_DEBUG_FORMAT debug_info) {
|
||||
MemoryMappedFile mapped_file(filename, 0);
|
||||
if (!mapped_file.data())
|
||||
return PEFileFormat::notPeCoff;
|
||||
const void* base = mapped_file.data();
|
||||
const size_t file_size = mapped_file.size();
|
||||
|
||||
const IMAGE_DOS_HEADER* header =
|
||||
TryReadStruct<IMAGE_DOS_HEADER>(base, 0, file_size);
|
||||
if (!header || (header->e_magic != IMAGE_DOS_SIGNATURE)) {
|
||||
return PEFileFormat::notPeCoff;
|
||||
}
|
||||
|
||||
// NTHeader is at position 'e_lfanew'.
|
||||
DWORD nt_header_offset = header->e_lfanew;
|
||||
// First, read a common IMAGE_NT_HEADERS structure. It should contain a
|
||||
// special flag marking whether PE module is x64 (OptionalHeader.Magic)
|
||||
// and so-called NT_SIGNATURE in Signature field.
|
||||
const IMAGE_NT_HEADERS* nt_header =
|
||||
TryReadStruct<IMAGE_NT_HEADERS>(base, nt_header_offset, file_size);
|
||||
if (!nt_header || (nt_header->Signature != IMAGE_NT_SIGNATURE)
|
||||
|| ((nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
|
||||
&& (nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)))
|
||||
return PEFileFormat::notPeCoff;
|
||||
|
||||
bool x64 = nt_header->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
|
||||
WORD sections_number = nt_header->FileHeader.NumberOfSections;
|
||||
DWORD debug_offset;
|
||||
DWORD debug_size;
|
||||
DWORD section_offset;
|
||||
if (x64) {
|
||||
const IMAGE_NT_HEADERS64* header_64 =
|
||||
TryReadStruct<IMAGE_NT_HEADERS64>(base, nt_header_offset, file_size);
|
||||
if (!header_64)
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
debug_offset =
|
||||
header_64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
|
||||
.VirtualAddress;
|
||||
debug_size =
|
||||
header_64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
|
||||
.Size;
|
||||
section_offset = nt_header_offset + sizeof(IMAGE_NT_HEADERS64);
|
||||
} else {
|
||||
const IMAGE_NT_HEADERS32* header_32 =
|
||||
TryReadStruct<IMAGE_NT_HEADERS32>(base, nt_header_offset, file_size);
|
||||
if (!header_32)
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
debug_offset =
|
||||
header_32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
|
||||
.VirtualAddress;
|
||||
debug_size =
|
||||
header_32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
|
||||
.Size;
|
||||
section_offset = nt_header_offset + sizeof(IMAGE_NT_HEADERS32);
|
||||
}
|
||||
|
||||
DWORD debug_end_pos = debug_offset + debug_size;
|
||||
while (debug_offset < debug_end_pos) {
|
||||
for (WORD i = 0; i < sections_number; ++i) {
|
||||
// Section headers are placed sequentially after the NT_HEADER (32/64).
|
||||
const IMAGE_SECTION_HEADER* section =
|
||||
TryReadStruct<IMAGE_SECTION_HEADER>(base, section_offset, file_size);
|
||||
if (!section)
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
|
||||
section_offset += sizeof(IMAGE_SECTION_HEADER);
|
||||
|
||||
// Current `debug_offset` should be inside a section, stop if we find
|
||||
// a suitable one (we don't consider any malformed sections here).
|
||||
if ((section->VirtualAddress <= debug_offset) &&
|
||||
(debug_offset < section->VirtualAddress + section->SizeOfRawData)) {
|
||||
DWORD offset =
|
||||
section->PointerToRawData + debug_offset - section->VirtualAddress;
|
||||
// Go to the position of current ImageDebugDirectory (offset).
|
||||
const IMAGE_DEBUG_DIRECTORY* debug_directory =
|
||||
TryReadStruct<IMAGE_DEBUG_DIRECTORY>(base, offset, file_size);
|
||||
if (!debug_directory)
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
// Process ImageDebugDirectory with CodeViewRecord type and skip
|
||||
// all others.
|
||||
if (debug_directory->Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
|
||||
DWORD debug_directory_size = debug_directory->SizeOfData;
|
||||
if (debug_directory_size < sizeof(RSDS_DEBUG_FORMAT))
|
||||
// RSDS section is malformed.
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
// Go to the position of current ImageDebugDirectory Raw Data
|
||||
// (debug_directory->PointerToRawData) and read the RSDS section.
|
||||
const RSDS_DEBUG_FORMAT* rsds =
|
||||
TryReadStruct<RSDS_DEBUG_FORMAT>(
|
||||
base, debug_directory->PointerToRawData, file_size);
|
||||
|
||||
if (!rsds)
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
|
||||
memcpy(debug_info->guid, rsds->guid, sizeof(rsds->guid));
|
||||
memcpy(debug_info->age, rsds->age, sizeof(rsds->age));
|
||||
return PEFileFormat::peWithBuildId;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug_offset += sizeof(IMAGE_DEBUG_DIRECTORY);
|
||||
}
|
||||
|
||||
return PEFileFormat::peWithoutBuildId;
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
76
externals/breakpad/src/client/linux/minidump_writer/pe_file.h
vendored
Normal file
76
externals/breakpad/src/client/linux/minidump_writer/pe_file.h
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// 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 LLC 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_PE_FILE_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_PE_FILE_H_
|
||||
|
||||
#include "client/linux/minidump_writer/pe_structs.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
typedef enum {
|
||||
notPeCoff = 0,
|
||||
peWithoutBuildId = 1,
|
||||
peWithBuildId = 2
|
||||
} PEFileFormat;
|
||||
|
||||
class PEFile {
|
||||
public:
|
||||
/**
|
||||
* Attempts to parse RSDS_DEBUG_FORMAT record from a PE (Portable
|
||||
* Executable) file. To do this we check whether the loaded file is a PE
|
||||
* file, and if it is - try to find IMAGE_DEBUG_DIRECTORY structure with
|
||||
* its type set to IMAGE_DEBUG_TYPE_CODEVIEW.
|
||||
*
|
||||
* @param filename Filename for the module to parse.
|
||||
* @param debug_info RSDS_DEBUG_FORMAT struct to be populated with PE debug
|
||||
* info (GUID and age).
|
||||
* @return
|
||||
* notPeCoff: not PE/COFF file;
|
||||
* peWithoutBuildId: a PE/COFF file but build-id is not set;
|
||||
* peWithBuildId: a PE/COFF file and build-id is set.
|
||||
*/
|
||||
static PEFileFormat TryGetDebugInfo(const char* filename,
|
||||
PRSDS_DEBUG_FORMAT debug_info);
|
||||
|
||||
private:
|
||||
template <class TStruct>
|
||||
static const TStruct* TryReadStruct(const void* base,
|
||||
const DWORD position,
|
||||
const size_t file_size) {
|
||||
if (position + sizeof(TStruct) >= file_size){
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const void* ptr = static_cast<const char*>(base) + position;
|
||||
return reinterpret_cast<const TStruct*>(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PE_FILE_H_
|
225
externals/breakpad/src/client/linux/minidump_writer/pe_structs.h
vendored
Normal file
225
externals/breakpad/src/client/linux/minidump_writer/pe_structs.h
vendored
Normal file
|
@ -0,0 +1,225 @@
|
|||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// 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 LLC 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_PE_STRUCTS_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_PE_STRUCTS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
typedef uint8_t BYTE;
|
||||
typedef uint16_t WORD;
|
||||
typedef uint32_t DWORD;
|
||||
typedef uint64_t ULONGLONG;
|
||||
|
||||
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
|
||||
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
|
||||
|
||||
#define IMAGE_DEBUG_TYPE_CODEVIEW 2
|
||||
|
||||
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
|
||||
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
|
||||
|
||||
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
|
||||
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
|
||||
|
||||
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
|
||||
WORD e_magic; // Magic number
|
||||
WORD e_cblp; // Bytes on last page of file
|
||||
WORD e_cp; // Pages in file
|
||||
WORD e_crlc; // Relocations
|
||||
WORD e_cparhdr; // Size of header in paragraphs
|
||||
WORD e_minalloc; // Minimum extra paragraphs needed
|
||||
WORD e_maxalloc; // Maximum extra paragraphs needed
|
||||
WORD e_ss; // Initial (relative) SS value
|
||||
WORD e_sp; // Initial SP value
|
||||
WORD e_csum; // Checksum
|
||||
WORD e_ip; // Initial IP value
|
||||
WORD e_cs; // Initial (relative) CS value
|
||||
WORD e_lfarlc; // File address of relocation table
|
||||
WORD e_ovno; // Overlay number
|
||||
WORD e_res[4]; // Reserved words
|
||||
WORD e_oemid; // OEM identifier (for e_oeminfo)
|
||||
WORD e_oeminfo; // OEM information; e_oemid specific
|
||||
WORD e_res2[10]; // Reserved words
|
||||
DWORD e_lfanew; // File address of new exe header
|
||||
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
|
||||
|
||||
typedef struct _IMAGE_FILE_HEADER {
|
||||
WORD Machine;
|
||||
WORD NumberOfSections;
|
||||
DWORD TimeDateStamp;
|
||||
DWORD PointerToSymbolTable;
|
||||
DWORD NumberOfSymbols;
|
||||
WORD SizeOfOptionalHeader;
|
||||
WORD Characteristics;
|
||||
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
|
||||
|
||||
typedef struct _IMAGE_DATA_DIRECTORY {
|
||||
DWORD VirtualAddress;
|
||||
DWORD Size;
|
||||
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
|
||||
|
||||
|
||||
typedef struct _IMAGE_DEBUG_DIRECTORY {
|
||||
DWORD Characteristics;
|
||||
DWORD TimeDateStamp;
|
||||
WORD MajorVersion;
|
||||
WORD MinorVersion;
|
||||
DWORD Type;
|
||||
DWORD SizeOfData;
|
||||
DWORD AddressOfRawData;
|
||||
DWORD PointerToRawData;
|
||||
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
|
||||
|
||||
typedef struct _IMAGE_OPTIONAL_HEADER64 {
|
||||
//
|
||||
// Standard fields - Magic.
|
||||
//
|
||||
WORD Magic;
|
||||
BYTE MajorLinkerVersion;
|
||||
BYTE MinorLinkerVersion;
|
||||
DWORD SizeOfCode;
|
||||
DWORD SizeOfInitializedData;
|
||||
DWORD SizeOfUninitializedData;
|
||||
DWORD AddressOfEntryPoint;
|
||||
DWORD BaseOfCode;
|
||||
//
|
||||
// NT additional fields.
|
||||
//
|
||||
ULONGLONG ImageBase;
|
||||
DWORD SectionAlignment;
|
||||
DWORD FileAlignment;
|
||||
WORD MajorOperatingSystemVersion;
|
||||
WORD MinorOperatingSystemVersion;
|
||||
WORD MajorImageVersion;
|
||||
WORD MinorImageVersion;
|
||||
WORD MajorSubsystemVersion;
|
||||
WORD MinorSubsystemVersion;
|
||||
DWORD Win32VersionValue;
|
||||
DWORD SizeOfImage;
|
||||
DWORD SizeOfHeaders;
|
||||
DWORD CheckSum;
|
||||
WORD Subsystem;
|
||||
WORD DllCharacteristics;
|
||||
ULONGLONG SizeOfStackReserve;
|
||||
ULONGLONG SizeOfStackCommit;
|
||||
ULONGLONG SizeOfHeapReserve;
|
||||
ULONGLONG SizeOfHeapCommit;
|
||||
DWORD LoaderFlags;
|
||||
DWORD NumberOfRvaAndSizes;
|
||||
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
|
||||
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
|
||||
|
||||
typedef struct _IMAGE_OPTIONAL_HEADER {
|
||||
//
|
||||
// Standard fields.
|
||||
//
|
||||
WORD Magic;
|
||||
BYTE MajorLinkerVersion;
|
||||
BYTE MinorLinkerVersion;
|
||||
DWORD SizeOfCode;
|
||||
DWORD SizeOfInitializedData;
|
||||
DWORD SizeOfUninitializedData;
|
||||
DWORD AddressOfEntryPoint;
|
||||
DWORD BaseOfCode;
|
||||
DWORD BaseOfData;
|
||||
//
|
||||
// NT additional fields.
|
||||
//
|
||||
DWORD ImageBase;
|
||||
DWORD SectionAlignment;
|
||||
DWORD FileAlignment;
|
||||
WORD MajorOperatingSystemVersion;
|
||||
WORD MinorOperatingSystemVersion;
|
||||
WORD MajorImageVersion;
|
||||
WORD MinorImageVersion;
|
||||
WORD MajorSubsystemVersion;
|
||||
WORD MinorSubsystemVersion;
|
||||
DWORD Win32VersionValue;
|
||||
DWORD SizeOfImage;
|
||||
DWORD SizeOfHeaders;
|
||||
DWORD CheckSum;
|
||||
WORD Subsystem;
|
||||
WORD DllCharacteristics;
|
||||
DWORD SizeOfStackReserve;
|
||||
DWORD SizeOfStackCommit;
|
||||
DWORD SizeOfHeapReserve;
|
||||
DWORD SizeOfHeapCommit;
|
||||
DWORD LoaderFlags;
|
||||
DWORD NumberOfRvaAndSizes;
|
||||
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
|
||||
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
|
||||
|
||||
typedef struct _IMAGE_NT_HEADERS64 {
|
||||
DWORD Signature;
|
||||
IMAGE_FILE_HEADER FileHeader;
|
||||
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
|
||||
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
|
||||
|
||||
typedef struct _IMAGE_NT_HEADERS32 {
|
||||
DWORD Signature;
|
||||
IMAGE_FILE_HEADER FileHeader;
|
||||
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
|
||||
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
|
||||
|
||||
typedef struct _IMAGE_NT_HEADERS {
|
||||
DWORD Signature;
|
||||
IMAGE_FILE_HEADER FileHeader;
|
||||
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
|
||||
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
|
||||
|
||||
#define IMAGE_SIZEOF_SHORT_NAME 8
|
||||
|
||||
typedef struct _IMAGE_SECTION_HEADER {
|
||||
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
|
||||
union {
|
||||
DWORD PhysicalAddress;
|
||||
DWORD VirtualSize;
|
||||
} Misc;
|
||||
DWORD VirtualAddress;
|
||||
DWORD SizeOfRawData;
|
||||
DWORD PointerToRawData;
|
||||
DWORD PointerToRelocations;
|
||||
DWORD PointerToLinenumbers;
|
||||
WORD NumberOfRelocations;
|
||||
WORD NumberOfLinenumbers;
|
||||
DWORD Characteristics;
|
||||
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
|
||||
|
||||
typedef struct _RSDS_DEBUG_FORMAT {
|
||||
DWORD signature;
|
||||
BYTE guid[16];
|
||||
BYTE age[4];
|
||||
char pdbpath[1];
|
||||
} RSDS_DEBUG_FORMAT, *PRSDS_DEBUG_FORMAT;
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PE_STRUCTS_H_
|
129
externals/breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader.h
vendored
Normal file
129
externals/breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader.h
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// 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 LLC 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_PROC_CPUINFO_READER_H_
|
||||
#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "client/linux/minidump_writer/line_reader.h"
|
||||
#include "common/linux/linux_libc_support.h"
|
||||
#include "third_party/lss/linux_syscall_support.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
// A class for reading /proc/cpuinfo without using fopen/fgets or other
|
||||
// functions which may allocate memory.
|
||||
class ProcCpuInfoReader {
|
||||
public:
|
||||
ProcCpuInfoReader(int fd)
|
||||
: line_reader_(fd), pop_count_(-1) {
|
||||
}
|
||||
|
||||
// Return the next field name, or NULL in case of EOF.
|
||||
// field: (output) Pointer to zero-terminated field name.
|
||||
// Returns true on success, or false on EOF or error (line too long).
|
||||
bool GetNextField(const char** field) {
|
||||
for (;;) {
|
||||
const char* line;
|
||||
unsigned line_len;
|
||||
|
||||
// Try to read next line.
|
||||
if (pop_count_ >= 0) {
|
||||
line_reader_.PopLine(pop_count_);
|
||||
pop_count_ = -1;
|
||||
}
|
||||
|
||||
if (!line_reader_.GetNextLine(&line, &line_len))
|
||||
return false;
|
||||
|
||||
pop_count_ = static_cast<int>(line_len);
|
||||
|
||||
const char* line_end = line + line_len;
|
||||
|
||||
// Expected format: <field-name> <space>+ ':' <space> <value>
|
||||
// Note that:
|
||||
// - empty lines happen.
|
||||
// - <field-name> can contain spaces.
|
||||
// - some fields have an empty <value>
|
||||
char* sep = static_cast<char*>(my_memchr(line, ':', line_len));
|
||||
if (sep == NULL)
|
||||
continue;
|
||||
|
||||
// Record the value. Skip leading space after the column to get
|
||||
// its start.
|
||||
const char* val = sep+1;
|
||||
while (val < line_end && my_isspace(*val))
|
||||
val++;
|
||||
|
||||
value_ = val;
|
||||
value_len_ = static_cast<size_t>(line_end - val);
|
||||
|
||||
// Remove trailing spaces before the column to properly 0-terminate
|
||||
// the field name.
|
||||
while (sep > line && my_isspace(sep[-1]))
|
||||
sep--;
|
||||
|
||||
if (sep == line)
|
||||
continue;
|
||||
|
||||
// zero-terminate field name.
|
||||
*sep = '\0';
|
||||
|
||||
*field = line;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the field value. This must be called after a succesful
|
||||
// call to GetNextField().
|
||||
const char* GetValue() {
|
||||
assert(value_);
|
||||
return value_;
|
||||
}
|
||||
|
||||
// Same as GetValue(), but also returns the length in characters of
|
||||
// the value.
|
||||
const char* GetValueAndLen(size_t* length) {
|
||||
assert(value_);
|
||||
*length = value_len_;
|
||||
return value_;
|
||||
}
|
||||
|
||||
private:
|
||||
LineReader line_reader_;
|
||||
int pop_count_;
|
||||
const char* value_;
|
||||
size_t value_len_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_
|
188
externals/breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
vendored
Normal file
188
externals/breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2013 Google LLC
|
||||
//
|
||||
// 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 LLC 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h> // Must come first
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "client/linux/minidump_writer/proc_cpuinfo_reader.h"
|
||||
#include "breakpad_googletest_includes.h"
|
||||
#include "common/linux/scoped_tmpfile.h"
|
||||
|
||||
using namespace google_breakpad;
|
||||
|
||||
namespace {
|
||||
|
||||
typedef testing::Test ProcCpuInfoReaderTest;
|
||||
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, EmptyFile) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(""));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, OneLineTerminated) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, OneLine) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
size_t value_len;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValueAndLen(&value_len));
|
||||
ASSERT_EQ(3U, value_len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\nzoo : tut\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("zoo", field);
|
||||
ASSERT_STREQ("tut", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipMalformedLine) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("this line should have a column\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("\n\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipEmptyField) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString(" : bar\nzoo : tut\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("zoo", field);
|
||||
ASSERT_STREQ("tut", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo : bar\n\n\nfoo : bar\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
ASSERT_STREQ("bar", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, FieldWithSpaces) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo bar : zoo\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo bar", field);
|
||||
ASSERT_STREQ("zoo", reader.GetValue());
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
||||
|
||||
TEST(ProcCpuInfoReaderTest, EmptyValue) {
|
||||
ScopedTmpFile file;
|
||||
ASSERT_TRUE(file.InitString("foo :\n"));
|
||||
ProcCpuInfoReader reader(file.GetFd());
|
||||
|
||||
const char* field;
|
||||
ASSERT_TRUE(reader.GetNextField(&field));
|
||||
ASSERT_STREQ("foo", field);
|
||||
size_t value_len;
|
||||
ASSERT_STREQ("", reader.GetValueAndLen(&value_len));
|
||||
ASSERT_EQ(0U, value_len);
|
||||
|
||||
ASSERT_FALSE(reader.GetNextField(&field));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue