Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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/";

Expand Down Expand Up @@ -78,12 +76,6 @@ internal struct ParsedStat
//internal long cguest_time;
}

internal struct ParsedMapsModule
{
internal string FileName;
internal KeyValuePair<long, long> AddressRange;
}

internal static string GetExeFilePathForProcess(int pid)
{
return RootPath + pid.ToString(CultureInfo.InvariantCulture) + ExeFileName;
Expand All @@ -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;
Expand All @@ -114,80 +101,6 @@ internal static string GetFileDescriptorDirectoryPathForProcess(int pid)
return RootPath + pid.ToString(CultureInfo.InvariantCulture) + FileDescriptorDirectoryName;
}

internal static IEnumerable<ParsedMapsModule> ParseMapsModules(int pid)
{
try
{
return ParseMapsModulesCore(File.ReadLines(GetMapsFilePathForProcess(pid)));
}
catch (IOException) { }
catch (UnauthorizedAccessException) { }

return Array.Empty<ParsedMapsModule>();
}

private static IEnumerable<ParsedMapsModule> ParseMapsModulesCore(IEnumerable<string> 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<long, long> 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<long, long>(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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@
Link="Common\Interop\Linux\Interop.cgroups.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.cs"
Link="Common\Interop\Linux\Interop.ProcFsStat.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.ParseMapModules.cs"
Link="Common\Interop\Linux\Interop.ProcFsStat.ParseMapModules.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs"
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SchedGetSetAffinity.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,40 +38,7 @@ public static ProcessInfo[] GetProcessInfos(string machineName)
/// <returns>The array of modules.</returns>
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);
Expand Down