allow passing info about known memory mappings to MinidumpWriter and ExceptionHandler
r=thestig at http://breakpad.appspot.com/242001/show

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@741 4c0a9323-5329-0410-9bdc-e9ce6186880e
diff --git a/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
index 0a3b46f..5377aac 100644
--- a/src/client/linux/minidump_writer/minidump_writer_unittest.cc
+++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc
@@ -28,12 +28,17 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <unistd.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
 #include <sys/syscall.h>
 
+#include "breakpad_googletest_includes.h"
 #include "client/linux/handler/exception_handler.h"
+#include "client/linux/minidump_writer/linux_dumper.h"
 #include "client/linux/minidump_writer/minidump_writer.h"
 #include "common/linux/eintr_wrapper.h"
-#include "breakpad_googletest_includes.h"
+#include "common/linux/file_id.h"
+#include "google_breakpad/processor/minidump.h"
 
 using namespace google_breakpad;
 
@@ -43,6 +48,10 @@
 #define TEMPDIR "/data/local/tmp"
 #endif
 
+// Length of a formatted GUID string =
+// sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator)
+const int kGUIDStringSize = 37;
+
 namespace {
 typedef testing::Test MinidumpWriterTest;
 }
@@ -76,3 +85,307 @@
 
   close(fds[1]);
 }
+
+// Test that mapping info can be specified when writing a minidump,
+// and that it ends up in the module list of the minidump.
+TEST(MinidumpWriterTest, MappingInfo) {
+  int fds[2];
+  ASSERT_NE(-1, pipe(fds));
+
+  // These are defined here so the parent can use them to check the
+  // data from the minidump afterwards.
+  const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
+  const char* kMemoryName = "a fake module";
+  const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
+    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+  };
+  char module_identifier_buffer[kGUIDStringSize];
+  FileID::ConvertIdentifierToString(kModuleGUID,
+                                    module_identifier_buffer,
+                                    sizeof(module_identifier_buffer));
+  string module_identifier(module_identifier_buffer);
+  // Strip out dashes
+  size_t pos;
+  while ((pos = module_identifier.find('-')) != string::npos) {
+    module_identifier.erase(pos, 1);
+  }
+  // And append a zero, because module IDs include an "age" field
+  // which is always zero on Linux.
+  module_identifier += "0";
+  
+  // Get some memory.
+  char* memory =
+    reinterpret_cast<char*>(mmap(NULL,
+                                 kMemorySize,
+                                 PROT_READ | PROT_WRITE,
+                                 MAP_PRIVATE | MAP_ANON,
+                                 -1,
+                                 0));
+  const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
+  ASSERT_TRUE(memory);
+
+  const pid_t child = fork();
+  if (child == 0) {
+    close(fds[1]);
+    char b;
+    HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+    close(fds[0]);
+    syscall(__NR_exit);
+  }
+  close(fds[0]);
+
+  ExceptionHandler::CrashContext context;
+  memset(&context, 0, sizeof(context));
+  context.tid = 1;
+
+  char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+  mktemp(templ);
+
+  // Add information about the mapped memory.
+  MappingInfo info;
+  info.start_addr = kMemoryAddress;
+  info.size = kMemorySize;
+  info.offset = 0;
+  strcpy(info.name, kMemoryName);
+  
+  MappingList mappings;
+  MappingEntry mapping;
+  mapping.first = info;
+  memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
+  mappings.push_back(mapping);
+  ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings));
+
+  // Read the minidump. Load the module list, and ensure that
+  // the mmap'ed |memory| is listed with the given module name
+  // and debug ID.
+  Minidump minidump(templ);
+  ASSERT_TRUE(minidump.Read());
+
+  MinidumpModuleList* module_list = minidump.GetModuleList();
+  ASSERT_TRUE(module_list);
+  const MinidumpModule* module =
+    module_list->GetModuleForAddress(kMemoryAddress);
+  ASSERT_TRUE(module);
+
+  EXPECT_EQ(kMemoryAddress, module->base_address());
+  EXPECT_EQ(kMemorySize, module->size());
+  EXPECT_EQ(kMemoryName, module->code_file());
+  EXPECT_EQ(module_identifier, module->debug_identifier());
+
+  unlink(templ);
+  close(fds[1]);
+}
+
+// Test that mapping info can be specified, and that it overrides
+// existing mappings that are wholly contained within the specified
+// range.
+TEST(MinidumpWriterTest, MappingInfoContained) {
+  int fds[2];
+  ASSERT_NE(-1, pipe(fds));
+
+  // These are defined here so the parent can use them to check the
+  // data from the minidump afterwards.
+  const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE);
+  const char* kMemoryName = "a fake module";
+  const u_int8_t kModuleGUID[sizeof(MDGUID)] = {
+    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
+  };
+  char module_identifier_buffer[kGUIDStringSize];
+  FileID::ConvertIdentifierToString(kModuleGUID,
+                                    module_identifier_buffer,
+                                    sizeof(module_identifier_buffer));
+  string module_identifier(module_identifier_buffer);
+  // Strip out dashes
+  size_t pos;
+  while ((pos = module_identifier.find('-')) != string::npos) {
+    module_identifier.erase(pos, 1);
+  }
+  // And append a zero, because module IDs include an "age" field
+  // which is always zero on Linux.
+  module_identifier += "0";
+  
+  // mmap a file
+  char tempfile[] = TEMPDIR "/minidump-writer-unittest-temp-XXXXXX";
+  mktemp(tempfile);
+  int fd = open(tempfile, O_RDWR | O_CREAT, 0);
+  ASSERT_NE(-1, fd);
+  unlink(tempfile);
+  // fill with zeros
+  char buffer[kMemorySize];
+  memset(buffer, 0, kMemorySize);
+  ASSERT_EQ(kMemorySize, write(fd, buffer, kMemorySize));
+  lseek(fd, 0, SEEK_SET);
+
+  char* memory =
+    reinterpret_cast<char*>(mmap(NULL,
+                                 kMemorySize,
+                                 PROT_READ | PROT_WRITE,
+                                 MAP_PRIVATE,
+                                 fd,
+                                 0));
+  const u_int64_t kMemoryAddress = reinterpret_cast<u_int64_t>(memory);
+  ASSERT_TRUE(memory);
+  close(fd);
+
+  const pid_t child = fork();
+  if (child == 0) {
+    close(fds[1]);
+    char b;
+    HANDLE_EINTR(read(fds[0], &b, sizeof(b)));
+    close(fds[0]);
+    syscall(__NR_exit);
+  }
+  close(fds[0]);
+
+  ExceptionHandler::CrashContext context;
+  memset(&context, 0, sizeof(context));
+  context.tid = 1;
+
+  char dumpfile[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+  mktemp(dumpfile);
+
+  // Add information about the mapped memory. Report it as being larger than
+  // it actually is.
+  MappingInfo info;
+  info.start_addr = kMemoryAddress - kMemorySize;
+  info.size = kMemorySize * 3;
+  info.offset = 0;
+  strcpy(info.name, kMemoryName);
+
+  MappingList mappings;
+  MappingEntry mapping;
+  mapping.first = info;
+  memcpy(mapping.second, kModuleGUID, sizeof(MDGUID));
+  mappings.push_back(mapping);
+  ASSERT_TRUE(WriteMinidump(dumpfile, child, &context, sizeof(context), mappings));
+
+  // Read the minidump. Load the module list, and ensure that
+  // the mmap'ed |memory| is listed with the given module name
+  // and debug ID.
+  Minidump minidump(dumpfile);
+  ASSERT_TRUE(minidump.Read());
+
+  MinidumpModuleList* module_list = minidump.GetModuleList();
+  ASSERT_TRUE(module_list);
+  const MinidumpModule* module =
+    module_list->GetModuleForAddress(kMemoryAddress);
+  ASSERT_TRUE(module);
+
+  EXPECT_EQ(info.start_addr, module->base_address());
+  EXPECT_EQ(info.size, module->size());
+  EXPECT_EQ(kMemoryName, module->code_file());
+  EXPECT_EQ(module_identifier, module->debug_identifier());
+
+  unlink(dumpfile);
+  close(fds[1]);
+}
+
+TEST(MinidumpWriterTest, DeletedBinary) {
+  static const int kNumberOfThreadsInHelperProgram = 1;
+  char kNumberOfThreadsArgument[2];
+  sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
+
+  // Locate helper binary next to the current binary.
+  char self_path[PATH_MAX];
+  if (readlink("/proc/self/exe", self_path, sizeof(self_path) - 1) == -1) {
+    FAIL() << "readlink failed: " << strerror(errno);
+    exit(1);
+  }
+  string helper_path(self_path);
+  size_t pos = helper_path.rfind('/');
+  if (pos == string::npos) {
+    FAIL() << "no trailing slash in path: " << helper_path;
+    exit(1);
+  }
+  helper_path.erase(pos + 1);
+  helper_path += "linux_dumper_unittest_helper";
+
+  // Copy binary to a temp file.
+  char binpath[] = TEMPDIR "/linux-dumper-unittest-helper-XXXXXX";
+  mktemp(binpath);
+  char cmdline[2 * PATH_MAX];
+  sprintf(cmdline, "/bin/cp \"%s\" \"%s\"", helper_path.c_str(), binpath);
+  ASSERT_EQ(0, system(cmdline));
+  ASSERT_EQ(0, chmod(binpath, 0755));
+
+  int fds[2];
+  ASSERT_NE(-1, pipe(fds));
+
+  pid_t child_pid = fork();
+  if (child_pid == 0) {
+    // In child process.
+    close(fds[0]);
+
+    // Pass the pipe fd and the number of threads as arguments.
+    char pipe_fd_string[8];
+    sprintf(pipe_fd_string, "%d", fds[1]);
+    execl(binpath,
+          binpath,
+          pipe_fd_string,
+          kNumberOfThreadsArgument,
+          NULL);
+  }
+  close(fds[1]);
+  // Wait for the child process to signal that it's ready.
+  struct pollfd pfd;
+  memset(&pfd, 0, sizeof(pfd));
+  pfd.fd = fds[0];
+  pfd.events = POLLIN | POLLERR;
+
+  const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
+  ASSERT_EQ(1, r);
+  ASSERT_TRUE(pfd.revents & POLLIN);
+  uint8_t junk;
+  read(fds[0], &junk, sizeof(junk));
+  close(fds[0]);
+
+  // Child is ready now.
+  // Unlink the test binary.
+  unlink(binpath);
+
+  ExceptionHandler::CrashContext context;
+  memset(&context, 0, sizeof(context));
+
+  char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX";
+  mktemp(templ);
+  // Set a non-zero tid to avoid tripping asserts.
+  context.tid = 1;
+  ASSERT_TRUE(WriteMinidump(templ, child_pid, &context, sizeof(context)));
+  kill(child_pid, SIGKILL);
+
+  struct stat st;
+  ASSERT_EQ(stat(templ, &st), 0);
+  ASSERT_GT(st.st_size, 0u);
+
+
+
+  Minidump minidump(templ);
+  ASSERT_TRUE(minidump.Read());
+
+  // Check that the main module filename is correct.
+  MinidumpModuleList* module_list = minidump.GetModuleList();
+  ASSERT_TRUE(module_list);
+  const MinidumpModule* module = module_list->GetMainModule();
+  EXPECT_STREQ(binpath, module->code_file().c_str());
+  // Check that the file ID is correct.
+  FileID fileid(helper_path.c_str());
+  uint8_t identifier[sizeof(MDGUID)];
+  EXPECT_TRUE(fileid.ElfFileIdentifier(identifier));
+  char identifier_string[kGUIDStringSize];
+  FileID::ConvertIdentifierToString(identifier,
+                                    identifier_string,
+                                    kGUIDStringSize);
+  string module_identifier(identifier_string);
+  // Strip out dashes
+  while ((pos = module_identifier.find('-')) != string::npos) {
+    module_identifier.erase(pos, 1);
+  }
+  // And append a zero, because module IDs include an "age" field
+  // which is always zero on Linux.
+  module_identifier += "0";
+  EXPECT_EQ(module_identifier, module->debug_identifier());
+
+  unlink(templ);
+}