>From 4c2a0276abdba2589bc6a93988caebf54615ae43 Mon Sep 17 00:00:00 2001
From: Mark Wielaard <mjw@redhat.com>
Date: Tue, 4 Mar 2014 11:27:15 +0100
Subject: [PATCH] libdwfl: dwfl_linux_proc_find_elf use elf_from_remote_memory for (deleted).

If a module has a "(deleted)" main ELF file, then try to read it from
remote memory if the Dwfl has process state attached by reusing the ptrace
mechanism from linux-pid-attach.

Signed-off-by: Mark Wielaard <mjw@redhat.com>
---
 libdwfl/ChangeLog          |   19 ++++++++++++++++
 libdwfl/dwfl_frame.c       |   16 +++++++++----
 libdwfl/libdwflP.h         |   36 ++++++++++++++++++++++++++++++-
 libdwfl/linux-pid-attach.c |   51 +++++++++++++++++++++++++-------------------
 libdwfl/linux-proc-maps.c  |   51 ++++++++++++++++++++++++++++++++++---------
 5 files changed, 134 insertions(+), 39 deletions(-)

diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog
index 8fcdc3b..40b3cf1 100644
--- a/libdwfl/ChangeLog
+++ b/libdwfl/ChangeLog
@@ -1,3 +1,22 @@
+2014-03-04  Mark Wielaard  <mjw@redhat.com>
+
+	* libdwflP.h (struct pid_arg): Moved here from linux-pid-attach.c.
+	(__libdwfl_get_pid_arg): New internal function declaration.
+	(__libdwfl_ptrace_attach): Likewise.
+	(__libdwfl_ptrace_detach): Likewise.
+	* dwfl_frame.c (dwfl_attach_state): Add "(deleted)" files to the
+	special exception modules that cannot be checked at this point.
+	* linux-pid-attach.c (struct pid_arg): Moved to libdwflP.h
+	(ptrace_attach): Renamed to...
+	(__libdwfl_ptrace_attach): New internal function.
+	(__libdwfl_ptrace_detach): Likewise. Extracted from ...
+	(pid_thread_detach): Call __libdwfl_ptrace_detach now.
+	(__libdwfl_get_pid_arg): New internal function.
+	* linux-proc-maps.c (dwfl_linux_proc_find_elf): Check if special
+	module name contains "(deleted)" and dwfl_pid gives an attached
+	pid. If pid is set and try to (re)use ptrace attach state of
+	process before reading memory.
+
 2014-03-03  Mark Wielaard  <mjw@redhat.com>
 
 	* elf-from-memory.c (elf_from_remote_memory): Keep track of
diff --git a/libdwfl/dwfl_frame.c b/libdwfl/dwfl_frame.c
index e45cf14..fd0b9ae 100644
--- a/libdwfl/dwfl_frame.c
+++ b/libdwfl/dwfl_frame.c
@@ -157,11 +157,17 @@ dwfl_attach_state (Dwfl *dwfl, Elf *elf, pid_t pid,
       ebl = NULL;
       for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; mod = mod->next)
 	{
-	  /* Reading of the vDSO module may fail as /proc/PID/mem is unreadable
-	     without PTRACE_ATTACH and we may not be PTRACE_ATTACH-ed now.
-	     MOD would not be re-read later to unwind it when we are already
-	     PTRACE_ATTACH-ed to PID.  */
-	  if (strncmp (mod->name, "[vdso: ", 7) == 0)
+	  /* Reading of the vDSO or (deleted) modules may fail as
+	     /proc/PID/mem is unreadable without PTRACE_ATTACH and
+	     we may not be PTRACE_ATTACH-ed now.  MOD would not be
+	     re-read later to unwind it when we are already
+	     PTRACE_ATTACH-ed to PID.  This happens when this function
+	     is called from dwfl_linux_proc_attach with elf == NULL.
+	     __libdwfl_module_getebl will call __libdwfl_getelf which
+	     will call the find_elf callback.  */
+	  if (strncmp (mod->name, "[vdso: ", 7) == 0
+	      || strcmp (strrchr (mod->name, ' ') ?: "",
+			 " (deleted)") == 0)
 	    continue;
 	  Dwfl_Error error = __libdwfl_module_getebl (mod);
 	  if (error != DWFL_E_NOERROR)
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 710e699..0b033fe 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -1,5 +1,5 @@
 /* Internal definitions for libdwfl.
-   Copyright (C) 2005-2013 Red Hat, Inc.
+   Copyright (C) 2005-2014 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -35,6 +35,7 @@
 #include <libdwfl.h>
 #include <libebl.h>
 #include <assert.h>
+#include <dirent.h>
 #include <errno.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -387,6 +388,39 @@ struct dwfl_arange
 };
 
 
+/* Structure used for keeping track of ptrace attaching a thread.
+   Shared by linux-pid-attach and linux-proc-maps.  If it has been setup
+   then get the instance through __libdwfl_get_pid_arg.  */
+struct pid_arg
+{
+  DIR *dir;
+  /* It is 0 if not used.  */
+  pid_t tid_attached;
+  /* Valid only if TID_ATTACHED is not zero.  */
+  bool tid_was_stopped;
+  /* True if threads are ptrace stopped by caller.  */
+  bool assume_ptrace_stopped;
+};
+
+/* If DWfl is not NULL and a Dwfl_Process has been setup that has
+   Dwfl_Thread_Callbacks set to pid_thread_callbacks, then return the
+   callbacks_arg, which will be a struct pid_arg.  Otherwise
+   returns NULL.  */
+extern struct pid_arg *__libdwfl_get_pid_arg (Dwfl *dwfl)
+  internal_function;
+
+/* Makes sure the given tid is attached. On success returns true and
+   sets tid_was_stopped.  */
+extern bool __libdwfl_ptrace_attach (pid_t tid, bool *tid_was_stoppedp)
+  internal_function;
+
+/* Detaches a tid that was attached through
+   __libdwfl_ptrace_attach. Must be given the tid_was_stopped as set
+   by __libdwfl_ptrace_attach.  */
+extern void __libdwfl_ptrace_detach (pid_t tid, bool tid_was_stopped)
+  internal_function;
+
+
 /* Internal wrapper for old dwfl_module_getsym and new dwfl_module_getsym_info.
    adjust_st_value set to true returns adjusted SYM st_value, set to false
    it will not adjust SYM at all, but does match against resolved *ADDR. */
diff --git a/libdwfl/linux-pid-attach.c b/libdwfl/linux-pid-attach.c
index 58d6942..0ec7324 100644
--- a/libdwfl/linux-pid-attach.c
+++ b/libdwfl/linux-pid-attach.c
@@ -37,16 +37,6 @@
 # define MAX(a, b) ((a) > (b) ? (a) : (b))
 #endif
 
-struct pid_arg
-{
-  DIR *dir;
-  /* It is 0 if not used.  */
-  pid_t tid_attached;
-  /* Valid only if TID_ATTACHED is not zero.  */
-  bool tid_was_stopped;
-  /* True if threads are ptrace stopped by caller.  */
-  bool assume_ptrace_stopped;
-};
 
 static bool
 linux_proc_pid_is_stopped (pid_t pid)
@@ -72,8 +62,9 @@ linux_proc_pid_is_stopped (pid_t pid)
   return retval;
 }
 
-static bool
-ptrace_attach (pid_t tid, bool *tid_was_stoppedp)
+bool
+internal_function
+__libdwfl_ptrace_attach (pid_t tid, bool *tid_was_stoppedp)
 {
   if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0)
     {
@@ -242,7 +233,7 @@ pid_set_initial_registers (Dwfl_Thread *thread, void *thread_arg)
   assert (pid_arg->tid_attached == 0);
   pid_t tid = INTUSE(dwfl_thread_tid) (thread);
   if (! pid_arg->assume_ptrace_stopped
-      && ! ptrace_attach (tid, &pid_arg->tid_was_stopped))
+      && ! __libdwfl_ptrace_attach (tid, &pid_arg->tid_was_stopped))
     return false;
   pid_arg->tid_attached = tid;
   Dwfl_Process *process = thread->process;
@@ -259,6 +250,19 @@ pid_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg)
   free (pid_arg);
 }
 
+void
+internal_function
+__libdwfl_ptrace_detach (pid_t tid, bool tid_was_stopped)
+{
+  /* This handling is needed only on older Linux kernels such as
+     2.6.32-358.23.2.el6.ppc64.  Later kernels such as
+     3.11.7-200.fc19.x86_64 remember the T (stopped) state
+     themselves and no longer need to pass SIGSTOP during
+     PTRACE_DETACH.  */
+  ptrace (PTRACE_DETACH, tid, NULL,
+	  (void *) (intptr_t) (tid_was_stopped ? SIGSTOP : 0));
+}
+
 static void
 pid_thread_detach (Dwfl_Thread *thread, void *thread_arg)
 {
@@ -267,15 +271,7 @@ pid_thread_detach (Dwfl_Thread *thread, void *thread_arg)
   assert (pid_arg->tid_attached == tid);
   pid_arg->tid_attached = 0;
   if (! pid_arg->assume_ptrace_stopped)
-    {
-      /* This handling is needed only on older Linux kernels such as
-         2.6.32-358.23.2.el6.ppc64.  Later kernels such as
-         3.11.7-200.fc19.x86_64 remember the T (stopped) state
-         themselves and no longer need to pass SIGSTOP during
-         PTRACE_DETACH.  */
-      ptrace (PTRACE_DETACH, tid, NULL,
-	      (void *) (intptr_t) (pid_arg->tid_was_stopped ? SIGSTOP : 0));
-    }
+    __libdwfl_ptrace_detach (tid, pid_arg->tid_was_stopped);
 }
 
 static const Dwfl_Thread_Callbacks pid_thread_callbacks =
@@ -347,3 +343,14 @@ dwfl_linux_proc_attach (Dwfl *dwfl, pid_t pid, bool assume_ptrace_stopped)
   return 0;
 }
 INTDEF (dwfl_linux_proc_attach)
+
+struct pid_arg *
+internal_function
+__libdwfl_get_pid_arg (Dwfl *dwfl)
+{
+  if (dwfl != NULL && dwfl->process != NULL
+      && dwfl->process->callbacks == &pid_thread_callbacks)
+    return (struct pid_arg *) dwfl->process->callbacks_arg;
+
+  return NULL;
+}
diff --git a/libdwfl/linux-proc-maps.c b/libdwfl/linux-proc-maps.c
index 3384403..8d18274 100644
--- a/libdwfl/linux-proc-maps.c
+++ b/libdwfl/linux-proc-maps.c
@@ -339,34 +339,60 @@ dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
 			  const char *module_name, Dwarf_Addr base,
 			  char **file_name, Elf **elfp)
 {
+  int pid = -1;
   if (module_name[0] == '/')
     {
       /* When this callback is used together with dwfl_linux_proc_report
 	 then we might see mappings of special character devices.  Make
 	 sure we only open and return regular files.  Special devices
-	 might hang on open or read.  */
+	 might hang on open or read.  (deleted) files are super special.
+	 The image might come from memory if we are attached.  */
       struct stat sb;
       if (stat (module_name, &sb) == -1 || (sb.st_mode & S_IFMT) != S_IFREG)
-	return -1;
+	{
+	  if (strcmp (strrchr (module_name, ' ') ?: "", " (deleted)") == 0)
+	    pid = INTUSE(dwfl_pid) (mod->dwfl);
+	  else
+	    return -1;
+	}
 
-      int fd = open64 (module_name, O_RDONLY);
-      if (fd >= 0)
+      if (pid == -1)
 	{
-	  *file_name = strdup (module_name);
-	  if (*file_name == NULL)
+	  int fd = open64 (module_name, O_RDONLY);
+	  if (fd >= 0)
 	    {
-	      close (fd);
-	      return ENOMEM;
+	      *file_name = strdup (module_name);
+	      if (*file_name == NULL)
+		{
+		  close (fd);
+		  return ENOMEM;
+		}
 	    }
+	  return fd;
 	}
-      return fd;
     }
 
-  int pid;
-  if (sscanf (module_name, "[vdso: %d]", &pid) == 1)
+  if (pid != -1 || sscanf (module_name, "[vdso: %d]", &pid) == 1)
     {
       /* Special case for in-memory ELF image.  */
 
+      bool detach = false;
+      bool tid_was_stopped = false;
+      struct pid_arg *pid_arg = __libdwfl_get_pid_arg (mod->dwfl);
+      if (pid_arg != NULL && ! pid_arg->assume_ptrace_stopped)
+	{
+	  pid_t tid = pid_arg->tid_attached;
+	  if (tid != 0)
+	    {
+	      /* If the pid already is attached we are fine, otherwise
+		 just read through the thread that is attached.  */
+	      if (tid != pid)
+		pid = tid;
+	    }
+	  else
+	    detach = __libdwfl_ptrace_attach (pid, &tid_was_stopped);
+	}
+
       char *fname;
       if (asprintf (&fname, PROCMEMFMT, pid) < 0)
 	return -1;
@@ -381,6 +407,9 @@ dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)),
 
       close (fd);
 
+      if (detach)
+	__libdwfl_ptrace_detach (pid, tid_was_stopped);
+
       *file_name = NULL;
       return -1;
     }
-- 
1.7.1

