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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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)
{
Comment thread
am11 marked this conversation as resolved.
Outdated
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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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