Add ConfigPath detection mechanism on OSX (#129)
authorSung Yoon Whang <suwhang@microsoft.com>
Tue, 26 Feb 2019 19:38:36 +0000 (11:38 -0800)
committerGitHub <noreply@github.com>
Tue, 26 Feb 2019 19:38:36 +0000 (11:38 -0800)
* Add ConfigPath detection mechanism on OSX

* remove debugging console writeline

* Some cleanup + comments about proc_pidpath

* Address PR comments

src/Tools/dotnet-collect/ConfigPathDetector.cs

index 05de154a638e2fe13b35722961ed58b88bc72a7d..619fc9465622b13d9317fb039ed113cdcf45c540 100644 (file)
@@ -28,9 +28,71 @@ namespace Microsoft.Diagnostics.Tools.Collect
             {
                 return Linux.TryDetectConfigPath(processId);
             }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                return OSX.TryDetectConfigPath(processId);
+            }
             return null;
         }
 
+        private static class OSX
+        {
+            // This is defined in proc_info.h (https://opensource.apple.com/source/xnu/xnu-1228/bsd/sys/proc_info.h)
+            private const int PROC_PIDPATHINFO_MAXSIZE = 1024 * 4;
+
+            /// <summary>
+            /// Gets the full path to the executable file identified by the specified PID
+            /// </summary>
+            /// <param name="pid">The PID of the running process</param>
+            /// <param name="buffer">A pointer to an allocated block of memory that will be filled with the process path</param>
+            /// <param name="bufferSize">The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE</param>
+            /// <returns>Returns the length of the path returned on success</returns>
+            [DllImport("libproc.dylib", SetLastError = true)]
+            private static extern unsafe int proc_pidpath(
+                int pid, 
+                byte* buffer, 
+                uint bufferSize);
+
+            /// <summary>
+            /// Gets the full path to the executable file identified by the specified PID
+            /// </summary>
+            /// <param name="pid">The PID of the running process</param>
+            /// <returns>Returns the full path to the process executable</returns>
+            internal static unsafe string proc_pidpath(int pid)
+            {
+                // The path is a fixed buffer size, so use that and trim it after
+                int result = 0;
+                byte* pBuffer = stackalloc byte[PROC_PIDPATHINFO_MAXSIZE];
+
+                // WARNING - Despite its name, don't try to pass in a smaller size than specified by PROC_PIDPATHINFO_MAXSIZE.
+                // For some reason libproc returns -1 if you specify something that's NOT EQUAL to PROC_PIDPATHINFO_MAXSIZE
+                // even if you declare your buffer to be smaller/larger than this size. 
+                result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
+                if (result <= 0)
+                {
+                    throw new ArgumentException("Could not find procpath using libproc.");
+                }
+
+                // OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
+                return System.Text.Encoding.UTF8.GetString(pBuffer, result);
+            }
+
+            public static string TryDetectConfigPath(int processId)
+            {
+                try
+                {
+                    var path = proc_pidpath(processId);
+                    var candidateDir = Path.GetDirectoryName(path);
+                    var candidateName = Path.GetFileNameWithoutExtension(path);
+                    return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
+                }
+                catch (ArgumentException)
+                {
+                    return null;  // The pinvoke above may fail - return null in that case to handle error gracefully.
+                }
+            }
+        }
+
         private static class Linux
         {
             public static string TryDetectConfigPath(int processId)
@@ -74,10 +136,9 @@ namespace Microsoft.Diagnostics.Tools.Collect
                             return Path.Combine(candidateDir, $"{candidateName}.eventpipeconfig");
                         }
                     }
-                    catch (Exception)
-                    {
-                        // Suppress exception and just try the next entry.
-                    }
+                    catch (ArgumentNullException) { return null; }
+                    catch (InvalidDataException) { return null; }
+                    catch (InvalidOperationException) { return null; }
                 }
                 return null;
             }