-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Aggregate adjacent memory sizes regardless of r+x (#45401)
* 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. * Account for rows preceding the first one with r+w * Account for last line * Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs Co-authored-by: Tom Deseyn <[email protected]> * Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs * Move module flag assignment after the final commit * Improve readibility of moduleHasReadAndExecFlags * Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs * Update src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs * Decouple line parsing from ProcessModule parsing Co-authored-by: Tom Deseyn <[email protected]> Co-authored-by: Eirik Tsarpalis <[email protected]>
- Loading branch information
1 parent
8dec5ad
commit ed0f1c1
Showing
4 changed files
with
166 additions
and
121 deletions.
There are no files selected for viewing
163 changes: 163 additions & 0 deletions
163
src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// 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) => | ||
RootPath + pid.ToString(CultureInfo.InvariantCulture) + MapsFileName; | ||
|
||
internal static ProcessModuleCollection? ParseMapsModules(int pid) | ||
{ | ||
try | ||
{ | ||
return ParseMapsModulesCore(File.ReadLines(GetMapsFilePathForProcess(pid))); | ||
} | ||
catch (IOException) { } | ||
catch (UnauthorizedAccessException) { } | ||
|
||
return null; | ||
} | ||
|
||
private static ProcessModuleCollection ParseMapsModulesCore(IEnumerable<string> lines) | ||
{ | ||
Debug.Assert(lines != null); | ||
|
||
ProcessModule? module = null; | ||
ProcessModuleCollection modules = new(capacity: 0); | ||
bool moduleHasReadAndExecFlags = false; | ||
|
||
foreach (string line in lines) | ||
{ | ||
if (!TryParseMapsEntry(line, out (long StartAddress, int Size, bool HasReadAndExecFlags, string Path) parsedLine)) | ||
{ | ||
// Invalid entry for the purposes of ProcessModule parsing, | ||
// discard flushing the current module if it exists. | ||
CommitCurrentModule(); | ||
continue; | ||
} | ||
|
||
// 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) | ||
{ | ||
// Is continuation, update the current module. | ||
module.ModuleMemorySize += parsedLine.Size; | ||
moduleHasReadAndExecFlags |= parsedLine.HasReadAndExecFlags; | ||
continue; | ||
} | ||
|
||
// Not a continuation, commit any current modules and create a new one. | ||
CommitCurrentModule(); | ||
|
||
module = new ProcessModule | ||
{ | ||
FileName = parsedLine.Path, | ||
ModuleName = Path.GetFileName(parsedLine.Path), | ||
ModuleMemorySize = parsedLine.Size, | ||
EntryPointAddress = IntPtr.Zero // unknown | ||
}; | ||
|
||
// 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((void*)parsedLine.StartAddress); | ||
} | ||
|
||
moduleHasReadAndExecFlags = parsedLine.HasReadAndExecFlags; | ||
} | ||
|
||
// Commit any pending modules. | ||
CommitCurrentModule(); | ||
|
||
return modules; | ||
|
||
void CommitCurrentModule() | ||
{ | ||
// 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; | ||
} | ||
} | ||
} | ||
|
||
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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters