aboutsummaryrefslogtreecommitdiff
path: root/src/client/linux/minidump_writer
diff options
context:
space:
mode:
authornealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e>2009-08-17 23:12:53 +0000
committernealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e>2009-08-17 23:12:53 +0000
commitb0baafc4da1f3ffb84e267dd19d176db3de1c14e (patch)
tree3953f64180195ad40e27ab834f9b175e29042025 /src/client/linux/minidump_writer
parentFix build errors with gcc 4.4. Patch by Silvius Rus <rus@google.com>. (diff)
downloadbreakpad-b0baafc4da1f3ffb84e267dd19d176db3de1c14e.tar.xz
Merge of Breakpad Chrome Linux fork
A=agl, Lei Zhang R=nealsid, agl git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@384 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client/linux/minidump_writer')
-rw-r--r--src/client/linux/minidump_writer/directory_reader.h105
-rw-r--r--src/client/linux/minidump_writer/directory_reader_unittest.cc77
-rw-r--r--src/client/linux/minidump_writer/line_reader.h130
-rw-r--r--src/client/linux/minidump_writer/line_reader_unittest.cc184
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.cc419
-rw-r--r--src/client/linux/minidump_writer/linux_dumper.h142
-rw-r--r--src/client/linux/minidump_writer/linux_dumper_unittest.cc118
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.cc872
-rw-r--r--src/client/linux/minidump_writer/minidump_writer.h53
-rw-r--r--src/client/linux/minidump_writer/minidump_writer_unittest.cc79
10 files changed, 2179 insertions, 0 deletions
diff --git a/src/client/linux/minidump_writer/directory_reader.h b/src/client/linux/minidump_writer/directory_reader.h
new file mode 100644
index 00000000..4698b7e5
--- /dev/null
+++ b/src/client/linux/minidump_writer/directory_reader.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common/linux/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for enumerating a directory without using diropen/readdir or other
+// functions which may allocate memory.
+class DirectoryReader {
+ public:
+ DirectoryReader(int fd)
+ : fd_(fd),
+ buf_used_(0) {
+ }
+
+ // Return the next entry from the directory
+ // name: (output) the NUL terminated entry name
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // After calling this, one must call |PopEntry| otherwise you'll get the same
+ // entry over and over.
+ bool GetNextEntry(const char** name) {
+ struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ if (buf_used_ == 0) {
+ // need to read more entries.
+ const int n = sys_getdents(fd_, dent, sizeof(buf_));
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+ }
+
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ assert(buf_used_ > 0);
+
+ *name = dent->d_name;
+ return true;
+ }
+
+ void PopEntry() {
+ if (!buf_used_)
+ return;
+
+ const struct kernel_dirent* const dent =
+ reinterpret_cast<kernel_dirent*>(buf_);
+
+ buf_used_ -= dent->d_reclen;
+ memmove(buf_, buf_ + dent->d_reclen, buf_used_);
+ }
+
+ private:
+ const int fd_;
+ bool hit_eof_;
+ unsigned buf_used_;
+ uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_
diff --git a/src/client/linux/minidump_writer/directory_reader_unittest.cc b/src/client/linux/minidump_writer/directory_reader_unittest.cc
new file mode 100644
index 00000000..3034e619
--- /dev/null
+++ b/src/client/linux/minidump_writer/directory_reader_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <set>
+#include <string>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include "client/linux/minidump_writer/directory_reader.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+namespace {
+typedef testing::Test DirectoryReaderTest;
+}
+
+TEST(DirectoryReaderTest, CompareResults) {
+ std::set<std::string> dent_set;
+
+ DIR *const dir = opendir("/proc/self");
+ ASSERT_TRUE(dir != NULL);
+
+ struct dirent* dent;
+ while ((dent = readdir(dir)))
+ dent_set.insert(dent->d_name);
+
+ closedir(dir);
+
+ const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY);
+ ASSERT_GE(fd, 0);
+
+ DirectoryReader dir_reader(fd);
+ unsigned seen = 0;
+
+ const char* name;
+ while (dir_reader.GetNextEntry(&name)) {
+ ASSERT_TRUE(dent_set.find(name) != dent_set.end());
+ seen++;
+ dir_reader.PopEntry();
+ }
+
+ ASSERT_TRUE(dent_set.find("status") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("stat") != dent_set.end());
+ ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end());
+
+ ASSERT_EQ(dent_set.size(), seen);
+ close(fd);
+}
diff --git a/src/client/linux/minidump_writer/line_reader.h b/src/client/linux/minidump_writer/line_reader.h
new file mode 100644
index 00000000..5c0a1154
--- /dev/null
+++ b/src/client/linux/minidump_writer/line_reader.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
+
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include "common/linux/linux_syscall_support.h"
+
+namespace google_breakpad {
+
+// A class for reading a file, line by line, without using fopen/fgets or other
+// functions which may allocate memory.
+class LineReader {
+ public:
+ LineReader(int fd)
+ : fd_(fd),
+ hit_eof_(false),
+ buf_used_(0) {
+ }
+
+ // The maximum length of a line.
+ static const size_t kMaxLineLen = 512;
+
+ // Return the next line from the file.
+ // line: (output) a pointer to the start of the line. The line is NUL
+ // terminated.
+ // len: (output) the length of the line (not inc the NUL byte)
+ //
+ // Returns true iff successful (false on EOF).
+ //
+ // One must call |PopLine| after this function, otherwise you'll continue to
+ // get the same line over and over.
+ bool GetNextLine(const char **line, unsigned *len) {
+ for (;;) {
+ if (buf_used_ == 0 && hit_eof_)
+ return false;
+
+ for (unsigned i = 0; i < buf_used_; ++i) {
+ if (buf_[i] == '\n' || buf_[i] == 0) {
+ buf_[i] = 0;
+ *len = i;
+ *line = buf_;
+ return true;
+ }
+ }
+
+ if (buf_used_ == sizeof(buf_)) {
+ // we scanned the whole buffer and didn't find an end-of-line marker.
+ // This line is too long to process.
+ return false;
+ }
+
+ // We didn't find any end-of-line terminators in the buffer. However, if
+ // this is the last line in the file it might not have one:
+ if (hit_eof_) {
+ assert(buf_used_);
+ // There's room for the NUL because of the buf_used_ == sizeof(buf_)
+ // check above.
+ buf_[buf_used_] = 0;
+ *len = buf_used_;
+ buf_used_ += 1; // since we appended the NUL.
+ *line = buf_;
+ return true;
+ }
+
+ // Otherwise, we should pull in more data from the file
+ const ssize_t n = sys_read(fd_, buf_ + buf_used_,
+ sizeof(buf_) - buf_used_);
+ if (n < 0) {
+ return false;
+ } else if (n == 0) {
+ hit_eof_ = true;
+ } else {
+ buf_used_ += n;
+ }
+
+ // At this point, we have either set the hit_eof_ flag, or we have more
+ // data to process...
+ }
+ }
+
+ void PopLine(unsigned len) {
+ // len doesn't include the NUL byte at the end.
+
+ assert(buf_used_ >= len + 1);
+ buf_used_ -= len + 1;
+ memmove(buf_, buf_ + len + 1, buf_used_);
+ }
+
+ private:
+ const int fd_;
+
+ bool hit_eof_;
+ unsigned buf_used_;
+ char buf_[kMaxLineLen];
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_
diff --git a/src/client/linux/minidump_writer/line_reader_unittest.cc b/src/client/linux/minidump_writer/line_reader_unittest.cc
new file mode 100644
index 00000000..222a098e
--- /dev/null
+++ b/src/client/linux/minidump_writer/line_reader_unittest.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "client/linux/minidump_writer/line_reader.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+static int TemporaryFile() {
+ static const char templ[] = "/tmp/line-reader-unittest-XXXXXX";
+ char templ_copy[sizeof(templ)];
+ memcpy(templ_copy, templ, sizeof(templ));
+ const int fd = mkstemp(templ_copy);
+ if (fd >= 0)
+ unlink(templ_copy);
+
+ return fd;
+}
+
+namespace {
+typedef testing::Test LineReaderTest;
+}
+
+TEST(LineReaderTest, EmptyFile) {
+ const int fd = TemporaryFile();
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, OneLineTerminated) {
+ const int fd = TemporaryFile();
+ write(fd, "a\n", 2);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, OneLine) {
+ const int fd = TemporaryFile();
+ write(fd, "a", 1);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TwoLinesTerminated) {
+ const int fd = TemporaryFile();
+ write(fd, "a\nb\n", 4);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'b');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TwoLines) {
+ const int fd = TemporaryFile();
+ write(fd, "a\nb", 3);
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'a');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, 1);
+ ASSERT_EQ(line[0], 'b');
+ ASSERT_EQ(line[1], 0);
+ reader.PopLine(len);
+
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
+
+TEST(LineReaderTest, MaxLength) {
+ const int fd = TemporaryFile();
+ char l[LineReader::kMaxLineLen - 1];
+ memset(l, 'a', sizeof(l));
+ write(fd, l, sizeof(l));
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_TRUE(reader.GetNextLine(&line, &len));
+ ASSERT_EQ(len, sizeof(l));
+ ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0);
+ ASSERT_EQ(line[len], 0);
+
+ close(fd);
+}
+
+TEST(LineReaderTest, TooLong) {
+ const int fd = TemporaryFile();
+ char l[LineReader::kMaxLineLen];
+ memset(l, 'a', sizeof(l));
+ write(fd, l, sizeof(l));
+ lseek(fd, 0, SEEK_SET);
+ LineReader reader(fd);
+
+ const char *line;
+ unsigned len;
+ ASSERT_FALSE(reader.GetNextLine(&line, &len));
+
+ close(fd);
+}
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
new file mode 100644
index 00000000..e821db7d
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -0,0 +1,419 @@
+// 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.
+
+// This code deals with the mechanics of getting information about a crashed
+// process. Since this code may run in a compromised address space, the same
+// rules apply as detailed at the top of minidump_writer.h: no libc calls and
+// use the alternative allocator.
+
+#include "client/linux/minidump_writer/linux_dumper.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+
+#include "client/linux/minidump_writer/directory_reader.h"
+#include "client/linux/minidump_writer/line_reader.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/linux/linux_syscall_support.h"
+
+// Suspend a thread by attaching to it.
+static bool SuspendThread(pid_t pid) {
+ // This may fail if the thread has just died or debugged.
+ errno = 0;
+ if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
+ errno != 0) {
+ return false;
+ }
+ while (sys_waitpid(pid, NULL, __WALL) < 0) {
+ if (errno != EINTR) {
+ sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
+ return false;
+ }
+ }
+ return true;
+}
+
+// Resume a thread by detaching from it.
+static bool ResumeThread(pid_t pid) {
+ return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
+}
+
+namespace google_breakpad {
+
+LinuxDumper::LinuxDumper(int pid)
+ : pid_(pid),
+ threads_suspened_(false),
+ threads_(&allocator_, 8),
+ mappings_(&allocator_) {
+}
+
+bool LinuxDumper::Init() {
+ return EnumerateThreads(&threads_) &&
+ EnumerateMappings(&mappings_);
+}
+
+bool LinuxDumper::ThreadsSuspend() {
+ if (threads_suspened_)
+ return true;
+ bool good = true;
+ for (size_t i = 0; i < threads_.size(); ++i)
+ good &= SuspendThread(threads_[i]);
+ threads_suspened_ = true;
+ return good;
+}
+
+bool LinuxDumper::ThreadsResume() {
+ if (!threads_suspened_)
+ return false;
+ bool good = true;
+ for (size_t i = 0; i < threads_.size(); ++i)
+ good &= ResumeThread(threads_[i]);
+ threads_suspened_ = false;
+ return good;
+}
+
+void
+LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
+ assert(path);
+ if (!path) {
+ return;
+ }
+
+ path[0] = '\0';
+
+ const unsigned pid_len = my_int_len(pid);
+
+ assert(node);
+ if (!node) {
+ return;
+ }
+
+ size_t node_len = my_strlen(node);
+ assert(node_len < NAME_MAX);
+ if (node_len >= NAME_MAX) {
+ return;
+ }
+
+ assert(node_len > 0);
+ if (node_len == 0) {
+ return;
+ }
+
+ assert(pid > 0);
+ if (pid <= 0) {
+ return;
+ }
+
+ const size_t total_length = 6 + pid_len + 1 + node_len;
+
+ assert(total_length < NAME_MAX);
+ if (total_length >= NAME_MAX) {
+ return;
+ }
+
+ memcpy(path, "/proc/", 6);
+ my_itos(path + 6, pid, pid_len);
+ memcpy(path + 6 + pid_len, "/", 1);
+ memcpy(path + 6 + pid_len + 1, node, node_len);
+ memcpy(path + total_length, "\0", 1);
+}
+
+void*
+LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
+ char auxv_path[80];
+ BuildProcPath(auxv_path, pid, "auxv");
+
+ // If BuildProcPath errors out due to invalid input, we'll handle it when
+ // we try to sys_open the file.
+
+ // Find the AT_SYSINFO_EHDR entry for linux-gate.so
+ // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
+ // information.
+ int fd = sys_open(auxv_path, O_RDONLY, 0);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ elf_aux_entry one_aux_entry;
+ while (sys_read(fd,
+ &one_aux_entry,
+ sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
+ one_aux_entry.a_type != AT_NULL) {
+ if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
+ close(fd);
+ return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
+ }
+ }
+ close(fd);
+ return NULL;
+}
+
+bool
+LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
+ char maps_path[80];
+ BuildProcPath(maps_path, pid_, "maps");
+
+ // linux_gate_loc is the beginning of the kernel's mapping of
+ // linux-gate.so in the process. It doesn't actually show up in the
+ // maps list as a filename, so we use the aux vector to find it's
+ // load location and special case it's entry when creating the list
+ // of mappings.
+ const void* linux_gate_loc;
+ linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
+
+ const int fd = sys_open(maps_path, O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+
+ const char* line;
+ unsigned line_len;
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ uintptr_t start_addr, end_addr, offset;
+
+ const char* i1 = my_read_hex_ptr(&start_addr, line);
+ if (*i1 == '-') {
+ const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
+ if (*i2 == ' ') {
+ const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
+ if (*i3 == ' ') {
+ MappingInfo* const module = new(allocator_) MappingInfo;
+ memset(module, 0, sizeof(MappingInfo));
+ module->start_addr = start_addr;
+ module->size = end_addr - start_addr;
+ module->offset = offset;
+ const char* name = NULL;
+ // Only copy name if the name is a valid path name, or if
+ // we've found the VDSO image
+ if ((name = my_strchr(line, '/')) != NULL) {
+ const unsigned l = my_strlen(name);
+ if (l < sizeof(module->name))
+ memcpy(module->name, name, l);
+ } else if (linux_gate_loc &&
+ reinterpret_cast<void*>(module->start_addr) ==
+ linux_gate_loc) {
+ memcpy(module->name,
+ kLinuxGateLibraryName,
+ my_strlen(kLinuxGateLibraryName));
+ module->offset = 0;
+ }
+ result->push_back(module);
+ }
+ }
+ }
+ line_reader->PopLine(line_len);
+ }
+
+ sys_close(fd);
+
+ return result->size() > 0;
+}
+
+// Parse /proc/$pid/task to list all the threads of the process identified by
+// pid.
+bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
+ char task_path[80];
+ BuildProcPath(task_path, pid_, "task");
+
+ const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
+ if (fd < 0)
+ return false;
+ DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
+
+ // The directory may contain duplicate entries which we filter by assuming
+ // that they are consecutive.
+ int last_tid = -1;
+ const char* dent_name;
+ while (dir_reader->GetNextEntry(&dent_name)) {
+ if (my_strcmp(dent_name, ".") &&
+ my_strcmp(dent_name, "..")) {
+ int tid = 0;
+ if (my_strtoui(&tid, dent_name) &&
+ last_tid != tid) {
+ last_tid = tid;
+ result->push_back(tid);
+ }
+ }
+ dir_reader->PopEntry();
+ }
+
+ sys_close(fd);
+ return true;
+}
+
+// Read thread info from /proc/$pid/status.
+// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible,
+// these members are set to -1. Returns true iff all three members are
+// availible.
+bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) {
+ assert(info != NULL);
+ char status_path[80];
+ BuildProcPath(status_path, tid, "status");
+
+ const int fd = open(status_path, O_RDONLY);
+ if (fd < 0)
+ return false;
+
+ LineReader* const line_reader = new(allocator_) LineReader(fd);
+ const char* line;
+ unsigned line_len;
+
+ info->ppid = info->tgid = -1;
+
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ if (my_strncmp("Tgid:\t", line, 6) == 0) {
+ my_strtoui(&info->tgid, line + 6);
+ } else if (my_strncmp("PPid:\t", line, 6) == 0) {
+ my_strtoui(&info->ppid, line + 6);
+ }
+
+ line_reader->PopLine(line_len);
+ }
+
+ if (info->ppid == -1 || info->tgid == -1)
+ return false;
+
+ if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 ||
+ sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
+ return false;
+ }
+
+#if defined(__i386) || defined(__x86_64)
+ if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
+ return false;
+
+ for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
+ if (sys_ptrace(
+ PTRACE_PEEKUSER, tid,
+ reinterpret_cast<void*> (offsetof(struct user,
+ u_debugreg[0]) + i *
+ sizeof(debugreg_t)),
+ &info->dregs[i]) == -1) {
+ return false;
+ }
+ }
+#endif
+
+ const uint8_t* stack_pointer;
+#if defined(__i386)
+ memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
+#elif defined(__x86_64)
+ memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
+#else
+#error "This code hasn't been ported to your platform yet."
+#endif
+
+ if (!GetStackInfo(&info->stack, &info->stack_len,
+ (uintptr_t) stack_pointer))
+ return false;
+
+ return true;
+}
+
+// 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) {
+#if defined(__i386) || defined(__x86_64)
+ static const bool stack_grows_down = true;
+ static const uintptr_t page_size = 4096;
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+ // Move the stack pointer to the bottom of the page that it's in.
+ 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 unsigned kStackToCapture = 32 * 1024;
+
+ const MappingInfo* mapping = FindMapping(stack_pointer);
+ if (!mapping)
+ return false;
+ if (stack_grows_down) {
+ const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
+ const ptrdiff_t distance_to_end =
+ static_cast<ptrdiff_t>(mapping->size) - offset;
+ *stack_len = distance_to_end > kStackToCapture ?
+ kStackToCapture : distance_to_end;
+ *stack = stack_pointer;
+ } else {
+ const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
+ *stack_len = offset > kStackToCapture ? kStackToCapture : offset;
+ *stack = stack_pointer - *stack_len;
+ }
+
+ return true;
+}
+
+// static
+void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length) {
+ unsigned long tmp;
+ size_t done = 0;
+ static const size_t word_size = sizeof(tmp);
+ uint8_t* const local = (uint8_t*) dest;
+ uint8_t* const remote = (uint8_t*) src;
+
+ while (done < length) {
+ const size_t l = length - done > word_size ? word_size : length - done;
+ if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1)
+ tmp = 0;
+ memcpy(local + done, &tmp, l);
+ done += l;
+ }
+}
+
+// Find the mapping which the given memory address falls in.
+const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
+ const uintptr_t addr = (uintptr_t) address;
+
+ for (size_t i = 0; i < mappings_.size(); ++i) {
+ const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
+ if (addr >= start && addr - start < mappings_[i]->size)
+ return mappings_[i];
+ }
+
+ return NULL;
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
new file mode 100644
index 00000000..252e9ee6
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -0,0 +1,142 @@
+// 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_LINUX_DUMPER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_
+
+#include <elf.h>
+#include <stdint.h>
+#include <sys/user.h>
+#include <linux/limits.h>
+
+#include "common/linux/memory.h"
+
+namespace google_breakpad {
+
+typedef typeof(((struct user*) 0)->u_debugreg[0]) debugreg_t;
+
+// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
+#if defined(__i386)
+typedef Elf32_auxv_t elf_aux_entry;
+#elif defined(__x86_64__)
+typedef Elf64_auxv_t elf_aux_entry;
+#endif
+// When we find the VDSO mapping in the process's address space, this
+// is the name we use for it when writing it to the minidump.
+// This should always be less than NAME_MAX!
+const char kLinuxGateLibraryName[] = "linux-gate.so";
+
+// We produce one of these structures for each thread in the crashed process.
+struct ThreadInfo {
+ pid_t tgid; // thread group id
+ pid_t ppid; // parent process
+
+ // Even on platforms where the stack grows down, the following will point to
+ // the smallest address in the stack.
+ const void* stack; // pointer to the stack area
+ size_t stack_len; // length of the stack to copy
+
+ user_regs_struct regs;
+ user_fpregs_struct fpregs;
+#if defined(__i386) || defined(__x86_64)
+ user_fpxregs_struct fpxregs;
+
+ static const unsigned kNumDebugRegisters = 8;
+ debugreg_t dregs[8];
+#endif
+};
+
+// One of these is produced for each mapping in the process (i.e. line in
+// /proc/$x/maps).
+struct MappingInfo {
+ uintptr_t start_addr;
+ size_t size;
+ size_t offset; // offset into the backed file.
+ char name[NAME_MAX];
+};
+
+class LinuxDumper {
+ public:
+ explicit LinuxDumper(pid_t pid);
+
+ // Parse the data for |threads| and |mappings|.
+ bool Init();
+
+ // Suspend/resume all threads in the given process.
+ bool ThreadsSuspend();
+ bool ThreadsResume();
+
+ // Read information about the given thread. Returns true on success. One must
+ // have called |ThreadsSuspend| first.
+ bool ThreadInfoGet(pid_t tid, ThreadInfo* info);
+
+ // These are only valid after a call to |Init|.
+ const wasteful_vector<pid_t> &threads() { return threads_; }
+ const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
+ const MappingInfo* FindMapping(const void* address) const;
+
+ // Find a block of memory to take as the stack given the top of stack pointer.
+ // stack: (output) the lowest address in the memory area
+ // stack_len: (output) the length of the memory area
+ // stack_top: the current top of the stack
+ bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top);
+
+ PageAllocator* allocator() { return &allocator_; }
+
+ // memcpy from a remote process.
+ static void CopyFromProcess(void* dest, pid_t child, const void* src,
+ size_t length);
+
+ // Builds a proc path for a certain pid for a node. path is a
+ // character array that is overwritten, and node is the final node
+ // without any slashes.
+ void BuildProcPath(char* path, pid_t pid, const char* node) const;
+
+ // Utility method to find the location of where the kernel has
+ // mapped linux-gate.so in memory(shows up in /proc/pid/maps as
+ // [vdso], but we can't guarantee that it's the only virtual dynamic
+ // shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR
+ // is the safest way to go.)
+ void* FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const;
+ private:
+ bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
+ bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
+
+ const pid_t pid_;
+
+ mutable PageAllocator allocator_;
+
+ bool threads_suspened_;
+ wasteful_vector<pid_t> threads_; // the ids of all the threads
+ wasteful_vector<MappingInfo*> mappings_; // info from /proc/<pid>/maps
+};
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_
diff --git a/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
new file mode 100644
index 00000000..f5ed914b
--- /dev/null
+++ b/src/client/linux/minidump_writer/linux_dumper_unittest.cc
@@ -0,0 +1,118 @@
+// 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 <unistd.h>
+
+#include "client/linux/minidump_writer/linux_dumper.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+namespace {
+typedef testing::Test LinuxDumperTest;
+}
+
+TEST(LinuxDumperTest, Setup) {
+ LinuxDumper dumper(getpid());
+}
+
+TEST(LinuxDumperTest, FindMappings) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
+ ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
+ ASSERT_FALSE(dumper.FindMapping(NULL));
+}
+
+TEST(LinuxDumperTest, ThreadList) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ ASSERT_GE(dumper.threads().size(), 1);
+ bool found = false;
+ for (size_t i = 0; i < dumper.threads().size(); ++i) {
+ if (dumper.threads()[i] == getpid()) {
+ found = true;
+ break;
+ }
+ }
+}
+
+TEST(LinuxDumperTest, BuildProcPath) {
+ const pid_t pid = getpid();
+ LinuxDumper dumper(pid);
+
+ char maps_path[256] = "dummymappath";
+ char maps_path_expected[256];
+ snprintf(maps_path_expected, sizeof(maps_path_expected),
+ "/proc/%d/maps", pid);
+ dumper.BuildProcPath(maps_path, pid, "maps");
+ ASSERT_STREQ(maps_path, maps_path_expected);
+
+ // In release mode, we expect BuildProcPath to handle the invalid
+ // parameters correctly and fill map_path with an empty
+ // NULL-terminated string.
+#ifdef NDEBUG
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, 0, "maps");
+ EXPECT_STREQ(maps_path, "");
+
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, getpid(), "");
+ EXPECT_STREQ(maps_path, "");
+
+ snprintf(maps_path, sizeof(maps_path), "dummymappath");
+ dumper.BuildProcPath(maps_path, getpid(), NULL);
+ EXPECT_STREQ(maps_path, "");
+#endif
+}
+
+TEST(LinuxDumperTest, MappingsIncludeLinuxGate) {
+ LinuxDumper dumper(getpid());
+ ASSERT_TRUE(dumper.Init());
+
+ void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid());
+ if (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));
+ }
+}
diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc
new file mode 100644
index 00000000..a99309f3
--- /dev/null
+++ b/src/client/linux/minidump_writer/minidump_writer.cc
@@ -0,0 +1,872 @@
+// 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.
+
+// 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 "client/linux/minidump_writer/minidump_writer.h"
+#include "client/minidump_file_writer-inl.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ucontext.h>
+#include <sys/user.h>
+#include <sys/utsname.h>
+
+#include "client/minidump_file_writer.h"
+#include "google_breakpad/common/minidump_format.h"
+#include "google_breakpad/common/minidump_cpu_amd64.h"
+#include "google_breakpad/common/minidump_cpu_x86.h"
+
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/line_reader.h"
+#include "client/linux/minidump_writer//linux_dumper.h"
+#include "common/linux/linux_libc_support.h"
+#include "common/linux/linux_syscall_support.h"
+
+// These are additional minidump stream values which are specific to the linux
+// breakpad implementation.
+enum {
+ MD_LINUX_CPU_INFO = 0x47670003, /* /proc/cpuinfo */
+ MD_LINUX_PROC_STATUS = 0x47670004, /* /proc/$x/status */
+ MD_LINUX_LSB_RELEASE = 0x47670005, /* /etc/lsb-release */
+ MD_LINUX_CMD_LINE = 0x47670006, /* /proc/$x/cmdline */
+ MD_LINUX_ENVIRON = 0x47670007, /* /proc/$x/environ */
+ MD_LINUX_AUXV = 0x47670008, /* /proc/$x/auxv */
+};
+
+// Minidump defines register structures which are different from the raw
+// structures which we get from the kernel. These are platform specific
+// functions to juggle the ucontext and user structures into minidump format.
+#if defined(__i386)
+typedef MDRawContextX86 RawContextCPU;
+
+// Write a uint16_t to memory
+// out: memory location to write to
+// v: value to write.
+static void U16(void* out, uint16_t v) {
+ memcpy(out, &v, sizeof(v));
+}
+
+// Write a uint32_t to memory
+// out: memory location to write to
+// v: value to write.
+static void U32(void* out, uint32_t v) {
+ memcpy(out, &v, sizeof(v));
+}
+
+// Juggle an x86 user_(fp|fpx|)regs_struct into minidump format
+// out: the minidump structure
+// info: the collection of register structures.
+static void CPUFillFromThreadInfo(MDRawContextX86 *out,
+ const google_breakpad::ThreadInfo &info) {
+ out->context_flags = MD_CONTEXT_X86_ALL;
+
+ out->dr0 = info.dregs[0];
+ out->dr1 = info.dregs[1];
+ out->dr2 = info.dregs[2];
+ out->dr3 = info.dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = info.dregs[6];
+ out->dr7 = info.dregs[7];
+
+ out->gs = info.regs.xgs;
+ out->fs = info.regs.xfs;
+ out->es = info.regs.xes;
+ out->ds = info.regs.xds;
+
+ out->edi = info.regs.edi;
+ out->esi = info.regs.esi;
+ out->ebx = info.regs.ebx;
+ out->edx = info.regs.edx;
+ out->ecx = info.regs.ecx;
+ out->eax = info.regs.eax;
+
+ out->ebp = info.regs.ebp;
+ out->eip = info.regs.eip;
+ out->cs = info.regs.xcs;
+ out->eflags = info.regs.eflags;
+ out->esp = info.regs.esp;
+ out->ss = info.regs.xss;
+
+ out->float_save.control_word = info.fpregs.cwd;
+ out->float_save.status_word = info.fpregs.swd;
+ out->float_save.tag_word = info.fpregs.twd;
+ out->float_save.error_offset = info.fpregs.fip;
+ out->float_save.error_selector = info.fpregs.fcs;
+ out->float_save.data_offset = info.fpregs.foo;
+ out->float_save.data_selector = info.fpregs.fos;
+
+ // 8 registers * 10 bytes per register.
+ memcpy(out->float_save.register_area, info.fpregs.st_space, 10 * 8);
+
+ // This matches the Intel fpsave format.
+ U16(out->extended_registers + 0, info.fpregs.cwd);
+ U16(out->extended_registers + 2, info.fpregs.swd);
+ U16(out->extended_registers + 4, info.fpregs.twd);
+ U16(out->extended_registers + 6, info.fpxregs.fop);
+ U32(out->extended_registers + 8, info.fpxregs.fip);
+ U16(out->extended_registers + 12, info.fpxregs.fcs);
+ U32(out->extended_registers + 16, info.fpregs.foo);
+ U16(out->extended_registers + 20, info.fpregs.fos);
+ U32(out->extended_registers + 24, info.fpxregs.mxcsr);
+
+ memcpy(out->extended_registers + 32, &info.fpxregs.st_space, 128);
+ memcpy(out->extended_registers + 160, &info.fpxregs.xmm_space, 128);
+}
+
+// Juggle an x86 ucontext into minidump format
+// out: the minidump structure
+// info: the collection of register structures.
+static void CPUFillFromUContext(MDRawContextX86 *out, const ucontext *uc,
+ const struct _libc_fpstate* fp) {
+ const greg_t* regs = uc->uc_mcontext.gregs;
+
+ out->context_flags = MD_CONTEXT_X86_FULL |
+ MD_CONTEXT_X86_FLOATING_POINT;
+
+ out->gs = regs[REG_GS];
+ out->fs = regs[REG_FS];
+ out->es = regs[REG_ES];
+ out->ds = regs[REG_DS];
+
+ out->edi = regs[REG_EDI];
+ out->esi = regs[REG_ESI];
+ out->ebx = regs[REG_EBX];
+ out->edx = regs[REG_EDX];
+ out->ecx = regs[REG_ECX];
+ out->eax = regs[REG_EAX];
+
+ out->ebp = regs[REG_EBP];
+ out->eip = regs[REG_EIP];
+ out->cs = regs[REG_CS];
+ out->eflags = regs[REG_EFL];
+ out->esp = regs[REG_UESP];
+ out->ss = regs[REG_SS];
+
+ out->float_save.control_word = fp->cw;
+ out->float_save.status_word = fp->sw;
+ out->float_save.tag_word = fp->tag;
+ out->float_save.error_offset = fp->ipoff;
+ out->float_save.error_selector = fp->cssel;
+ out->float_save.data_offset = fp->dataoff;
+ out->float_save.data_selector = fp->datasel;
+
+ // 8 registers * 10 bytes per register.
+ memcpy(out->float_save.register_area, fp->_st, 10 * 8);
+}
+
+#elif defined(__x86_64)
+typedef MDRawContextAMD64 RawContextCPU;
+
+static void CPUFillFromThreadInfo(MDRawContextAMD64 *out,
+ const google_breakpad::ThreadInfo &info) {
+ out->context_flags = MD_CONTEXT_AMD64_FULL |
+ MD_CONTEXT_AMD64_SEGMENTS;
+
+ out->cs = info.regs.cs;
+
+ out->ds = info.regs.ds;
+ out->es = info.regs.es;
+ out->fs = info.regs.fs;
+ out->gs = info.regs.gs;
+
+ out->ss = info.regs.ss;
+ out->eflags = info.regs.eflags;
+
+ out->dr0 = info.dregs[0];
+ out->dr1 = info.dregs[1];
+ out->dr2 = info.dregs[2];
+ out->dr3 = info.dregs[3];
+ // 4 and 5 deliberatly omitted because they aren't included in the minidump
+ // format.
+ out->dr6 = info.dregs[6];
+ out->dr7 = info.dregs[7];
+
+ out->rax = info.regs.rax;
+ out->rcx = info.regs.rcx;
+ out->rdx = info.regs.rdx;
+ out->rbx = info.regs.rbx;
+
+ out->rsp = info.regs.rsp;
+
+ out->rbp = info.regs.rbp;
+ out->rsi = info.regs.rsi;
+ out->rdi = info.regs.rdi;
+ out->r8 = info.regs.r8;
+ out->r9 = info.regs.r9;
+ out->r10 = info.regs.r10;
+ out->r11 = info.regs.r11;
+ out->r12 = info.regs.r12;
+ out->r13 = info.regs.r13;
+ out->r14 = info.regs.r14;
+ out->r15 = info.regs.r15;
+
+ out->rip = info.regs.rip;
+
+ out->flt_save.control_word = info.fpregs.cwd;
+ out->flt_save.status_word = info.fpregs.swd;
+ out->flt_save.tag_word = info.fpregs.twd;
+ out->flt_save.error_opcode = info.fpregs.fop;
+ out->flt_save.error_offset = info.fpregs.rip;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_offset = info.fpregs.rdp;
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = info.fpregs.mxcsr;
+ out->flt_save.mx_csr_mask = info.fpregs.mxcsr_mask;
+ memcpy(&out->flt_save.float_registers, &info.fpregs.st_space, 8 * 16);
+ memcpy(&out->flt_save.xmm_registers, &info.fpregs.xmm_space, 16 * 16);
+}
+
+static void CPUFillFromUContext(MDRawContextAMD64 *out, const ucontext *uc,
+ const struct _libc_fpstate* fpregs) {
+ const greg_t* regs = uc->gregs;
+
+ out->context_flags = MD_CONTEXT_AMD64_FULL;
+
+ out->cs = regs[REG_CSGSFS] & 0xffff;
+
+ out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff;
+ out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff;
+
+ out->eflags = regs[REG_EFL];
+
+ out->rax = regs[REG_RAX];
+ out->rcx = regs[REG_RCX];
+ out->rdx = regs[REG_RDX];
+ out->rbx = regs[REG_RBX];
+
+ out->rsp = regs[REG_RSP];
+ out->rbp = regs[REG_RBP];
+ out->rsi = regs[REG_RSI];
+ out->rdi = regs[REG_RDI];
+ out->r8 = regs[REG_R8];
+ out->r9 = regs[REG_R9];
+ out->r10 = regs[REG_R10];
+ out->r11 = regs[REG_R11];
+ out->r12 = regs[REG_R12];
+ out->r13 = regs[REG_R13];
+ out->r14 = regs[REG_R14];
+ out->r15 = regs[REG_R15];
+
+ out->rip = regs[REG_RIP];
+
+ out->flt_save.control_word = fpregs->cwd;
+ out->flt_save.status_word = fpregs->swd;
+ out->flt_save.tag_word = fpregs->ftw;
+ out->flt_save.error_opcode = fpregs->fop;
+ out->flt_save.error_offset = fpregs->rip;
+ out->flt_save.data_offset = fpregs->rdp;
+ out->flt_save.error_selector = 0; // We don't have this.
+ out->flt_save.data_selector = 0; // We don't have this.
+ out->flt_save.mx_csr = fpregs->mxcsr;
+ out->flt_save.mx_csr_mask = fpregs->mxcsr_mask;
+ memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16);
+ memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16);
+}
+
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+namespace google_breakpad {
+
+class MinidumpWriter {
+ public:
+ MinidumpWriter(const char* filename,
+ pid_t crashing_pid,
+ const ExceptionHandler::CrashContext* context)
+ : filename_(filename),
+ siginfo_(&context->siginfo),
+ ucontext_(&context->context),
+ float_state_(&context->float_state),
+ crashing_tid_(context->tid),
+ dumper_(crashing_pid) {
+ }
+
+ bool Init() {
+ return dumper_.Init() && minidump_writer_.Open(filename_) &&
+ dumper_.ThreadsSuspend();
+ }
+
+ ~MinidumpWriter() {
+ minidump_writer_.Close();
+ dumper_.ThreadsResume();
+ }
+
+ bool Dump() {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of stream which we write.
+ static const unsigned kNumWriters = 11;
+
+ TypedMDRVA<MDRawHeader> header(&minidump_writer_);
+ TypedMDRVA<MDRawDirectory> dir(&minidump_writer_);
+ if (!header.Allocate())
+ return false;
+ if (!dir.AllocateArray(kNumWriters))
+ return false;
+ 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 (!WriteMappings(&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, crashing_tid_, "status"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_LSB_RELEASE;
+ if (!WriteFile(&dirent.location, "/etc/lsb-release"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_CMD_LINE;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "cmdline"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_ENVIRON;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "environ"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_AUXV;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "auxv"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ dirent.stream_type = MD_LINUX_AUXV;
+ if (!WriteProcFile(&dirent.location, crashing_tid_, "maps"))
+ NullifyDirectoryEntry(&dirent);
+ dir.CopyIndex(dir_index++, &dirent);
+
+ // If you add more directory entries, don't forget to update kNumWriters,
+ // above.
+
+ dumper_.ThreadsResume();
+ 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;
+
+ 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 (thread.thread_id == crashing_tid_) {
+ const void* stack;
+ size_t stack_len;
+ if (!dumper_.GetStackInfo(&stack, &stack_len, GetStackPointer()))
+ return false;
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(stack_len))
+ return false;
+ uint8_t* stack_copy = (uint8_t*) dumper_.allocator()->Alloc(stack_len);
+ dumper_.CopyFromProcess(stack_copy, thread.thread_id, stack, stack_len);
+ memory.Copy(stack_copy, stack_len);
+ thread.stack.start_of_memory_range = (uintptr_t) (stack);
+ thread.stack.memory = memory.location();
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+ CPUFillFromUContext(cpu.get(), ucontext_, float_state_);
+ thread.thread_context = cpu.location();
+ crashing_thread_context_ = cpu.location();
+ } else {
+ ThreadInfo info;
+ if (!dumper_.ThreadInfoGet(dumper_.threads()[i], &info))
+ return false;
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(info.stack_len))
+ return false;
+ uint8_t* stack_copy =
+ (uint8_t*) dumper_.allocator()->Alloc(info.stack_len);
+ dumper_.CopyFromProcess(stack_copy, thread.thread_id, info.stack,
+ info.stack_len);
+ memory.Copy(stack_copy, info.stack_len);
+ thread.stack.start_of_memory_range = (uintptr_t)(info.stack);
+ thread.stack.memory = memory.location();
+ TypedMDRVA<RawContextCPU> cpu(&minidump_writer_);
+ if (!cpu.Allocate())
+ return false;
+ my_memset(cpu.get(), 0, sizeof(RawContextCPU));
+ CPUFillFromThreadInfo(cpu.get(), info);
+ thread.thread_context = cpu.location();
+ }
+
+ list.CopyIndexAfterObject(i, &thread, sizeof(thread));
+ }
+
+ return true;
+ }
+
+ static bool ShouldIncludeMapping(const MappingInfo& mapping) {
+ if (mapping.name[0] == 0 || // we only want modules with filenames.
+ mapping.offset || // we only want to include one mapping per shared lib.
+ mapping.size < 4096) { // too small to get a signature for.
+ return false;
+ }
+
+ return true;
+ }
+
+ // 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 = 0;
+
+ for (unsigned i = 0; i < dumper_.mappings().size(); ++i) {
+ const MappingInfo& mapping = *dumper_.mappings()[i];
+ if (ShouldIncludeMapping(mapping))
+ num_output_mappings++;
+ }
+
+ TypedMDRVA<uint32_t> list(&minidump_writer_);
+ if (!list.AllocateObjectAndArray(num_output_mappings, sizeof(MDRawModule)))
+ return false;
+
+ dirent->stream_type = MD_MODULE_LIST_STREAM;
+ dirent->location = list.location();
+ *list.get() = num_output_mappings;
+
+ for (unsigned i = 0, j = 0; i < num_mappings; ++i) {
+ const MappingInfo& mapping = *dumper_.mappings()[i];
+ if (!ShouldIncludeMapping(mapping))
+ continue;
+
+ MDRawModule mod;
+ my_memset(&mod, 0, sizeof(mod));
+ mod.base_of_image = mapping.start_addr;
+ mod.size_of_image = mapping.size;
+ const size_t filepath_len = my_strlen(mapping.name);
+
+ // Figure out file name from path
+ const char* filename_ptr = mapping.name + filepath_len - 1;
+ while (filename_ptr >= mapping.name) {
+ if (*filename_ptr == '/')
+ break;
+ filename_ptr--;
+ }
+ filename_ptr++;
+ const size_t filename_len = mapping.name + filepath_len - filename_ptr;
+
+ uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX];
+ uint8_t* cv_ptr = cv_buf;
+ UntypedMDRVA cv(&minidump_writer_);
+ if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1))
+ return false;
+
+ const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE;
+ memcpy(cv_ptr, &cv_signature, sizeof(cv_signature));
+ cv_ptr += sizeof(cv_signature);
+
+ {
+ // We XOR the first page of the file to get a signature for it.
+ uint8_t xor_buf[sizeof(MDGUID)];
+ size_t done = 0;
+ uint8_t* signature = cv_ptr;
+ cv_ptr += sizeof(xor_buf);
+
+ my_memset(signature, 0, sizeof(xor_buf));
+ while (done < 4096) {
+ dumper_.CopyFromProcess(xor_buf, crashing_tid_,
+ (void *) (mod.base_of_image + done),
+ sizeof(xor_buf));
+ for (unsigned i = 0; i < sizeof(xor_buf); ++i)
+ signature[i] ^= xor_buf[i];
+ done += sizeof(xor_buf);
+ }
+ my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux.
+ cv_ptr += sizeof(uint32_t);
+ }
+
+ // Write pdb_file_name
+ memcpy(cv_ptr, filename_ptr, filename_len + 1);
+ cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1);
+
+ mod.cv_record = cv.location();
+
+ MDLocationDescriptor ld;
+ if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld))
+ return false;
+ mod.module_name_rva = ld.rva;
+
+ list.CopyIndexAfterObject(j++, &mod, sizeof(mod));
+ }
+
+ return true;
+ }
+
+ bool WriteExceptionStream(MDRawDirectory* dirent) {
+ TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_);
+ if (!exc.Allocate())
+ return false;
+ my_memset(exc.get(), 0, sizeof(MDRawExceptionStream));
+
+ dirent->stream_type = MD_EXCEPTION_STREAM;
+ dirent->location = exc.location();
+
+ exc.get()->thread_id = crashing_tid_;
+ exc.get()->exception_record.exception_code = siginfo_->si_signo;
+ exc.get()->exception_record.exception_address =
+ (uintptr_t) siginfo_->si_addr;
+ exc.get()->thread_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;
+ }
+
+ private:
+#if defined(__i386)
+ uintptr_t GetStackPointer() {
+ return ucontext_->uc_mcontext.gregs[REG_ESP];
+ }
+#elif defined(__x86_64)
+ uintptr_t GetStackPointer() {
+ return ucontext_->uc_mcontext.gregs[REG_RSP];
+ }
+#else
+#error "This code has not been ported to your platform yet."
+#endif
+
+ void NullifyDirectoryEntry(MDRawDirectory* dirent) {
+ dirent->stream_type = 0;
+ dirent->location.data_size = 0;
+ dirent->location.rva = 0;
+ }
+
+ 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";
+ static const size_t vendor_id_name_length = sizeof(vendor_id_name) - 1;
+
+ struct CpuInfoEntry {
+ const char* info_name;
+ int value;
+ bool found;
+ } cpu_info_table[] = {
+ { "processor", -1, false },
+ { "model", 0, false },
+ { "stepping", 0, false },
+ { "cpuid level", 0, false },
+ };
+
+ // processor_architecture should always be set, do this first
+ sys_info->processor_architecture =
+#if defined(__i386)
+ MD_CPU_ARCHITECTURE_X86;
+#elif defined(__x86_64)
+ MD_CPU_ARCHITECTURE_AMD64;
+#else
+#error "Unknown CPU arch"
+#endif
+
+ const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0);
+ if (fd < 0)
+ return false;
+
+ {
+ PageAllocator allocator;
+ LineReader* const line_reader = new(allocator) LineReader(fd);
+ const char* line;
+ unsigned line_len;
+ while (line_reader->GetNextLine(&line, &line_len)) {
+ for (size_t i = 0;
+ i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
+ i++) {
+ CpuInfoEntry* entry = &cpu_info_table[i];
+ if (entry->found)
+ continue;
+ if (!strncmp(line, entry->info_name, strlen(entry->info_name))) {
+ const char* value = strchr(line, ':');
+ if (!value)
+ continue;
+
+ // the above strncmp only matches the prefix, it might be the wrong
+ // line. i.e. we matched "model name" instead of "model".
+ // check and make sure there is only spaces between the prefix and
+ // the colon.
+ const char* space_ptr = line + strlen(entry->info_name);
+ for (; space_ptr < value; space_ptr++) {
+ if (!isspace(*space_ptr)) {
+ break;
+ }
+ }
+ if (space_ptr != value)
+ continue;
+
+ sscanf(++value, " %d", &(entry->value));
+ entry->found = true;
+ }
+ }
+
+ // special case for vendor_id
+ if (!strncmp(line, vendor_id_name, vendor_id_name_length)) {
+ const char* value = strchr(line, ':');
+ if (!value)
+ goto popline;
+
+ // skip ':" and all the spaces that follows
+ do {
+ value++;
+ } while (isspace(*value));
+
+ if (*value) {
+ size_t length = strlen(value);
+ if (length == 0)
+ goto popline;
+ // we don't want the trailing newline
+ if (value[length - 1] == '\n')
+ length--;
+ // ensure we have space for the value
+ if (length < sizeof(vendor_id))
+ strncpy(vendor_id, value, length);
+ }
+ }
+
+popline:
+ line_reader->PopLine(line_len);
+ }
+ sys_close(fd);
+ }
+
+ // make sure we got everything we wanted
+ for (size_t i = 0;
+ i < sizeof(cpu_info_table) / sizeof(cpu_info_table[0]);
+ i++) {
+ if (!cpu_info_table[i].found) {
+ return false;
+ }
+ }
+ // /proc/cpuinfo contains cpu id, change it into number by adding one.
+ cpu_info_table[0].value++;
+
+ sys_info->number_of_processors = cpu_info_table[0].value;
+ sys_info->processor_level = cpu_info_table[3].value;
+ sys_info->processor_revision = cpu_info_table[1].value << 8 |
+ cpu_info_table[2].value;
+
+ if (vendor_id[0] != '\0') {
+ memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id,
+ sizeof(sys_info->cpu.x86_cpu_info.vendor_id));
+ }
+ return true;
+ }
+
+ 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 kMaxFileSize = 1024;
+ uint8_t* data = (uint8_t*) dumper_.allocator()->Alloc(kMaxFileSize);
+
+ size_t done = 0;
+ while (done < kMaxFileSize) {
+ ssize_t r;
+ do {
+ r = sys_read(fd, data + done, kMaxFileSize - done);
+ } while (r == -1 && errno == EINTR);
+
+ if (r < 1)
+ break;
+ done += r;
+ }
+ sys_close(fd);
+
+ if (!done)
+ return false;
+
+ UntypedMDRVA memory(&minidump_writer_);
+ if (!memory.Allocate(done))
+ return false;
+ memory.Copy(data, done);
+ *result = memory.location();
+ return true;
+ }
+
+ bool WriteOSInformation(MDRawSystemInfo* sys_info) {
+ sys_info->platform_id = MD_OS_LINUX;
+
+ 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 = strlen(separator);
+ size_t info_len = strlen(*cur_info);
+ if (info_len == 0)
+ continue;
+
+ if (space_left < info_len + (first_item ? 0 : separator_len))
+ break;
+
+ if (!first_item) {
+ strcat(buf, separator);
+ space_left -= separator_len;
+ }
+
+ first_item = false;
+ strcat(buf, *cur_info);
+ 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[80];
+ memcpy(buf, "/proc/", 6);
+ const unsigned pid_len = my_int_len(pid);
+ my_itos(buf + 6, pid, pid_len);
+ buf[6 + pid_len] = '/';
+ memcpy(buf + 6 + pid_len + 1, filename, my_strlen(filename) + 1);
+ return WriteFile(result, buf);
+ }
+
+ const char* const filename_; // output filename
+ const siginfo_t* const siginfo_; // from the signal handler (see sigaction)
+ const struct ucontext* const ucontext_; // also from the signal handler
+ const struct _libc_fpstate* const float_state_; // ditto
+ const pid_t crashing_tid_; // the process which actually crashed
+ LinuxDumper dumper_;
+ MinidumpFileWriter minidump_writer_;
+ MDLocationDescriptor crashing_thread_context_;
+};
+
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size) {
+ if (blob_size != sizeof(ExceptionHandler::CrashContext))
+ return false;
+ const ExceptionHandler::CrashContext* context =
+ reinterpret_cast<const ExceptionHandler::CrashContext*>(blob);
+ MinidumpWriter writer(filename, crashing_process, context);
+ if (!writer.Init())
+ return false;
+ return writer.Dump();
+}
+
+} // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/minidump_writer.h b/src/client/linux/minidump_writer/minidump_writer.h
new file mode 100644
index 00000000..579f68cd
--- /dev/null
+++ b/src/client/linux/minidump_writer/minidump_writer.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
+#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
+
+#include <stdint.h>
+#include <unistd.h>
+
+namespace google_breakpad {
+
+// Write a minidump to the filesystem. This function does not malloc nor use
+// libc functions which may. Thus, it can be used in contexts where the state
+// of the heap may be corrupt.
+// filename: the filename to write to. This is opened O_EXCL and fails if
+// open fails.
+// crashing_process: the pid of the crashing process. This must be trusted.
+// blob: a blob of data from the crashing process. See exception_handler.h
+// blob_size: the length of |blob|, in bytes
+//
+// Returns true iff successful.
+bool WriteMinidump(const char* filename, pid_t crashing_process,
+ const void* blob, size_t blob_size);
+
+} // namespace google_breakpad
+
+#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_
diff --git a/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
new file mode 100644
index 00000000..5ff336ce
--- /dev/null
+++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -0,0 +1,79 @@
+// 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 <unistd.h>
+#include <sys/syscall.h>
+
+#include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/minidump_writer.h"
+#include "breakpad_googletest_includes.h"
+
+using namespace google_breakpad;
+
+// This provides a wrapper around system calls which may be
+// interrupted by a signal and return EINTR. See man 7 signal.
+#define HANDLE_EINTR(x) ({ \
+ typeof(x) __eintr_result__; \
+ do { \
+ __eintr_result__ = x; \
+ } while (__eintr_result__ == -1 && errno == EINTR); \
+ __eintr_result__;\
+})
+
+namespace {
+typedef testing::Test MinidumpWriterTest;
+}
+
+TEST(MinidumpWriterTest, Setup) {
+ int fds[2];
+ ASSERT_NE(-1, pipe(fds));
+
+ const pid_t child = fork();
+ if (child == 0) {
+ close(fds[1]);
+ char b;
+ HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+ close(fds[0]);
+ syscall(__NR_exit);
+ }
+ close(fds[0]);
+
+ ExceptionHandler::CrashContext context;
+ memset(&context, 0, sizeof(context));
+
+ char templ[] = "/tmp/minidump-writer-unittest-XXXXXX";
+ mktemp(templ);
+ ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context)));
+ struct stat st;
+ ASSERT_EQ(stat(templ, &st), 0);
+ ASSERT_GT(st.st_size, 0u);
+ unlink(templ);
+
+ close(fds[1]);
+}