Linux: Attempt to generate an ELF identifier for deleted running binaries.
Review URL: http://breakpad.appspot.com/228001

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@733 4c0a9323-5329-0410-9bdc-e9ce6186880e
diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc
index 578b788..4885136 100644
--- a/src/client/linux/minidump_writer/linux_dumper.cc
+++ b/src/client/linux/minidump_writer/linux_dumper.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009, Google Inc.
+// Copyright (c) 2010, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -36,7 +36,6 @@
 
 #include <asm/ptrace.h>
 #include <assert.h>
-#include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
@@ -47,7 +46,6 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <sys/types.h>
 #include <sys/ptrace.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -61,6 +59,7 @@
 #include "third_party/lss/linux_syscall_support.h"
 
 static const char kMappedFileUnsafePrefix[] = "/dev/";
+static const char kDeletedSuffix[] = " (deleted)";
 
 // Suspend a thread by attaching to it.
 static bool SuspendThread(pid_t pid) {
@@ -200,7 +199,7 @@
   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);
+  path[total_length] = '\0';
 }
 
 bool
@@ -209,11 +208,20 @@
 {
   assert(mapping_id < mappings_.size());
   my_memset(identifier, 0, sizeof(MDGUID));
-  const MappingInfo* mapping = mappings_[mapping_id];
-  if (IsMappedFileOpenUnsafe(mapping)) {
+  MappingInfo* mapping = mappings_[mapping_id];
+  if (IsMappedFileOpenUnsafe(mapping))
     return false;
-  }
-  int fd = sys_open(mapping->name, O_RDONLY, 0);
+
+  char filename[NAME_MAX];
+  size_t filename_len = my_strlen(mapping->name);
+  assert(filename_len < NAME_MAX);
+  if (filename_len >= NAME_MAX)
+    return false;
+  memcpy(filename, mapping->name, filename_len);
+  filename[filename_len] = '\0';
+  bool filename_modified = HandleDeletedFileInMapping(filename);
+
+  int fd = sys_open(filename, O_RDONLY, 0);
   if (fd < 0)
     return false;
   struct kernel_stat st;
@@ -231,6 +239,8 @@
 
   bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
   sys_munmap(base, st.st_size);
+  if (success && filename_modified)
+    mapping->name[filename_len - sizeof(kDeletedSuffix) + 1] = '\0';
   return success;
 }
 
@@ -428,11 +438,8 @@
 #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;
+  return GetStackInfo(&info->stack, &info->stack_len,
+                      (uintptr_t) stack_pointer);
 }
 
 // Get information about the stack, given the stack pointer. We don't try to
@@ -447,7 +454,7 @@
       reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
 
   // The number of bytes of stack which we try to capture.
-  static ptrdiff_t kStackToCapture = 32 * 1024;
+  static const ptrdiff_t kStackToCapture = 32 * 1024;
 
   const MappingInfo* mapping = FindMapping(stack_pointer);
   if (!mapping)
@@ -493,4 +500,42 @@
   return NULL;
 }
 
+bool LinuxDumper::HandleDeletedFileInMapping(char* path) {
+  static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1;
+
+  // Check for ' (deleted)' in |path|.
+  // |path| has to be at least as long as "/x (deleted)".
+  const size_t path_len = my_strlen(path);
+  if (path_len < kDeletedSuffixLen + 2)
+    return false;
+  if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix,
+                 kDeletedSuffixLen) != 0) {
+    return false;
+  }
+
+  // Check |path| against the /proc/pid/exe 'symlink'.
+  char exe_link[NAME_MAX];
+  char new_path[NAME_MAX];
+  BuildProcPath(exe_link, pid_, "exe");
+  ssize_t new_path_len = sys_readlink(exe_link, new_path, NAME_MAX);
+  if (new_path_len <= 0 || new_path_len == NAME_MAX)
+    return false;
+  new_path[new_path_len] = '\0';
+  if (my_strcmp(path, new_path) != 0)
+    return false;
+
+  // Check to see if someone actually named their executable 'foo (deleted)'.
+  struct kernel_stat exe_stat;
+  struct kernel_stat new_path_stat;
+  if (sys_stat(exe_link, &exe_stat) == 0 &&
+      sys_stat(new_path, &new_path_stat) == 0 &&
+      exe_stat.st_dev == new_path_stat.st_dev &&
+      exe_stat.st_ino == new_path_stat.st_ino) {
+    return false;
+  }
+
+  memcpy(path, exe_link, NAME_MAX);
+  return true;
+}
+
 }  // namespace google_breakpad
diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h
index 71d1188..16b15f3 100644
--- a/src/client/linux/minidump_writer/linux_dumper.h
+++ b/src/client/linux/minidump_writer/linux_dumper.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009, Google Inc.
+// Copyright (c) 2010, Google Inc.
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without
@@ -165,6 +165,17 @@
   bool EnumerateMappings(wasteful_vector<MappingInfo*>* result) const;
   bool EnumerateThreads(wasteful_vector<pid_t>* result) const;
 
+  // For the case where a running program has been deleted, it'll show up in
+  // /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then
+  // see if '/path/to/program (deleted)' matches /proc/pid/exe and return
+  // /proc/pid/exe in |path| so ELF identifier generation works correctly. This
+  // also checks to see if '/path/to/program (deleted)' exists, so it does not
+  // get fooled by a poorly named binary.
+  // For programs that don't end with ' (deleted)', this is a no-op.
+  // This assumes |path| is a buffer with length NAME_MAX.
+  // Returns true if |path| is modified.
+  bool HandleDeletedFileInMapping(char* path);
+
   const pid_t pid_;
 
   mutable PageAllocator allocator_;