diff options
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/linux/minidump_writer')
22 files changed, 7021 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h new file mode 100644 index 0000000000..1cca9aa5a0 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set.h @@ -0,0 +1,144 @@ +// Copyright (c) 2013, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc new file mode 100644 index 0000000000..75172e9938 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/cpu_set_unittest.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2013, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "breakpad_googletest_includes.h" +#include "linux/minidump_writer/cpu_set.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test CpuSetTest; + +// Helper class to write test text file to a temporary file and return +// its file descriptor. +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("cpu_set", text) { + } +}; + +} + +TEST(CpuSetTest, EmptyCount) { + CpuSet set; + ASSERT_EQ(0, set.GetCount()); +} + +TEST(CpuSetTest, OneCpu) { + ScopedTestFile file("10"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(1, set.GetCount()); +} + +TEST(CpuSetTest, OneCpuTerminated) { + ScopedTestFile file("10\n"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(1, set.GetCount()); +} + +TEST(CpuSetTest, TwoCpusWithComma) { + ScopedTestFile file("1,10"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(2, set.GetCount()); +} + +TEST(CpuSetTest, TwoCpusWithRange) { + ScopedTestFile file("1-2"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(2, set.GetCount()); +} + +TEST(CpuSetTest, TenCpusWithRange) { + ScopedTestFile file("9-18"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(10, set.GetCount()); +} + +TEST(CpuSetTest, MultiItems) { + ScopedTestFile file("0, 2-4, 128"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(5, set.GetCount()); +} + +TEST(CpuSetTest, IntersectWith) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + ScopedTestFile file2("16-24"); + ASSERT_TRUE(file2.IsOk()); + 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) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + set1.IntersectWith(set1); + ASSERT_EQ(11, set1.GetCount()); +} + +TEST(CpuSetTest, EmptyIntersection) { + ScopedTestFile file1("0-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(20, set1.GetCount()); + + ScopedTestFile file2("20-39"); + ASSERT_TRUE(file2.IsOk()); + 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()); +} + diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h new file mode 100644 index 0000000000..a4bde18031 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc new file mode 100644 index 0000000000..b33507db95 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/directory_reader_unittest.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <set> +#include <string> + +#include <dirent.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "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); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h new file mode 100644 index 0000000000..d8e2dbcc11 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader.h @@ -0,0 +1,131 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "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 = 1024; + + // 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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc new file mode 100644 index 0000000000..576b8b5cdb --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/line_reader_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +#include "linux/minidump_writer/line_reader.h" +#include "breakpad_googletest_includes.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test LineReaderTest; + +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("line_reader", text) { + } + + ScopedTestFile(const char* text, size_t text_len) + : AutoTestFile("line_reader", text, text_len) { + } +}; + +} + +TEST(LineReaderTest, EmptyFile) { + ScopedTestFile file(""); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, OneLineTerminated) { + ScopedTestFile file("a\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("a"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("a\nb\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("a\nb"); + ASSERT_TRUE(file.IsOk()); + 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)); + ScopedTestFile file(l, sizeof(l)); + ASSERT_TRUE(file.IsOk()); + 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)); + ScopedTestFile file(l, sizeof(l)); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc new file mode 100644 index 0000000000..38feb0aaa7 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper. +// See linux_core_dumper.h for details. + +#include "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]); +#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::GetThreadNameByIndex(size_t index, char* name, + size_t size) { + // Not implemented + return false; +} + +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; + } + + 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]; +#else // __mips__ + memcpy(&info.regs, status->pr_reg, sizeof(info.regs)); +#endif // __mips__ + 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 diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h new file mode 100644 index 0000000000..7805854a33 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via its core dump and proc files. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ + +#include "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::GetThreadNameByIndex(). + // Reads the name of the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadNameByIndex(size_t index, char* info, size_t size); + + // 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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc new file mode 100644 index 0000000000..2e973167fd --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_core_dumper_unittest.cc @@ -0,0 +1,192 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_core_dumper_unittest.cc: +// Unit tests for google_breakpad::LinuxCoreDumoer. + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "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()); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc new file mode 100644 index 0000000000..3b400ce8ca --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc @@ -0,0 +1,999 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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. + +#include "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 "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" + +#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)"; +static const char kReservedFlags[] = " ---p"; +static const char kMozillaIpcPrefix[] = "org.mozilla.ipc."; + +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, mapping.offset); + 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, mapping.offset); + 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::IsIPCSharedMemorySegment(const char* name) { + if (my_strstr(name, kMozillaIpcPrefix) && + my_strstr(name, kDeletedSuffix)) { + return true; + } + + return false; +} + +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; + } + // Skip shared memory segments used for IPC + if (name && IsIPCSharedMemorySegment(name)) { + line_reader->PopLine(line_len); + continue; + } + // Merge adjacent mappings into one module, assuming they're a single + // library mapped by the dynamic linker. + 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)) { + module->system_mapping_info.end_addr = end_addr; + module->size = end_addr - module->start_addr; + module->exec |= exec; + line_reader->PopLine(line_len); + continue; + } + } + // Also merge mappings that result from address ranges that the + // linker reserved but which a loaded library did not use. These + // appear as an anonymous private mapping with no access flags set + // and which directly follow an executable mapping. + if (!name && !mappings_.empty()) { + MappingInfo* module = mappings_.back(); + uintptr_t module_end_addr = module->start_addr + module->size; + if ((start_addr == module_end_addr) && + module->exec && + module->name[0] == '/' && + ((offset == 0) || (offset == module_end_addr)) && + my_strncmp(i2, kReservedFlags, + sizeof(kReservedFlags) - 1) == 0) { + module->size = end_addr - module->start_addr; + 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 diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h new file mode 100644 index 0000000000..7155524ffc --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h @@ -0,0 +1,333 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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 "linux/dump_writer_common/mapping_info.h" +#include "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) +typedef Elf32_auxv_t elf_aux_entry; +#elif defined(__x86_64) || defined(__aarch64__) || \ + (defined(__mips__) && _MIPS_SIM != _ABIO32) +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; + + // Read the name ofthe |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadNameByIndex(size_t index, char* name, size_t size) = 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 pid() const { return pid_; } + 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_; + +private: + bool IsIPCSharedMemorySegment(const char* name); + +#if defined(__ANDROID__) + // 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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc new file mode 100644 index 0000000000..3ad48e5015 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper_unittest_helper.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Helper program for the linux_dumper class, which creates a bunch of +// threads. The first word of each thread's stack is set to the thread +// id. + +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "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" +#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; +} diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc new file mode 100644 index 0000000000..6ed70eeb61 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.cc @@ -0,0 +1,403 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper. +// See linux_ptrace_dumper.h for detals. +// This class was originally splitted from google_breakpad::LinuxDumper. + +// This code deals with the mechanics of getting information about a crashed +// process. Since this code may run in a compromised address space, the same +// rules apply as detailed at the top of minidump_writer.h: no libc calls and +// use the alternative allocator. + +#include "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 "linux/minidump_writer/directory_reader.h" +#include "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; + } + +#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__)) + 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]); +#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::GetThreadNameByIndex(size_t index, char* name, + size_t size) { + if (index >= threads_.size()) + return false; + + pid_t tid = threads_[index]; + + assert(name != NULL); + char path[NAME_MAX]; + + // Read the thread name (aka comm entry in /proc) + if (!BuildProcPath(path, tid, "comm")) + return false; + + const int fd = sys_open(path, O_RDONLY, 0); + if (fd < 0) + return false; + + const int len = sys_read(fd, name, size); + if (len > 0) + name[len - 1] = '\0'; // Get rid of the newline + + sys_close(fd); + + return len > 0; +} + +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_.empty(); +} + +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 diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h new file mode 100644 index 0000000000..576098c349 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper.h @@ -0,0 +1,106 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via ptrace. +// This class was originally splitted from google_breakpad::LinuxDumper. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ + +#include "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::GetThreadNameByIndex(). + // Reads the name of the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadNameByIndex(size_t index, char* name, size_t size); + + // 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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc new file mode 100644 index 0000000000..79d26a1e31 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc @@ -0,0 +1,580 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// 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. + +#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 "linux/minidump_writer/linux_ptrace_dumper.h" +#include "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; + +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]); +#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]; +#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)); +} diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc new file mode 100644 index 0000000000..03066e9110 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc @@ -0,0 +1,1562 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This code writes out minidump files: +// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx +// +// Minidumps are a Microsoft format which Breakpad uses for recording crash +// dumps. This code has to run in a compromised environment (the address space +// may have received SIGSEGV), thus the following rules apply: +// * You may not enter the dynamic linker. This means that we cannot call +// any symbols in a shared library (inc libc). Because of this we replace +// libc functions in linux_libc_support.h. +// * You may not call syscalls via the libc wrappers. This rule is a subset +// of the first rule but it bears repeating. We have direct wrappers +// around the system calls in linux_syscall_support.h. +// * You may not malloc. There's an alternative allocator in memory.h and +// a canonical instance in the LinuxDumper object. We use the placement +// new form to allocate objects and we don't delete them. + +#include "linux/handler/minidump_descriptor.h" +#include "linux/minidump_writer/minidump_writer.h" +#include "minidump_file_writer-inl.h" + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <link.h> +#include <stdio.h> +#if defined(__ANDROID__) +#include <sys/system_properties.h> +#endif +#include <sys/types.h> +#include <sys/ucontext.h> +#include <sys/user.h> +#include <sys/utsname.h> +#include <time.h> +#include <unistd.h> + +#include <algorithm> + +#include "linux/dump_writer_common/thread_info.h" +#include "linux/dump_writer_common/ucontext_reader.h" +#include "linux/handler/exception_handler.h" +#include "linux/minidump_writer/cpu_set.h" +#include "linux/minidump_writer/line_reader.h" +#include "linux/minidump_writer/linux_dumper.h" +#include "linux/minidump_writer/linux_ptrace_dumper.h" +#include "linux/minidump_writer/proc_cpuinfo_reader.h" +#include "minidump_file_writer.h" +#include "common/linux/file_id.h" +#include "common/linux/linux_libc_support.h" +#include "common/minidump_type_helper.h" +#include "google_breakpad/common/minidump_format.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace { + +using google_breakpad::AppMemoryList; +using google_breakpad::auto_wasteful_vector; +using google_breakpad::ExceptionHandler; +using google_breakpad::CpuSet; +using google_breakpad::kDefaultBuildIdSize; +using google_breakpad::LineReader; +using google_breakpad::LinuxDumper; +using google_breakpad::LinuxPtraceDumper; +using google_breakpad::MDTypeHelper; +using google_breakpad::MappingEntry; +using google_breakpad::MappingInfo; +using google_breakpad::MappingList; +using google_breakpad::MinidumpFileWriter; +using google_breakpad::PageAllocator; +using google_breakpad::ProcCpuInfoReader; +using google_breakpad::RawContextCPU; +using google_breakpad::ThreadInfo; +using google_breakpad::TypedMDRVA; +using google_breakpad::UContextReader; +using google_breakpad::UntypedMDRVA; +using google_breakpad::wasteful_vector; + +typedef MDTypeHelper<sizeof(void*)>::MDRawDebug MDRawDebug; +typedef MDTypeHelper<sizeof(void*)>::MDRawLinkMap MDRawLinkMap; + +class MinidumpWriter { + public: + // The following kLimit* constants are for when minidump_size_limit_ is set + // and the minidump size might exceed it. + // + // Estimate for how big each thread's stack will be (in bytes). + static const unsigned kLimitAverageThreadStackLength = 8 * 1024; + // Number of threads whose stack size we don't want to limit. These base + // threads will simply be the first N threads returned by the dumper (although + // the crashing thread will never be limited). Threads beyond this count are + // the extra threads. + static const unsigned kLimitBaseThreadCount = 20; + // Maximum stack size to dump for any extra thread (in bytes). + static const unsigned kLimitMaxExtraThreadStackLen = 2 * 1024; + // Make sure this number of additional bytes can fit in the minidump + // (exclude the stack data). + static const unsigned kLimitMinidumpFudgeFactor = 64 * 1024; + + MinidumpWriter(const char* minidump_path, + int minidump_fd, + const ExceptionHandler::CrashContext* context, + const MappingList& mappings, + const AppMemoryList& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks, + LinuxDumper* dumper) + : fd_(minidump_fd), + path_(minidump_path), + ucontext_(context ? &context->context : NULL), +#if !defined(__ARM_EABI__) && !defined(__mips__) + float_state_(context ? &context->float_state : NULL), +#endif + dumper_(dumper), + minidump_size_limit_(-1), + memory_blocks_(dumper_->allocator()), + mapping_list_(mappings), + app_memory_list_(appmem), + skip_stacks_if_mapping_unreferenced_( + skip_stacks_if_mapping_unreferenced), + principal_mapping_address_(principal_mapping_address), + principal_mapping_(nullptr), + sanitize_stacks_(sanitize_stacks) { + // Assert there should be either a valid fd or a valid path, not both. + assert(fd_ != -1 || minidump_path); + assert(fd_ == -1 || !minidump_path); + } + + bool Init() { + if (!dumper_->Init()) + return false; + + if (!dumper_->ThreadsSuspend() || !dumper_->LateInit()) + return false; + + if (skip_stacks_if_mapping_unreferenced_) { + principal_mapping_ = + dumper_->FindMappingNoBias(principal_mapping_address_); + if (!CrashingThreadReferencesPrincipalMapping()) + return false; + } + + if (fd_ != -1) + minidump_writer_.SetFile(fd_); + else if (!minidump_writer_.Open(path_)) + return false; + + return true; + } + + ~MinidumpWriter() { + // Don't close the file descriptor when it's been provided explicitly. + // Callers might still need to use it. + if (fd_ == -1) + minidump_writer_.Close(); + dumper_->ThreadsResume(); + } + + bool CrashingThreadReferencesPrincipalMapping() { + if (!ucontext_ || !principal_mapping_) + return false; + + const uintptr_t low_addr = + principal_mapping_->system_mapping_info.start_addr; + const uintptr_t high_addr = + principal_mapping_->system_mapping_info.end_addr; + + const uintptr_t stack_pointer = UContextReader::GetStackPointer(ucontext_); + const uintptr_t pc = UContextReader::GetInstructionPointer(ucontext_); + + if (pc >= low_addr && pc < high_addr) + return true; + + uint8_t* stack_copy; + const void* stack; + size_t stack_len; + + if (!dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) + return false; + + stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len)); + dumper_->CopyFromProcess(stack_copy, GetCrashThread(), stack, stack_len); + + uintptr_t stack_pointer_offset = + stack_pointer - reinterpret_cast<uintptr_t>(stack); + + return dumper_->StackHasPointerToMapping( + stack_copy, stack_len, stack_pointer_offset, *principal_mapping_); + } + + bool Dump() { + // A minidump file contains a number of tagged streams. This is the number + // of stream which we write. + unsigned kNumWriters = 14; + + TypedMDRVA<MDRawDirectory> dir(&minidump_writer_); + { + // Ensure the header gets flushed, as that happens in the destructor. + // If we crash somewhere below, we should have a mostly-intact dump + TypedMDRVA<MDRawHeader> header(&minidump_writer_); + if (!header.Allocate()) + return false; + + if (!dir.AllocateArray(kNumWriters)) + return false; + + my_memset(header.get(), 0, sizeof(MDRawHeader)); + + header.get()->signature = MD_HEADER_SIGNATURE; + header.get()->version = MD_HEADER_VERSION; + header.get()->time_date_stamp = time(NULL); + header.get()->stream_count = kNumWriters; + header.get()->stream_directory_rva = dir.position(); + } + + unsigned dir_index = 0; + MDRawDirectory dirent; + + if (!WriteThreadListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteThreadNamesStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteMappings(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteAppMemory()) + return false; + + if (!WriteMemoryListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteExceptionStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteSystemInfoStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CPU_INFO; + if (!WriteFile(&dirent.location, "/proc/cpuinfo")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_PROC_STATUS; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "status")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_LSB_RELEASE; + if (!WriteFile(&dirent.location, "/etc/lsb-release") && + !WriteFile(&dirent.location, "/etc/os-release")) { + NullifyDirectoryEntry(&dirent); + } + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CMD_LINE; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_ENVIRON; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_AUXV; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_MAPS; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_DSO_DEBUG; + if (!WriteDSODebugStream(&dirent)) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + // If you add more directory entries, don't forget to update kNumWriters, + // above. + + dumper_->ThreadsResume(); + return true; + } + + bool FillThreadStack(MDRawThread* thread, uintptr_t stack_pointer, + uintptr_t pc, int max_stack_len, uint8_t** stack_copy) { + *stack_copy = NULL; + const void* stack; + size_t stack_len; + + thread->stack.start_of_memory_range = stack_pointer; + thread->stack.memory.data_size = 0; + thread->stack.memory.rva = minidump_writer_.position(); + + if (dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) { + if (max_stack_len >= 0 && + stack_len > static_cast<unsigned int>(max_stack_len)) { + stack_len = max_stack_len; + // Skip empty chunks of length max_stack_len. + uintptr_t int_stack = reinterpret_cast<uintptr_t>(stack); + if (max_stack_len > 0) { + while (int_stack + max_stack_len < stack_pointer) { + int_stack += max_stack_len; + } + } + stack = reinterpret_cast<const void*>(int_stack); + } + *stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len)); + dumper_->CopyFromProcess(*stack_copy, thread->thread_id, stack, + stack_len); + + uintptr_t stack_pointer_offset = + stack_pointer - reinterpret_cast<uintptr_t>(stack); + if (skip_stacks_if_mapping_unreferenced_) { + if (!principal_mapping_) { + return true; + } + uintptr_t low_addr = principal_mapping_->system_mapping_info.start_addr; + uintptr_t high_addr = principal_mapping_->system_mapping_info.end_addr; + if ((pc < low_addr || pc > high_addr) && + !dumper_->StackHasPointerToMapping(*stack_copy, stack_len, + stack_pointer_offset, + *principal_mapping_)) { + return true; + } + } + + if (sanitize_stacks_) { + dumper_->SanitizeStackCopy(*stack_copy, stack_len, stack_pointer, + stack_pointer_offset); + } + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(stack_len)) + return false; + memory.Copy(*stack_copy, stack_len); + thread->stack.start_of_memory_range = reinterpret_cast<uintptr_t>(stack); + thread->stack.memory = memory.location(); + memory_blocks_.push_back(thread->stack); + } + return true; + } + + // Write information about the threads. + bool WriteThreadListStream(MDRawDirectory* dirent) { + const unsigned num_threads = dumper_->threads().size(); + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread))) + return false; + + dirent->stream_type = MD_THREAD_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = num_threads; + + // If there's a minidump size limit, check if it might be exceeded. Since + // most of the space is filled with stack data, just check against that. + // If this expects to exceed the limit, set extra_thread_stack_len such + // that any thread beyond the first kLimitBaseThreadCount threads will + // have only kLimitMaxExtraThreadStackLen bytes dumped. + int extra_thread_stack_len = -1; // default to no maximum + if (minidump_size_limit_ >= 0) { + const unsigned estimated_total_stack_size = num_threads * + kLimitAverageThreadStackLength; + const off_t estimated_minidump_size = minidump_writer_.position() + + estimated_total_stack_size + kLimitMinidumpFudgeFactor; + if (estimated_minidump_size > minidump_size_limit_) + extra_thread_stack_len = kLimitMaxExtraThreadStackLen; + } + + for (unsigned i = 0; i < num_threads; ++i) { + MDRawThread thread; + my_memset(&thread, 0, sizeof(thread)); + thread.thread_id = dumper_->threads()[i]; + + // We have a different source of information for the crashing thread. If + // we used the actual state of the thread we would find it running in the + // signal handler with the alternative stack, which would be deeply + // unhelpful. + if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() && + ucontext_ && + !dumper_->IsPostMortem()) { + uint8_t* stack_copy; + const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_); + if (!FillThreadStack(&thread, stack_ptr, + UContextReader::GetInstructionPointer(ucontext_), + -1, &stack_copy)) + return false; + + // Copy 256 bytes around crashing instruction pointer to minidump. + const size_t kIPMemorySize = 256; + uint64_t ip = UContextReader::GetInstructionPointer(ucontext_); + // Bound it to the upper and lower bounds of the memory map + // it's contained within. If it's not in mapped memory, + // don't bother trying to write it. + bool ip_is_mapped = false; + MDMemoryDescriptor ip_memory_d; + for (unsigned j = 0; j < dumper_->mappings().size(); ++j) { + const MappingInfo& mapping = *dumper_->mappings()[j]; + if (ip >= mapping.start_addr && + ip < mapping.start_addr + mapping.size) { + ip_is_mapped = true; + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::max(mapping.start_addr, + uintptr_t(ip - (kIPMemorySize / 2))); + uintptr_t end_of_range = + std::min(uintptr_t(ip + (kIPMemorySize / 2)), + uintptr_t(mapping.start_addr + mapping.size)); + ip_memory_d.memory.data_size = + end_of_range - ip_memory_d.start_of_memory_range; + break; + } + } + + if (ip_is_mapped) { + UntypedMDRVA ip_memory(&minidump_writer_); + if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) + return false; + uint8_t* memory_copy = + reinterpret_cast<uint8_t*>(Alloc(ip_memory_d.memory.data_size)); + dumper_->CopyFromProcess( + memory_copy, + thread.thread_id, + reinterpret_cast<void*>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size); + ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size); + ip_memory_d.memory = ip_memory.location(); + memory_blocks_.push_back(ip_memory_d); + } + + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); +#if !defined(__ARM_EABI__) && !defined(__mips__) + UContextReader::FillCPUContext(cpu.get(), ucontext_, float_state_); +#else + UContextReader::FillCPUContext(cpu.get(), ucontext_); +#endif + thread.thread_context = cpu.location(); + crashing_thread_context_ = cpu.location(); + } else { + ThreadInfo info; + if (!dumper_->GetThreadInfoByIndex(i, &info)) + return false; + + uint8_t* stack_copy; + int max_stack_len = -1; // default to no maximum for this thread + if (minidump_size_limit_ >= 0 && i >= kLimitBaseThreadCount) + max_stack_len = extra_thread_stack_len; + if (!FillThreadStack(&thread, info.stack_pointer, + info.GetInstructionPointer(), max_stack_len, + &stack_copy)) + return false; + + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); + info.FillCPUContext(cpu.get()); + thread.thread_context = cpu.location(); + if (dumper_->threads()[i] == GetCrashThread()) { + crashing_thread_context_ = cpu.location(); + if (!dumper_->IsPostMortem()) { + // This is the crashing thread of a live process, but + // no context was provided, so set the crash address + // while the instruction pointer is already here. + dumper_->set_crash_address(info.GetInstructionPointer()); + } + } + } + + list.CopyIndexAfterObject(i, &thread, sizeof(thread)); + } + + return true; + } + + bool WriteThreadName(pid_t tid, char* name, MDRawThreadName *thread_name) { + MDLocationDescriptor string_location; + + if (!minidump_writer_.WriteString(name, 0, &string_location)) + return false; + + thread_name->thread_id = tid; + thread_name->rva_of_thread_name = string_location.rva; + return true; + } + + // Write the threads' names. + bool WriteThreadNamesStream(MDRawDirectory* thread_names_stream) { + TypedMDRVA<MDRawThreadNamesList> list(&minidump_writer_); + const unsigned num_threads = dumper_->threads().size(); + + if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThreadName))) { + return false; + } + + thread_names_stream->stream_type = MD_THREAD_NAMES_STREAM; + thread_names_stream->location = list.location(); + list.get()->number_of_thread_names = num_threads; + + MDRawThreadName thread_name; + int thread_idx = 0; + + for (unsigned int i = 0; i < num_threads; ++i) { + const pid_t tid = dumper_->threads()[i]; + // This is a constant from the Linux kernel, documented in man 5 proc. + // The comm entries in /proc are no longer than this. + static const size_t TASK_COMM_LEN = 16; + char name[TASK_COMM_LEN]; + memset(&thread_name, 0, sizeof(MDRawThreadName)); + + if (dumper_->GetThreadNameByIndex(i, name, sizeof(name))) { + if (WriteThreadName(tid, name, &thread_name)) { + list.CopyIndexAfterObject(thread_idx++, &thread_name, + sizeof(MDRawThreadName)); + } + } + } + + return true; + } + + // Write application-provided memory regions. + bool WriteAppMemory() { + for (AppMemoryList::const_iterator iter = app_memory_list_.begin(); + iter != app_memory_list_.end(); + ++iter) { + uint8_t* data_copy = + reinterpret_cast<uint8_t*>(dumper_->allocator()->Alloc(iter->length)); + dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr, + iter->length); + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(iter->length)) { + return false; + } + memory.Copy(data_copy, iter->length); + MDMemoryDescriptor desc; + desc.start_of_memory_range = reinterpret_cast<uintptr_t>(iter->ptr); + desc.memory = memory.location(); + memory_blocks_.push_back(desc); + } + + return true; + } + + static bool ShouldIncludeMapping(const MappingInfo& mapping) { + if (mapping.name[0] == 0 || // only want modules with filenames. + // Only want to include one mapping per shared lib. + // Avoid filtering executable mappings. + (mapping.offset != 0 && !mapping.exec) || + mapping.size < 4096) { // too small to get a signature for. + return false; + } + + return true; + } + + // If there is caller-provided information about this mapping + // in the mapping_list_ list, return true. Otherwise, return false. + bool HaveMappingInfo(const MappingInfo& mapping) { + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + // Ignore any mappings that are wholly contained within + // mappings in the mapping_info_ list. + if (mapping.start_addr >= iter->first.start_addr && + (mapping.start_addr + mapping.size) <= + (iter->first.start_addr + iter->first.size)) { + return true; + } + } + return false; + } + + // Write information about the mappings in effect. Because we are using the + // minidump format, the information about the mappings is pretty limited. + // Because of this, we also include the full, unparsed, /proc/$x/maps file in + // another stream in the file. + bool WriteMappings(MDRawDirectory* dirent) { + const unsigned num_mappings = dumper_->mappings().size(); + unsigned num_output_mappings = mapping_list_.size(); + + for (unsigned i = 0; i < dumper_->mappings().size(); ++i) { + const MappingInfo& mapping = *dumper_->mappings()[i]; + if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping)) + num_output_mappings++; + } + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (num_output_mappings) { + if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE)) + return false; + } else { + // Still create the module list stream, although it will have zero + // modules. + if (!list.Allocate()) + return false; + } + + dirent->stream_type = MD_MODULE_LIST_STREAM; + dirent->location = list.location(); + *list.get() = num_output_mappings; + + // First write all the mappings from the dumper + unsigned int j = 0; + for (unsigned i = 0; i < num_mappings; ++i) { + const MappingInfo& mapping = *dumper_->mappings()[i]; + if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping)) + continue; + + MDRawModule mod; + if (!FillRawModule(mapping, true, i, &mod, NULL)) + return false; + list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); + } + // Next write all the mappings provided by the caller + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + MDRawModule mod; + if (!FillRawModule(iter->first, false, 0, &mod, &iter->second)) { + return false; + } + list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); + } + + return true; + } + + // Fill the MDRawModule |mod| with information about the provided + // |mapping|. If |identifier| is non-NULL, use it instead of calculating + // a file ID from the mapping. + bool FillRawModule(const MappingInfo& mapping, + bool member, + unsigned int mapping_id, + MDRawModule* mod, + const std::vector<uint8_t>* identifier) { + my_memset(mod, 0, MD_MODULE_SIZE); + + mod->base_of_image = mapping.start_addr; + mod->size_of_image = mapping.size; + + auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes( + dumper_->allocator()); + + if (identifier) { + // GUID was provided by caller. + identifier_bytes.insert(identifier_bytes.end(), + identifier->begin(), + identifier->end()); + } else { + // Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|. + if (!dumper_->ElfFileIdentifierForMapping(mapping, + member, + mapping_id, + identifier_bytes)) { + identifier_bytes.clear(); + } + } + + if (!identifier_bytes.empty()) { + UntypedMDRVA cv(&minidump_writer_); + if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size())) + return false; + + const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE; + cv.Copy(&cv_signature, sizeof(cv_signature)); + cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0], + identifier_bytes.size()); + + mod->cv_record = cv.location(); + } + + char file_name[NAME_MAX]; + char file_path[NAME_MAX]; + dumper_->GetMappingEffectiveNameAndPath( + mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); + + MDLocationDescriptor ld; + if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld)) + return false; + mod->module_name_rva = ld.rva; + return true; + } + + bool WriteMemoryListStream(MDRawDirectory* dirent) { + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!memory_blocks_.empty()) { + if (!list.AllocateObjectAndArray(memory_blocks_.size(), + sizeof(MDMemoryDescriptor))) + return false; + } else { + // Still create the memory list stream, although it will have zero + // memory blocks. + if (!list.Allocate()) + return false; + } + + dirent->stream_type = MD_MEMORY_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = memory_blocks_.size(); + + for (size_t i = 0; i < memory_blocks_.size(); ++i) { + list.CopyIndexAfterObject(i, &memory_blocks_[i], + sizeof(MDMemoryDescriptor)); + } + return true; + } + + bool WriteExceptionStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_); + if (!exc.Allocate()) + return false; + + MDRawExceptionStream* stream = exc.get(); + my_memset(stream, 0, sizeof(MDRawExceptionStream)); + + dirent->stream_type = MD_EXCEPTION_STREAM; + dirent->location = exc.location(); + + stream->thread_id = GetCrashThread(); + stream->exception_record.exception_code = dumper_->crash_signal(); + stream->exception_record.exception_flags = dumper_->crash_signal_code(); + stream->exception_record.exception_address = dumper_->crash_address(); + const std::vector<uint64_t> crash_exception_info = + dumper_->crash_exception_info(); + stream->exception_record.number_parameters = crash_exception_info.size(); + memcpy(stream->exception_record.exception_information, + crash_exception_info.data(), + sizeof(uint64_t) * crash_exception_info.size()); + stream->thread_context = crashing_thread_context_; + + return true; + } + + bool WriteSystemInfoStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_); + if (!si.Allocate()) + return false; + my_memset(si.get(), 0, sizeof(MDRawSystemInfo)); + + dirent->stream_type = MD_SYSTEM_INFO_STREAM; + dirent->location = si.location(); + + WriteCPUInformation(si.get()); + WriteOSInformation(si.get()); + + return true; + } + + bool WriteDSODebugStream(MDRawDirectory* dirent) { + ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr) *>(dumper_->auxv()[AT_PHDR]); + char* base; + int phnum = dumper_->auxv()[AT_PHNUM]; + if (!phnum || !phdr) + return false; + + // Assume the program base is at the beginning of the same page as the PHDR + base = reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(phdr) & ~0xfff); + + // Search for the program PT_DYNAMIC segment + ElfW(Addr) dyn_addr = 0; + for (; phnum >= 0; phnum--, phdr++) { + ElfW(Phdr) ph; + if (!dumper_->CopyFromProcess(&ph, dumper_->pid(), phdr, sizeof(ph))) + return false; + + // Adjust base address with the virtual address of the PT_LOAD segment + // corresponding to offset 0 + if (ph.p_type == PT_LOAD && ph.p_offset == 0) { + base -= ph.p_vaddr; + } + if (ph.p_type == PT_DYNAMIC) { + dyn_addr = ph.p_vaddr; + } + } + if (!dyn_addr) + return false; + + ElfW(Dyn) *dynamic = reinterpret_cast<ElfW(Dyn) *>(dyn_addr + base); + + // The dynamic linker makes information available that helps gdb find all + // DSOs loaded into the program. If this information is indeed available, + // dump it to a MD_LINUX_DSO_DEBUG stream. + struct r_debug* r_debug = NULL; + uint32_t dynamic_length = 0; + + for (int i = 0; ; ++i) { + ElfW(Dyn) dyn; + dynamic_length += sizeof(dyn); + if (!dumper_->CopyFromProcess(&dyn, dumper_->pid(), dynamic + i, sizeof(dyn))) { + return false; + } + +#ifdef __mips__ + const int32_t debug_tag = DT_MIPS_RLD_MAP; +#else + const int32_t debug_tag = DT_DEBUG; +#endif + if (dyn.d_tag == debug_tag) { + r_debug = reinterpret_cast<struct r_debug*>(dyn.d_un.d_ptr); + continue; + } else if (dyn.d_tag == DT_NULL) { + break; + } + } + + // The "r_map" field of that r_debug struct contains a linked list of all + // loaded DSOs. + // Our list of DSOs potentially is different from the ones in the crashing + // process. So, we have to be careful to never dereference pointers + // directly. Instead, we use CopyFromProcess() everywhere. + // See <link.h> for a more detailed discussion of the how the dynamic + // loader communicates with debuggers. + + // Count the number of loaded DSOs + int dso_count = 0; + struct r_debug debug_entry; + if (!dumper_->CopyFromProcess(&debug_entry, dumper_->pid(), r_debug, + sizeof(debug_entry))) { + return false; + } + for (struct link_map* ptr = debug_entry.r_map; ptr; ) { + struct link_map map; + if (!dumper_->CopyFromProcess(&map, dumper_->pid(), ptr, sizeof(map))) + return false; + + ptr = map.l_next; + dso_count++; + } + + MDRVA linkmap_rva = MinidumpFileWriter::kInvalidMDRVA; + if (dso_count > 0) { + // If we have at least one DSO, create an array of MDRawLinkMap + // entries in the minidump file. + TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_); + if (!linkmap.AllocateArray(dso_count)) + return false; + linkmap_rva = linkmap.location().rva; + int idx = 0; + + // Iterate over DSOs and write their information to mini dump + for (struct link_map* ptr = debug_entry.r_map; ptr; ) { + struct link_map map; + if (!dumper_->CopyFromProcess(&map, dumper_->pid(), ptr, sizeof(map))) + return false; + + ptr = map.l_next; + char filename[257] = { 0 }; + if (map.l_name) { + dumper_->CopyFromProcess(filename, dumper_->pid(), map.l_name, + sizeof(filename) - 1); + } + MDLocationDescriptor location; + if (!minidump_writer_.WriteString(filename, 0, &location)) + return false; + MDRawLinkMap entry; + entry.name = location.rva; + entry.addr = map.l_addr; + entry.ld = reinterpret_cast<uintptr_t>(map.l_ld); + linkmap.CopyIndex(idx++, &entry); + } + } + + // Write MD_LINUX_DSO_DEBUG record + TypedMDRVA<MDRawDebug> debug(&minidump_writer_); + if (!debug.AllocateObjectAndArray(1, dynamic_length)) + return false; + my_memset(debug.get(), 0, sizeof(MDRawDebug)); + dirent->stream_type = MD_LINUX_DSO_DEBUG; + dirent->location = debug.location(); + + debug.get()->version = debug_entry.r_version; + debug.get()->map = linkmap_rva; + debug.get()->dso_count = dso_count; + debug.get()->brk = debug_entry.r_brk; + debug.get()->ldbase = debug_entry.r_ldbase; + debug.get()->dynamic = reinterpret_cast<uintptr_t>(dynamic); + + wasteful_vector<char> dso_debug_data(dumper_->allocator(), dynamic_length); + // The passed-in size to the constructor (above) is only a hint. + // Must call .resize() to do actual initialization of the elements. + dso_debug_data.resize(dynamic_length); + dumper_->CopyFromProcess(&dso_debug_data[0], dumper_->pid(), dynamic, + dynamic_length); + debug.CopyIndexAfterObject(0, &dso_debug_data[0], dynamic_length); + + return true; + } + + void set_minidump_size_limit(off_t limit) { minidump_size_limit_ = limit; } + + private: + void* Alloc(unsigned bytes) { + return dumper_->allocator()->Alloc(bytes); + } + + pid_t GetCrashThread() const { + return dumper_->crash_thread(); + } + + void NullifyDirectoryEntry(MDRawDirectory* dirent) { + dirent->stream_type = 0; + dirent->location.data_size = 0; + dirent->location.rva = 0; + } + +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + bool WriteCPUInformation(MDRawSystemInfo* sys_info) { + char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0}; + static const char vendor_id_name[] = "vendor_id"; + + struct CpuInfoEntry { + const char* info_name; + int value; + bool found; + } cpu_info_table[] = { + { "processor", -1, false }, +#if defined(__i386__) || defined(__x86_64__) + { "model", 0, false }, + { "stepping", 0, false }, + { "cpu family", 0, false }, +#endif + }; + + // processor_architecture should always be set, do this first + sys_info->processor_architecture = +#if defined(__mips__) +# if _MIPS_SIM == _ABIO32 + MD_CPU_ARCHITECTURE_MIPS; +# elif _MIPS_SIM == _ABI64 + MD_CPU_ARCHITECTURE_MIPS64; +# else +# error "This mips ABI is currently not supported (n32)" +#endif +#elif defined(__i386__) + MD_CPU_ARCHITECTURE_X86; +#else + MD_CPU_ARCHITECTURE_AMD64; +#endif + + const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); + if (fd < 0) + return false; + + { + PageAllocator allocator; + ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd); + const char* field; + while (reader->GetNextField(&field)) { + bool is_first_entry = true; + for (CpuInfoEntry& entry : cpu_info_table) { + if (!is_first_entry && entry.found) { + // except for the 'processor' field, ignore repeated values. + continue; + } + is_first_entry = false; + if (!my_strcmp(field, entry.info_name)) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + if (value_len == 0) + continue; + + uintptr_t val; + if (my_read_decimal_ptr(&val, value) == value) + continue; + + entry.value = static_cast<int>(val); + entry.found = true; + } + } + + // special case for vendor_id + if (!my_strcmp(field, vendor_id_name)) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + if (value_len > 0) + my_strlcpy(vendor_id, value, sizeof(vendor_id)); + } + } + sys_close(fd); + } + + // make sure we got everything we wanted + for (const CpuInfoEntry& entry : cpu_info_table) { + if (!entry.found) { + return false; + } + } + // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo, + // assuming this is the highest id, change it to the number of CPUs + // by adding one. + cpu_info_table[0].value++; + + sys_info->number_of_processors = cpu_info_table[0].value; +#if defined(__i386__) || defined(__x86_64__) + sys_info->processor_level = cpu_info_table[3].value; + sys_info->processor_revision = cpu_info_table[1].value << 8 | + cpu_info_table[2].value; +#endif + + if (vendor_id[0] != '\0') { + my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id, + sizeof(sys_info->cpu.x86_cpu_info.vendor_id)); + } + return true; + } +#elif defined(__arm__) || defined(__aarch64__) + bool WriteCPUInformation(MDRawSystemInfo* sys_info) { + // The CPUID value is broken up in several entries in /proc/cpuinfo. + // This table is used to rebuild it from the entries. + const struct CpuIdEntry { + const char* field; + char format; + char bit_lshift; + char bit_length; + } cpu_id_entries[] = { + { "CPU implementer", 'x', 24, 8 }, + { "CPU variant", 'x', 20, 4 }, + { "CPU part", 'x', 4, 12 }, + { "CPU revision", 'd', 0, 4 }, + }; + + // The ELF hwcaps are listed in the "Features" entry as textual tags. + // This table is used to rebuild them. + const struct CpuFeaturesEntry { + const char* tag; + uint32_t hwcaps; + } cpu_features_entries[] = { +#if defined(__arm__) + { "swp", MD_CPU_ARM_ELF_HWCAP_SWP }, + { "half", MD_CPU_ARM_ELF_HWCAP_HALF }, + { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB }, + { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT }, + { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT }, + { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA }, + { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP }, + { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP }, + { "java", MD_CPU_ARM_ELF_HWCAP_JAVA }, + { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT }, + { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH }, + { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE }, + { "neon", MD_CPU_ARM_ELF_HWCAP_NEON }, + { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 }, + { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 }, + { "tls", MD_CPU_ARM_ELF_HWCAP_TLS }, + { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 }, + { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA }, + { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT }, + { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT }, +#elif defined(__aarch64__) + // No hwcaps on aarch64. +#endif + }; + + // processor_architecture should always be set, do this first + sys_info->processor_architecture = +#if defined(__aarch64__) + MD_CPU_ARCHITECTURE_ARM64_OLD; +#else + MD_CPU_ARCHITECTURE_ARM; +#endif + + // /proc/cpuinfo is not readable under various sandboxed environments + // (e.g. Android services with the android:isolatedProcess attribute) + // prepare for this by setting default values now, which will be + // returned when this happens. + // + // Note: Bogus values are used to distinguish between failures (to + // read /sys and /proc files) and really badly configured kernels. + sys_info->number_of_processors = 0; + sys_info->processor_level = 1U; // There is no ARMv1 + sys_info->processor_revision = 42; + sys_info->cpu.arm_cpu_info.cpuid = 0; + sys_info->cpu.arm_cpu_info.elf_hwcaps = 0; + + // Counting the number of CPUs involves parsing two sysfs files, + // because the content of /proc/cpuinfo will only mirror the number + // of 'online' cores, and thus will vary with time. + // See http://www.kernel.org/doc/Documentation/cputopology.txt + { + CpuSet cpus_present; + CpuSet cpus_possible; + + int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0); + if (fd >= 0) { + cpus_present.ParseSysFile(fd); + sys_close(fd); + + fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0); + if (fd >= 0) { + cpus_possible.ParseSysFile(fd); + sys_close(fd); + + cpus_present.IntersectWith(cpus_possible); + int cpu_count = cpus_present.GetCount(); + if (cpu_count > 255) + cpu_count = 255; + sys_info->number_of_processors = static_cast<uint8_t>(cpu_count); + } + } + } + + // Parse /proc/cpuinfo to reconstruct the CPUID value, as well + // as the ELF hwcaps field. For the latter, it would be easier to + // read /proc/self/auxv but unfortunately, this file is not always + // readable from regular Android applications on later versions + // (>= 4.1) of the Android platform. + const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); + if (fd < 0) { + // Do not return false here to allow the minidump generation + // to happen properly. + return true; + } + + { + PageAllocator allocator; + ProcCpuInfoReader* const reader = + new(allocator) ProcCpuInfoReader(fd); + const char* field; + while (reader->GetNextField(&field)) { + for (const CpuIdEntry& entry : cpu_id_entries) { + if (my_strcmp(entry.field, field) != 0) + continue; + uintptr_t result = 0; + const char* value = reader->GetValue(); + const char* p = value; + if (value[0] == '0' && value[1] == 'x') { + p = my_read_hex_ptr(&result, value+2); + } else if (entry.format == 'x') { + p = my_read_hex_ptr(&result, value); + } else { + p = my_read_decimal_ptr(&result, value); + } + if (p == value) + continue; + + result &= (1U << entry.bit_length)-1; + result <<= entry.bit_lshift; + sys_info->cpu.arm_cpu_info.cpuid |= + static_cast<uint32_t>(result); + } +#if defined(__arm__) + // Get the architecture version from the "Processor" field. + // Note that it is also available in the "CPU architecture" field, + // however, some existing kernels are misconfigured and will report + // invalid values here (e.g. 6, while the CPU is ARMv7-A based). + // The "Processor" field doesn't have this issue. + if (!my_strcmp(field, "Processor")) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + // Expected format: <text> (v<level><endian>) + // Where <text> is some text like "ARMv7 Processor rev 2" + // and <level> is a decimal corresponding to the ARM + // architecture number. <endian> is either 'l' or 'b' + // and corresponds to the endianess, it is ignored here. + while (value_len > 0 && my_isspace(value[value_len-1])) + value_len--; + + size_t nn = value_len; + while (nn > 0 && value[nn-1] != '(') + nn--; + if (nn > 0 && value[nn] == 'v') { + uintptr_t arch_level = 5; + my_read_decimal_ptr(&arch_level, value + nn + 1); + sys_info->processor_level = static_cast<uint16_t>(arch_level); + } + } +#elif defined(__aarch64__) + // The aarch64 architecture does not provide the architecture level + // in the Processor field, so we instead check the "CPU architecture" + // field. + if (!my_strcmp(field, "CPU architecture")) { + uintptr_t arch_level = 0; + const char* value = reader->GetValue(); + const char* p = value; + p = my_read_decimal_ptr(&arch_level, value); + if (p == value) + continue; + sys_info->processor_level = static_cast<uint16_t>(arch_level); + } +#endif + // Rebuild the ELF hwcaps from the 'Features' field. + if (!my_strcmp(field, "Features")) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + + // Parse each space-separated tag. + while (value_len > 0) { + const char* tag = value; + size_t tag_len = value_len; + const char* p = my_strchr(tag, ' '); + if (p) { + tag_len = static_cast<size_t>(p - tag); + value += tag_len + 1; + value_len -= tag_len + 1; + } else { + tag_len = strlen(tag); + value_len = 0; + } + for (const CpuFeaturesEntry& entry : cpu_features_entries) { + if (tag_len == strlen(entry.tag) && + !memcmp(tag, entry.tag, tag_len)) { + sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry.hwcaps; + break; + } + } + } + } + } + sys_close(fd); + } + + return true; + } +#else +# error "Unsupported CPU" +#endif + + bool WriteFile(MDLocationDescriptor* result, const char* filename) { + const int fd = sys_open(filename, O_RDONLY, 0); + if (fd < 0) + return false; + + // We can't stat the files because several of the files that we want to + // read are kernel seqfiles, which always have a length of zero. So we have + // to read as much as we can into a buffer. + static const unsigned kBufSize = 1024 - 2*sizeof(void*); + struct Buffers { + Buffers* next; + size_t len; + uint8_t data[kBufSize]; + } *buffers = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers))); + buffers->next = NULL; + buffers->len = 0; + + size_t total = 0; + for (Buffers* bufptr = buffers;;) { + ssize_t r; + do { + r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len); + } while (r == -1 && errno == EINTR); + + if (r < 1) + break; + + total += r; + bufptr->len += r; + if (bufptr->len == kBufSize) { + bufptr->next = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers))); + bufptr = bufptr->next; + bufptr->next = NULL; + bufptr->len = 0; + } + } + sys_close(fd); + + if (!total) + return false; + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(total)) + return false; + for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) { + // Check for special case of a zero-length buffer. This should only + // occur if a file's size happens to be a multiple of the buffer's + // size, in which case the final sys_read() will have resulted in + // zero bytes being read after the final buffer was just allocated. + if (buffers->len == 0) { + // This can only occur with final buffer. + assert(buffers->next == NULL); + continue; + } + memory.Copy(pos, &buffers->data, buffers->len); + pos += buffers->len; + } + *result = memory.location(); + return true; + } + + bool WriteOSInformation(MDRawSystemInfo* sys_info) { +#if defined(__ANDROID__) + sys_info->platform_id = MD_OS_ANDROID; +#else + sys_info->platform_id = MD_OS_LINUX; +#endif + + struct utsname uts; + if (uname(&uts)) + return false; + + static const size_t buf_len = 512; + char buf[buf_len] = {0}; + size_t space_left = buf_len - 1; + const char* info_table[] = { + uts.sysname, + uts.release, + uts.version, + uts.machine, + NULL + }; + bool first_item = true; + for (const char** cur_info = info_table; *cur_info; cur_info++) { + static const char separator[] = " "; + size_t separator_len = sizeof(separator) - 1; + size_t info_len = my_strlen(*cur_info); + if (info_len == 0) + continue; + + if (space_left < info_len + (first_item ? 0 : separator_len)) + break; + + if (!first_item) { + my_strlcat(buf, separator, sizeof(buf)); + space_left -= separator_len; + } + + first_item = false; + my_strlcat(buf, *cur_info, sizeof(buf)); + space_left -= info_len; + } + + MDLocationDescriptor location; + if (!minidump_writer_.WriteString(buf, 0, &location)) + return false; + sys_info->csd_version_rva = location.rva; + + return true; + } + + bool WriteProcFile(MDLocationDescriptor* result, pid_t pid, + const char* filename) { + char buf[NAME_MAX]; + if (!dumper_->BuildProcPath(buf, pid, filename)) + return false; + return WriteFile(result, buf); + } + + // Only one of the 2 member variables below should be set to a valid value. + const int fd_; // File descriptor where the minidum should be written. + const char* path_; // Path to the file where the minidum should be written. + + const ucontext_t* const ucontext_; // also from the signal handler +#if !defined(__ARM_EABI__) && !defined(__mips__) + const google_breakpad::fpstate_t* const float_state_; // ditto +#endif + LinuxDumper* dumper_; + MinidumpFileWriter minidump_writer_; + off_t minidump_size_limit_; + MDLocationDescriptor crashing_thread_context_; + // Blocks of memory written to the dump. These are all currently + // written while writing the thread list stream, but saved here + // so a memory list stream can be written afterwards. + wasteful_vector<MDMemoryDescriptor> memory_blocks_; + // Additional information about some mappings provided by the caller. + const MappingList& mapping_list_; + // Additional memory regions to be included in the dump, + // provided by the caller. + const AppMemoryList& app_memory_list_; + // If set, skip recording any threads that do not reference the + // mapping containing principal_mapping_address_. + bool skip_stacks_if_mapping_unreferenced_; + uintptr_t principal_mapping_address_; + const MappingInfo* principal_mapping_; + // If true, apply stack sanitization to stored stack data. + bool sanitize_stacks_; +}; + + +bool WriteMinidumpImpl(const char* minidump_path, + int minidump_fd, + off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + LinuxPtraceDumper dumper(crashing_process); + const ExceptionHandler::CrashContext* context = NULL; + if (blob) { + if (blob_size != sizeof(ExceptionHandler::CrashContext)) + return false; + context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob); + dumper.SetCrashInfoFromSigInfo(context->siginfo); + dumper.set_crash_thread(context->tid); + } + MinidumpWriter writer(minidump_path, minidump_fd, context, mappings, + appmem, skip_stacks_if_mapping_unreferenced, + principal_mapping_address, sanitize_stacks, &dumper); + // Set desired limit for file size of minidump (-1 means no limit). + writer.set_minidump_size_limit(minidump_size_limit); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace + +namespace google_breakpad { + +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(minidump_path, -1, -1, + crashing_process, blob, blob_size, + MappingList(), AppMemoryList(), + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(NULL, minidump_fd, -1, + crashing_process, blob, blob_size, + MappingList(), AppMemoryList(), + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +bool WriteMinidump(const char* minidump_path, pid_t process, + pid_t process_blamed_thread) { + LinuxPtraceDumper dumper(process); + // MinidumpWriter will set crash address + dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED); + dumper.set_crash_thread(process_blamed_thread); + MappingList mapping_list; + AppMemoryList app_memory_list; + MinidumpWriter writer(minidump_path, -1, NULL, mapping_list, + app_memory_list, false, 0, false, &dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(minidump_path, -1, -1, crashing_process, + blob, blob_size, + mappings, appmem, + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(NULL, minidump_fd, -1, crashing_process, + blob, blob_size, + mappings, appmem, + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +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& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(minidump_path, -1, minidump_size_limit, + crashing_process, blob, blob_size, + mappings, appmem, + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +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& appmem, + bool skip_stacks_if_mapping_unreferenced, + uintptr_t principal_mapping_address, + bool sanitize_stacks) { + return WriteMinidumpImpl(NULL, minidump_fd, minidump_size_limit, + crashing_process, blob, blob_size, + mappings, appmem, + skip_stacks_if_mapping_unreferenced, + principal_mapping_address, + sanitize_stacks); +} + +bool WriteMinidump(const char* filename, + const MappingList& mappings, + const AppMemoryList& appmem, + LinuxDumper* dumper) { + MinidumpWriter writer(filename, -1, NULL, mappings, appmem, + false, 0, false, dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h new file mode 100644 index 0000000000..1d02ec8d7d --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.h @@ -0,0 +1,143 @@ +// Copyright (c) 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ + +#include <stdint.h> +#include <sys/types.h> +#include <sys/ucontext.h> +#include <unistd.h> + +#include <list> +#include <type_traits> +#include <utility> + +#include "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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc new file mode 100644 index 0000000000..4e49a4e577 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest.cc @@ -0,0 +1,934 @@ +// Copyright (c) 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <fcntl.h> +#include <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 "linux/handler/exception_handler.h" +#include "linux/minidump_writer/linux_dumper.h" +#include "linux/minidump_writer/minidump_writer.h" +#include "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; + +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; + mapping.second.assign(std::begin(kModuleGUID), std::end(kModuleGUID)); + 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; + mapping.second.assign(std::begin(kModuleGUID), std::end(kModuleGUID)); + 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.c_str(), binpath.c_str())) + << "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; +#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 diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc new file mode 100644 index 0000000000..3e27be73cc --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// minidump_writer_unittest_utils.cc: +// Shared routines used by unittests under linux/minidump_writer. + +#include <limits.h> +#include <stdlib.h> + +#include "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 diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h new file mode 100644 index 0000000000..bc7b7c83f4 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer_unittest_utils.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// minidump_writer_unittest_utils.h: +// Shared routines used by unittests under 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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h new file mode 100644 index 0000000000..94a637fc15 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader.h @@ -0,0 +1,130 @@ +// Copyright (c) 2013, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "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_ diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc new file mode 100644 index 0000000000..26fe9e3a38 --- /dev/null +++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc @@ -0,0 +1,199 @@ +// Copyright (c) 2013, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "linux/minidump_writer/proc_cpuinfo_reader.h" +#include "breakpad_googletest_includes.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +#if !defined(__ANDROID__) +#define TEMPDIR "/tmp" +#else +#define TEMPDIR "/data/local/tmp" +#endif + + +namespace { + +typedef testing::Test ProcCpuInfoReaderTest; + +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("proc_cpuinfo_reader", text) { + } +}; + +} + +TEST(ProcCpuInfoReaderTest, EmptyFile) { + ScopedTestFile file(""); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char *field; + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, OneLineTerminated) { + ScopedTestFile file("foo : bar\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("foo : bar"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("foo : bar\nzoo : tut\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("this line should have a column\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("\n\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file(" : bar\nzoo : tut\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("foo : bar\n\n\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("foo bar : zoo\n"); + ASSERT_TRUE(file.IsOk()); + 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) { + ScopedTestFile file("foo :\n"); + ASSERT_TRUE(file.IsOk()); + 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)); +} |