From 23a6dc977499ff48351b1e303cdd8bf660e774b5 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Tue, 8 Dec 2020 11:37:00 +0200 Subject: [PATCH 01/10] Aggregate adjacent memory sizes regardless of r+x When adjacent memory ranges of same module differ by permission, the line should not be skipped due to the lack of readability/executability flags. --- .../Interop.ProcFsStat.ParseMapModules.cs | 134 ++++++++++++++++++ .../Linux/procfs/Interop.ProcFsStat.cs | 87 ------------ .../src/System.Diagnostics.Process.csproj | 2 + .../Diagnostics/ProcessManager.Linux.cs | 35 +---- 4 files changed, 137 insertions(+), 121 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs new file mode 100644 index 00000000000000..6dfb0521e9764a --- /dev/null +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Text; + +internal static partial class Interop +{ + internal static partial class procfs + { + private const string MapsFileName = "/maps"; + + private static string GetMapsFilePathForProcess(int pid) + { + return RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; + } + + private static (long Start, int Size) TryParseAddressRange(string s, ref int start, ref int end) + { + int pos = s.IndexOf('-', start, end - start); + if (pos > 0) + { + if (long.TryParse(s.AsSpan(start, pos), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long startingAddress) && + long.TryParse(s.AsSpan(pos + 1, end - (pos + 1)), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long endingAddress)) + { + return (startingAddress, (int)(endingAddress - startingAddress)); + } + } + + return default; + } + + private static bool HasReadAndExecFlags(string s, ref int start, ref int end) + { + bool sawRead = false, sawExec = false; + for (int i = start; i < end; i++) + { + if (s[i] == 'r') + sawRead = true; + else if (s[i] == 'x') + sawExec = true; + } + + return sawRead & sawExec; + } + + internal static ProcessModuleCollection? ParseMapsModules(int pid) + { + try + { + return ParseMapsModulesCore(File.ReadLines(GetMapsFilePathForProcess(pid))); + } + catch (IOException) { } + catch (UnauthorizedAccessException) { } + + return null; + } + + private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable lines) + { + Debug.Assert(lines != null); + + ProcessModule? previous = null; + ProcessModuleCollection modules = new(capacity: 0); + + foreach (string line in lines) + { + // Use a StringParser to avoid string.Split costs + var parser = new StringParser(line, separator: ' ', skipEmpty: true); + + // Parse the address start and size + (long Start, int Size) addressRange = parser.ParseRaw(TryParseAddressRange); + + if (addressRange == default) + { + continue; + } + + // Parse the permissions + bool hasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); + + // Skip past the offset, dev, and inode fields + parser.MoveNext(); + parser.MoveNext(); + parser.MoveNext(); + + // Parse the pathname + if (!parser.MoveNext()) + { + continue; + } + + string pathname = parser.ExtractCurrentToEnd(); + bool isContinuation = previous?.FileName == pathname && (long)previous.BaseAddress + previous.ModuleMemorySize == addressRange.Start; + + if (isContinuation) + { + previous!.ModuleMemorySize += addressRange.Size; + continue; + } + + // we only care about entries with 'r' and 'x' set. + if (!hasReadAndExecFlags) + { + continue; + } + + var module = new ProcessModule + { + FileName = pathname, + ModuleName = Path.GetFileName(pathname), + ModuleMemorySize = addressRange.Size, + EntryPointAddress = IntPtr.Zero // unknown + }; + + // on 32-bit platforms, it throws System.OverflowException without the void* cast. + unsafe + { + module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); + } + + modules.Add(module); + previous = module; + } + + return modules; + } + } +} diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs index 45d484eeac6b6c..0ca179b736eca8 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; @@ -17,7 +16,6 @@ internal static partial class procfs private const string ExeFileName = "/exe"; private const string CmdLineFileName = "/cmdline"; private const string StatFileName = "/stat"; - private const string MapsFileName = "/maps"; private const string FileDescriptorDirectoryName = "/fd/"; private const string TaskDirectoryName = "/task/"; @@ -78,12 +76,6 @@ internal struct ParsedStat //internal long cguest_time; } - internal struct ParsedMapsModule - { - internal string FileName; - internal KeyValuePair AddressRange; - } - internal static string GetExeFilePathForProcess(int pid) { return RootPath + pid.ToString(CultureInfo.InvariantCulture) + ExeFileName; @@ -99,11 +91,6 @@ internal static string GetStatFilePathForProcess(int pid) return RootPath + pid.ToString(CultureInfo.InvariantCulture) + StatFileName; } - internal static string GetMapsFilePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; - } - internal static string GetTaskDirectoryPathForProcess(int pid) { return RootPath + pid.ToString(CultureInfo.InvariantCulture) + TaskDirectoryName; @@ -114,80 +101,6 @@ internal static string GetFileDescriptorDirectoryPathForProcess(int pid) return RootPath + pid.ToString(CultureInfo.InvariantCulture) + FileDescriptorDirectoryName; } - internal static IEnumerable ParseMapsModules(int pid) - { - try - { - return ParseMapsModulesCore(File.ReadLines(GetMapsFilePathForProcess(pid))); - } - catch (IOException) { } - catch (UnauthorizedAccessException) { } - - return Array.Empty(); - } - - private static IEnumerable ParseMapsModulesCore(IEnumerable lines) - { - Debug.Assert(lines != null); - - // Parse each line from the maps file into a ParsedMapsModule result - foreach (string line in lines) - { - // Use a StringParser to avoid string.Split costs - var parser = new StringParser(line, separator: ' ', skipEmpty: true); - - // Parse the address range - KeyValuePair addressRange = - parser.ParseRaw(delegate (string s, ref int start, ref int end) - { - long startingAddress = 0, endingAddress = 0; - int pos = s.IndexOf('-', start, end - start); - if (pos > 0) - { - if (long.TryParse(s.AsSpan(start, pos), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out startingAddress)) - { - long.TryParse(s.AsSpan(pos + 1, end - (pos + 1)), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out endingAddress); - } - } - return new KeyValuePair(startingAddress, endingAddress); - }); - - // Parse the permissions (we only care about entries with 'r' and 'x' set) - if (!parser.ParseRaw(delegate (string s, ref int start, ref int end) - { - bool sawRead = false, sawExec = false; - for (int i = start; i < end; i++) - { - if (s[i] == 'r') - sawRead = true; - else if (s[i] == 'x') - sawExec = true; - } - return sawRead & sawExec; - })) - { - continue; - } - - // Skip past the offset, dev, and inode fields - parser.MoveNext(); - parser.MoveNext(); - parser.MoveNext(); - - // Parse the pathname - if (!parser.MoveNext()) - { - continue; - } - string pathname = parser.ExtractCurrentToEnd(); - - // We only get here if a we have a non-empty pathname and - // the permissions included both readability and executability. - // Yield the result. - yield return new ParsedMapsModule { FileName = pathname, AddressRange = addressRange }; - } - } - private static string GetStatFilePathForThread(int pid, int tid) { // Perf note: Calling GetTaskDirectoryPathForProcess will allocate a string, diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 3c41c1027ffc59..28e419f230f0e4 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -279,6 +279,8 @@ Link="Common\Interop\Linux\Interop.cgroups.cs" /> + The array of modules. internal static ProcessModuleCollection GetModules(int processId) { - var modules = new ProcessModuleCollection(0); - - // Process from the parsed maps file each entry representing a module - foreach (Interop.procfs.ParsedMapsModule entry in Interop.procfs.ParseMapsModules(processId)) - { - int sizeOfImage = (int)(entry.AddressRange.Value - entry.AddressRange.Key); - - // A single module may be split across multiple map entries; consolidate based on - // the name and address ranges of sequential entries. - if (modules.Count > 0) - { - ProcessModule module = modules[modules.Count - 1]; - if (module.FileName == entry.FileName && - ((long)module.BaseAddress + module.ModuleMemorySize == entry.AddressRange.Key)) - { - // Merge this entry with the previous one - module.ModuleMemorySize += sizeOfImage; - continue; - } - } - - // It's not a continuation of a previous entry but a new one: add it. - unsafe - { - modules.Add(new ProcessModule() - { - FileName = entry.FileName, - ModuleName = Path.GetFileName(entry.FileName), - BaseAddress = new IntPtr(unchecked((void*)entry.AddressRange.Key)), - ModuleMemorySize = sizeOfImage, - EntryPointAddress = IntPtr.Zero // unknown - }); - } - } + ProcessModuleCollection modules = Interop.procfs.ParseMapsModules(processId) ?? new(capacity: 0); // Move the main executable module to be the first in the list if it's not already string? exePath = Process.GetExePath(processId); From 9fdc4ebd2407d5802d88d74cc9ec0d87aa82f298 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Dec 2020 11:05:18 +0000 Subject: [PATCH 02/10] Account for rows preceding the first one with r+w --- .../Interop.ProcFsStat.ParseMapModules.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 6dfb0521e9764a..93662a60950feb 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -65,8 +65,9 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable { Debug.Assert(lines != null); - ProcessModule? previous = null; + ProcessModule? module = null; ProcessModuleCollection modules = new(capacity: 0); + bool moduleHasReadAndExecFlags = false; foreach (string line in lines) { @@ -78,11 +79,13 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable if (addressRange == default) { + Commit(); continue; } // Parse the permissions - bool hasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); + bool lineHasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); + moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; // Skip past the offset, dev, and inode fields parser.MoveNext(); @@ -92,25 +95,22 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable // Parse the pathname if (!parser.MoveNext()) { + Commit(); continue; } string pathname = parser.ExtractCurrentToEnd(); - bool isContinuation = previous?.FileName == pathname && (long)previous.BaseAddress + previous.ModuleMemorySize == addressRange.Start; + bool isContinuation = module?.FileName == pathname && (long)module.BaseAddress + module.ModuleMemorySize == addressRange.Start; if (isContinuation) { - previous!.ModuleMemorySize += addressRange.Size; + module!.ModuleMemorySize += addressRange.Size; continue; } - // we only care about entries with 'r' and 'x' set. - if (!hasReadAndExecFlags) - { - continue; - } + Commit(); - var module = new ProcessModule + module = new ProcessModule { FileName = pathname, ModuleName = Path.GetFileName(pathname), @@ -124,8 +124,16 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); } - modules.Add(module); - previous = module; + void Commit() + { + // we only add module to collection, if at least one row had 'r' and 'x' set. + if (moduleHasReadAndExecFlags && module is not null) + { + modules.Add(module); + module = null; + moduleHasReadAndExecFlags = false; + } + } } return modules; From a8bf88f4f2faedadea51a55f262954de2c7762b3 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:57:59 +0000 Subject: [PATCH 03/10] Account for last line --- .../Interop.ProcFsStat.ParseMapModules.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 93662a60950feb..8e6919a4848246 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -123,20 +123,21 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable { module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); } + } + + Commit(); + return modules; - void Commit() + void Commit() + { + // we only add module to collection, if at least one row had 'r' and 'x' set. + if (moduleHasReadAndExecFlags && module is not null) { - // we only add module to collection, if at least one row had 'r' and 'x' set. - if (moduleHasReadAndExecFlags && module is not null) - { - modules.Add(module); - module = null; - moduleHasReadAndExecFlags = false; - } + modules.Add(module); + module = null; + moduleHasReadAndExecFlags = false; } } - - return modules; } } } From c164e4f3fcebd4d87835323b5c29769560b1c98f Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Wed, 9 Dec 2020 18:03:32 +0200 Subject: [PATCH 04/10] Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs Co-authored-by: Tom Deseyn --- .../Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 8e6919a4848246..8d0d4de2abb094 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -124,8 +124,9 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); } } - + // Commit last line. Commit(); + return modules; void Commit() From c09623ffd0a2ecc8f83432f844d4a3a5382c8f97 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Wed, 9 Dec 2020 18:04:45 +0200 Subject: [PATCH 05/10] Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs --- .../Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 8d0d4de2abb094..ae0649466ed0bc 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -124,6 +124,7 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); } } + // Commit last line. Commit(); From 758047fe7e745c24d044d49e1d63008f6ac4f1bf Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Dec 2020 18:30:48 +0000 Subject: [PATCH 06/10] Move module flag assignment after the final commit --- .../Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index ae0649466ed0bc..a32aa027f57707 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -85,7 +85,6 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable // Parse the permissions bool lineHasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); - moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; // Skip past the offset, dev, and inode fields parser.MoveNext(); @@ -109,6 +108,7 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable } Commit(); + moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; module = new ProcessModule { From cd566d4e3c0815f1de80a900be381d0136a4b476 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Thu, 10 Dec 2020 07:15:25 +0000 Subject: [PATCH 07/10] Improve readibility of moduleHasReadAndExecFlags --- .../Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index a32aa027f57707..71ba6c40d38336 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -104,12 +104,13 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable if (isContinuation) { module!.ModuleMemorySize += addressRange.Size; + moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; continue; } Commit(); - moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; + moduleHasReadAndExecFlags = lineHasReadAndExecFlags; module = new ProcessModule { FileName = pathname, @@ -137,7 +138,6 @@ void Commit() { modules.Add(module); module = null; - moduleHasReadAndExecFlags = false; } } } From 97d69075aef7f1c01dbfcb746f4792086c12eb8f Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Thu, 10 Dec 2020 10:22:40 +0200 Subject: [PATCH 08/10] Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs --- .../Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 71ba6c40d38336..a442c0884e4136 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -91,7 +91,7 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable parser.MoveNext(); parser.MoveNext(); - // Parse the pathname + // we only care about the named modules if (!parser.MoveNext()) { Commit(); From b47bced66c0d820670c19d8d58c78767ee451529 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Thu, 10 Dec 2020 10:22:48 +0200 Subject: [PATCH 09/10] Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs --- .../Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index a442c0884e4136..4a31300c624995 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -98,6 +98,7 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable continue; } + // Parse the pathname string pathname = parser.ExtractCurrentToEnd(); bool isContinuation = module?.FileName == pathname && (long)module.BaseAddress + module.ModuleMemorySize == addressRange.Start; From ec9785af35863ebc0a10f0693cf4ac21d9ff3674 Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Thu, 10 Dec 2020 11:23:02 +0200 Subject: [PATCH 10/10] Decouple line parsing from ProcessModule parsing --- .../Interop.ProcFsStat.ParseMapModules.cs | 163 ++++++++++-------- 1 file changed, 90 insertions(+), 73 deletions(-) diff --git a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs index 4a31300c624995..eeb472a19e5d7f 100644 --- a/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs +++ b/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs @@ -15,39 +15,8 @@ internal static partial class procfs { private const string MapsFileName = "/maps"; - private static string GetMapsFilePathForProcess(int pid) - { - return RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; - } - - private static (long Start, int Size) TryParseAddressRange(string s, ref int start, ref int end) - { - int pos = s.IndexOf('-', start, end - start); - if (pos > 0) - { - if (long.TryParse(s.AsSpan(start, pos), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long startingAddress) && - long.TryParse(s.AsSpan(pos + 1, end - (pos + 1)), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long endingAddress)) - { - return (startingAddress, (int)(endingAddress - startingAddress)); - } - } - - return default; - } - - private static bool HasReadAndExecFlags(string s, ref int start, ref int end) - { - bool sawRead = false, sawExec = false; - for (int i = start; i < end; i++) - { - if (s[i] == 'r') - sawRead = true; - else if (s[i] == 'x') - sawExec = true; - } - - return sawRead & sawExec; - } + private static string GetMapsFilePathForProcess(int pid) => + RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; internal static ProcessModuleCollection? ParseMapsModules(int pid) { @@ -71,68 +40,52 @@ private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable foreach (string line in lines) { - // Use a StringParser to avoid string.Split costs - var parser = new StringParser(line, separator: ' ', skipEmpty: true); - - // Parse the address start and size - (long Start, int Size) addressRange = parser.ParseRaw(TryParseAddressRange); - - if (addressRange == default) + if (!TryParseMapsEntry(line, out (long StartAddress, int Size, bool HasReadAndExecFlags, string Path) parsedLine)) { - Commit(); + // Invalid entry for the purposes of ProcessModule parsing, + // discard flushing the current module if it exists. + CommitCurrentModule(); continue; } - // Parse the permissions - bool lineHasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); - - // Skip past the offset, dev, and inode fields - parser.MoveNext(); - parser.MoveNext(); - parser.MoveNext(); - - // we only care about the named modules - if (!parser.MoveNext()) + // Check if entry is a continuation of the current module. + if (module is not null && + module.FileName == parsedLine.Path && + (long)module.BaseAddress + module.ModuleMemorySize == parsedLine.StartAddress) { - Commit(); + // Is continuation, update the current module. + module.ModuleMemorySize += parsedLine.Size; + moduleHasReadAndExecFlags |= parsedLine.HasReadAndExecFlags; continue; } - // Parse the pathname - string pathname = parser.ExtractCurrentToEnd(); - bool isContinuation = module?.FileName == pathname && (long)module.BaseAddress + module.ModuleMemorySize == addressRange.Start; + // Not a continuation, commit any current modules and create a new one. + CommitCurrentModule(); - if (isContinuation) - { - module!.ModuleMemorySize += addressRange.Size; - moduleHasReadAndExecFlags |= lineHasReadAndExecFlags; - continue; - } - - Commit(); - - moduleHasReadAndExecFlags = lineHasReadAndExecFlags; module = new ProcessModule { - FileName = pathname, - ModuleName = Path.GetFileName(pathname), - ModuleMemorySize = addressRange.Size, + FileName = parsedLine.Path, + ModuleName = Path.GetFileName(parsedLine.Path), + ModuleMemorySize = parsedLine.Size, EntryPointAddress = IntPtr.Zero // unknown }; - // on 32-bit platforms, it throws System.OverflowException without the void* cast. + // on 32-bit platforms, it throws System.OverflowException with IntPtr.ctor(Int64), + // so we use IntPtr.ctor(void*) to skip the overflow checking. unsafe { - module.BaseAddress = new IntPtr(unchecked((void*)addressRange.Start)); + module.BaseAddress = new IntPtr((void*)parsedLine.StartAddress); } + + moduleHasReadAndExecFlags = parsedLine.HasReadAndExecFlags; } - // Commit last line. - Commit(); + // Commit any pending modules. + CommitCurrentModule(); return modules; - void Commit() + void CommitCurrentModule() { // we only add module to collection, if at least one row had 'r' and 'x' set. if (moduleHasReadAndExecFlags && module is not null) @@ -142,5 +95,69 @@ void Commit() } } } + + private static bool TryParseMapsEntry(string line, out (long StartAddress, int Size, bool HasReadAndExecFlags, string Path) parsedLine) + { + // Use a StringParser to avoid string.Split costs + var parser = new StringParser(line, separator: ' ', skipEmpty: true); + + // Parse the address start and size + (long start, int size) = parser.ParseRaw(TryParseAddressRange); + + if (size < 0) + { + parsedLine = default; + return false; + } + + // Parse the permissions + bool lineHasReadAndExecFlags = parser.ParseRaw(HasReadAndExecFlags); + + // Skip past the offset, dev, and inode fields + parser.MoveNext(); + parser.MoveNext(); + parser.MoveNext(); + + // we only care about the named modules + if (!parser.MoveNext()) + { + parsedLine = default; + return false; + } + + // Parse the pathname + string pathname = parser.ExtractCurrentToEnd(); + parsedLine = (start, size, lineHasReadAndExecFlags, pathname); + return true; + + static (long Start, int Size) TryParseAddressRange(string s, ref int start, ref int end) + { + int pos = s.IndexOf('-', start, end - start); + if (pos > 0) + { + if (long.TryParse(s.AsSpan(start, pos), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long startingAddress) && + long.TryParse(s.AsSpan(pos + 1, end - (pos + 1)), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long endingAddress)) + { + return (startingAddress, (int)(endingAddress - startingAddress)); + } + } + + return (0, -1); + } + + static bool HasReadAndExecFlags(string s, ref int start, ref int end) + { + bool sawRead = false, sawExec = false; + for (int i = start; i < end; i++) + { + if (s[i] == 'r') + sawRead = true; + else if (s[i] == 'x') + sawExec = true; + } + + return sawRead & sawExec; + } + } } }