v 0.1.2
This commit is contained in:
parent
09af9d5091
commit
802598be79
@ -12,6 +12,7 @@ namespace WebmrAPI.Configuration
|
||||
{
|
||||
public int ProcessScanInterval { get; set; } = 5;
|
||||
public int MemoryRegionScanTimeout { get; set; } = 30;
|
||||
public bool AllowProcessReadAccess { get; set; } = true;
|
||||
}
|
||||
|
||||
public class WebServerSettings
|
||||
|
9
Exceptions/GettingModuleInfoException.cs
Normal file
9
Exceptions/GettingModuleInfoException.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// File: Exceptions/GettingModuleInfoException.cs
|
||||
|
||||
namespace WebmrAPI.Exceptions
|
||||
{
|
||||
public class GettingModuleInfoException : ProcessMonitorException
|
||||
{
|
||||
public GettingModuleInfoException(string message) : base(message) { }
|
||||
}
|
||||
}
|
9
Exceptions/MemoryRegionException.cs
Normal file
9
Exceptions/MemoryRegionException.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// File: Exceptions/MemoryRegionException.cs
|
||||
|
||||
namespace WebmrAPI.Exceptions
|
||||
{
|
||||
public class MemoryRegionException : ProcessMonitorException
|
||||
{
|
||||
public MemoryRegionException(string message) : base(message) { }
|
||||
}
|
||||
}
|
9
Exceptions/ProcessAccessDeniedException.cs
Normal file
9
Exceptions/ProcessAccessDeniedException.cs
Normal file
@ -0,0 +1,9 @@
|
||||
// File: Exceptions/ProcessAccessDeniedException.cs
|
||||
|
||||
namespace WebmrAPI.Exceptions
|
||||
{
|
||||
public class ProcessAccessDeniedException : ProcessMonitorException
|
||||
{
|
||||
public ProcessAccessDeniedException(string message) : base(message) { }
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
namespace WebmrAPI.Exceptions
|
||||
// File: Exceptions/ProcessMonitorException.cs
|
||||
|
||||
namespace WebmrAPI.Exceptions
|
||||
{
|
||||
public class ProcessMonitorException : Exception
|
||||
{
|
||||
|
@ -1,10 +1,11 @@
|
||||
// File: Models/MemoryRegion.cs
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
public class MemoryRegion : ConcurrentDTO
|
||||
public class MemoryRegion : ConcurrentObject
|
||||
{
|
||||
private long _addr = 0;
|
||||
private ulong _size = 0;
|
||||
|
@ -1,26 +1,26 @@
|
||||
// File: Models/MemoryRegionInfo.cs
|
||||
|
||||
using WebmrAPI.Services;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
public class MemoryRegionInfo : MemoryRegion
|
||||
{
|
||||
private WinApi.MemoryState _state = 0;
|
||||
private WinApi.MemoryPageProtectionState _protect = 0;
|
||||
private WinApi.MemoryType _type = 0;
|
||||
private WindowsProcess.MemoryState _state = 0;
|
||||
private WindowsProcess.MemoryPageProtectionState _protect = 0;
|
||||
private WindowsProcess.MemoryType _type = 0;
|
||||
|
||||
public WinApi.MemoryState MemoryState
|
||||
public WindowsProcess.MemoryState MemoryState
|
||||
{
|
||||
get => LockedGet(ref _state);
|
||||
set => LockedSet(ref _state, value);
|
||||
}
|
||||
public WinApi.MemoryPageProtectionState MemoryPageProtection
|
||||
public WindowsProcess.MemoryPageProtectionState MemoryPageProtection
|
||||
{
|
||||
get => LockedGet(ref _protect);
|
||||
set => LockedSet(ref _protect, value);
|
||||
}
|
||||
public WinApi.MemoryType MemoryType
|
||||
public WindowsProcess.MemoryType MemoryType
|
||||
{
|
||||
get => LockedGet(ref _type);
|
||||
set => LockedSet(ref _type, value);
|
||||
|
@ -4,15 +4,15 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
public enum ProcessStatus
|
||||
{
|
||||
Undefined,
|
||||
Running,
|
||||
NotResponding
|
||||
}
|
||||
|
||||
public class ProcessBaseInfo : MemoryRegion
|
||||
{
|
||||
public enum ProcessStatus
|
||||
{
|
||||
Undefined,
|
||||
Running,
|
||||
NotResponding
|
||||
}
|
||||
|
||||
private TimeSpan _lastPTime = TimeSpan.Zero;
|
||||
private TimeSpan _curPTime = TimeSpan.Zero;
|
||||
|
||||
|
@ -2,25 +2,36 @@
|
||||
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.Json.Serialization;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class ProcessInfo : ProcessBaseInfo
|
||||
{
|
||||
private DateTime _lastUpdate = DateTime.MinValue;
|
||||
private List<MemoryRegionInfo> _regions = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTime LastUpdate
|
||||
public LazyConcurrentContainer<MemoryRegionInfo> MemoryRegionsContainer { get; set; } = new();
|
||||
[JsonIgnore]
|
||||
public LazyConcurrentContainer<ProcessModuleInfo> ModulesContainer { get; set; } = new();
|
||||
[JsonIgnore]
|
||||
public LazyConcurrentContainer<ProcessThreadInfo> ThreadsContainer { get; set; } = new();
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<MemoryRegionInfo>? MemoryRegions
|
||||
{
|
||||
get => LockedGet(ref _lastUpdate);
|
||||
set => LockedSet(ref _lastUpdate, value);
|
||||
get => MemoryRegionsContainer.Values;
|
||||
}
|
||||
public List<MemoryRegionInfo> MemoryRegions
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<ProcessModuleInfo>? Modules
|
||||
{
|
||||
get => LockedGet(ref _regions);
|
||||
set => LockedSet(ref _regions, value);
|
||||
get => ModulesContainer.Values;
|
||||
}
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IEnumerable<ProcessThreadInfo>? Threads
|
||||
{
|
||||
get => ThreadsContainer.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
Models/ProcessModuleInfo.cs
Normal file
37
Models/ProcessModuleInfo.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// File: Models/ProcessModuleInfo.cs
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
public class ProcessModuleInfo : MemoryRegion
|
||||
{
|
||||
private string? _moduleName = null;
|
||||
private string? _fileName = null;
|
||||
private long _entrypointAddress = 0;
|
||||
|
||||
public string? ModuleName
|
||||
{
|
||||
get => LockedGet(ref _moduleName);
|
||||
set => LockedSet(ref _moduleName, value);
|
||||
}
|
||||
|
||||
public string? FileName
|
||||
{
|
||||
get => LockedGet(ref _fileName);
|
||||
set => LockedSet(ref _fileName, value);
|
||||
}
|
||||
|
||||
public string? EntrypointAddress
|
||||
{
|
||||
get => (EntrypointRawAddress > 0) ? $"0x{EntrypointRawAddress:X16}" : null;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public long EntrypointRawAddress
|
||||
{
|
||||
get => LockedGet(ref _entrypointAddress);
|
||||
set => LockedSet(ref _entrypointAddress, value);
|
||||
}
|
||||
}
|
||||
}
|
52
Models/ProcessThreadInfo.cs
Normal file
52
Models/ProcessThreadInfo.cs
Normal file
@ -0,0 +1,52 @@
|
||||
// File: Models/ProcessThreadInfo.cs
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
{
|
||||
public class ProcessThreadInfo : ConcurrentObject
|
||||
{
|
||||
private TimeSpan _lastPTime = TimeSpan.Zero;
|
||||
private TimeSpan _curPTime = TimeSpan.Zero;
|
||||
|
||||
private int _id = 0;
|
||||
private int _currentPriority = 0;
|
||||
private int _basePriority = 0;
|
||||
private double _cpuUsage = 0;
|
||||
|
||||
[JsonIgnore]
|
||||
public TimeSpan TotalProcessorTime
|
||||
{
|
||||
set { lock (_lock) { _lastPTime = _curPTime; _curPTime = value; } }
|
||||
}
|
||||
[JsonIgnore]
|
||||
public double ProcessorTime
|
||||
{
|
||||
get { lock (_lock) return (_curPTime - _lastPTime).TotalMilliseconds; }
|
||||
}
|
||||
|
||||
public int ID
|
||||
{
|
||||
get => LockedGet(ref _id);
|
||||
set => LockedSet(ref _id, value);
|
||||
}
|
||||
|
||||
public int CurrentPriority
|
||||
{
|
||||
get => LockedGet(ref _currentPriority);
|
||||
set => LockedSet(ref _currentPriority, value);
|
||||
}
|
||||
|
||||
public int BasePriority
|
||||
{
|
||||
get => LockedGet(ref _basePriority);
|
||||
set => LockedSet(ref _basePriority, value);
|
||||
}
|
||||
public double CpuUsage
|
||||
{
|
||||
get => LockedGet(ref _cpuUsage);
|
||||
set => LockedSet(ref _cpuUsage, value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +1,62 @@
|
||||
// File: Services/ProcessMonitor.cs
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Management;
|
||||
using System.Runtime.Versioning;
|
||||
using WebmrAPI.Configuration;
|
||||
using WebmrAPI.Exceptions;
|
||||
using WebmrAPI.Models;
|
||||
using WebmrAPI.Services.Scanners;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class ProcessMonitor : IHostedService, IDisposable
|
||||
{
|
||||
private DateTime _lastScanTime = DateTime.MinValue;
|
||||
private DateTime _currentScanTime = DateTime.MinValue;
|
||||
|
||||
private Timer? _timer;
|
||||
private readonly ILogger<ProcessMonitor> _logger;
|
||||
private readonly MonitoringSettings _config;
|
||||
private readonly MonitoringSettings _config;
|
||||
private LazyConcurrentContainer<ProcessInfo> _processes = new();
|
||||
private ScanProvider _provider;
|
||||
|
||||
private readonly object _lock = new object();
|
||||
private ConcurrentDictionary<int, ProcessInfo> _processesBuffer = new();
|
||||
public ILogger<ProcessMonitor> Logger { get => _logger; }
|
||||
public MonitoringSettings Config { get => _config; }
|
||||
public LazyConcurrentContainer<ProcessInfo> Processes { get => _processes; }
|
||||
|
||||
private DateTime ScanTime
|
||||
public IEnumerable<ProcessBaseInfo>? GetBufferedProcesses()
|
||||
{
|
||||
set
|
||||
{
|
||||
_lastScanTime = _currentScanTime;
|
||||
_currentScanTime = value;
|
||||
}
|
||||
return _processes.Values;
|
||||
}
|
||||
|
||||
private TimeSpan Elapsed
|
||||
public ProcessInfo? GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails)
|
||||
{
|
||||
get => _currentScanTime - _lastScanTime;
|
||||
}
|
||||
|
||||
public IEnumerable<ProcessBaseInfo> GetBufferedProcesses()
|
||||
{
|
||||
lock (_lock)
|
||||
if (_processes.Container != null && _processes.Container.TryGetValue(pid, out var info))
|
||||
{
|
||||
return _processesBuffer.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public ProcessInfo? GetProcessDetails(int pid)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_processesBuffer.TryGetValue(pid, out ProcessInfo? data))
|
||||
try
|
||||
{
|
||||
return PopulateProcessInfo(data);
|
||||
_provider.CreateScanTask(info, target).Scan();
|
||||
_logger.LogInformation($"Scan details of the process {info.Name} (PID: {info.PID}) was completed.");
|
||||
return info;
|
||||
}
|
||||
catch (ProcessMonitorException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Unhandled error during scanning of the process {info.Name} (PID: {info.PID}): {ex.Message}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public ProcessMonitor(ILogger<ProcessMonitor> logger, IOptions<AppSettings> settings)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = settings.Value.Monitoring;
|
||||
_provider = new(this);
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@ -76,236 +73,24 @@ namespace WebmrAPI.Services
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool OpenProcess(ProcessInfo process, out IntPtr ctx)
|
||||
{
|
||||
ctx = WinApi.OpenProcess(WinApi.PROCESS_QUERY_INFORMATION | WinApi.PROCESS_VM_OPERATION, false, (uint)process.PID);
|
||||
if (ctx == IntPtr.Zero)
|
||||
{
|
||||
var errno = WinApi.LastError;
|
||||
|
||||
if (errno == 5)
|
||||
{
|
||||
_logger.LogWarning($"Access denied to process {process.Name} (PID: {process.PID})");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"WinAPI call 'OpenProcess for PID {process.PID}' failed", errno);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ProcessInfo PopulateProcessInfo(ProcessInfo dst, List<MemoryRegionInfo> regions)
|
||||
{
|
||||
dst.MemoryRegions = regions;
|
||||
dst.LastUpdate = DateTime.UtcNow;
|
||||
return dst;
|
||||
}
|
||||
|
||||
private ProcessInfo PopulateProcessInfo(ProcessInfo dst)
|
||||
{
|
||||
if ((DateTime.UtcNow - dst.LastUpdate).TotalSeconds < _config.MemoryRegionScanTimeout) return dst;
|
||||
|
||||
IntPtr cur = IntPtr.Zero;
|
||||
IntPtr ctx = IntPtr.Zero;
|
||||
long next = 0;
|
||||
var regions = new List<MemoryRegionInfo>();
|
||||
|
||||
try
|
||||
{
|
||||
if (OpenProcess(dst, out ctx))
|
||||
while ((next > cur.ToInt64() && next < 0x7fffffffffff) || next == 0)
|
||||
{
|
||||
WinApi.MEMORY_BASIC_INFORMATION mbi;
|
||||
|
||||
cur = new IntPtr(next);
|
||||
|
||||
if (WinApi.VirtualQueryEx(ctx, cur, out mbi, WinApi.MBISize) == 0)
|
||||
{
|
||||
_logger.LogWarning($"VirtualQueryEx for PID {dst.PID} at address 0x{cur.ToInt64():X12} failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if (mbi.State == WinApi.MemoryState.Commit || mbi.State == WinApi.MemoryState.Reserve)
|
||||
{
|
||||
regions.Add(new MemoryRegionInfo
|
||||
{
|
||||
MemoryAddress = mbi.BaseAddress.ToInt64(),
|
||||
MemorySize = mbi.RegionSize.ToUInt64(),
|
||||
MemoryState = mbi.State,
|
||||
MemoryPageProtection = mbi.PageProtection,
|
||||
MemoryType = mbi.Type
|
||||
});
|
||||
}
|
||||
|
||||
next = mbi.BaseAddress.ToInt64() + (long)mbi.RegionSize.ToUInt64();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ctx != IntPtr.Zero) WinApi.CloseHandle(ctx);
|
||||
}
|
||||
|
||||
return PopulateProcessInfo(dst, regions);
|
||||
}
|
||||
private void PopulateBaseProcessInfo(ProcessInfo dst, System.Diagnostics.Process process, Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> wmiData)
|
||||
{
|
||||
dst.PID = process.Id;
|
||||
dst.MemorySize = (ulong)process.WorkingSet64;
|
||||
dst.ThreadCount = process.Threads.Count;
|
||||
dst.TotalProcessorTime = process.TotalProcessorTime;
|
||||
dst.MemoryAddress = 0x000000000000;
|
||||
|
||||
if (wmiData.TryGetValue(process.Id, out var entry))
|
||||
{
|
||||
dst.Name = entry.Name;
|
||||
dst.CommandLine = entry.CommandLine;
|
||||
dst.ParentPID = entry.ParentPID;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst.Name = process.ProcessName;
|
||||
_logger.LogDebug("No WMI data was found for PID {pid}.", process.Id);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (process.MainModule != null)
|
||||
{
|
||||
dst.FileName = process.MainModule.FileName;
|
||||
dst.MemoryAddress = process.MainModule.BaseAddress.ToInt64();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug($"Process {process.ProcessName} (PID: {process.Id}) process has no MainModule");
|
||||
}
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex)
|
||||
{
|
||||
_logger.LogDebug($"Process {process.ProcessName} (PID: {process.Id}), getting process address ends with message: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Process {process.ProcessName} (PID: {process.Id}), an error occurred while getting process address: {ex.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
dst.StartTime = process.StartTime;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug("Could not get StartTime for PID {Pid}: {Message}", process.Id, ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Refresh();
|
||||
dst.Status = process.Responding ? ProcessStatus.Running : ProcessStatus.NotResponding;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
dst.Status = ProcessStatus.Undefined;
|
||||
_logger.LogDebug("Could not get Status for PID {Pid}: {Message}", process.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWork(object? state)
|
||||
{
|
||||
_logger.LogDebug("Initiating process scan...");
|
||||
try
|
||||
{
|
||||
var cur = new Dictionary<int, ProcessInfo>();
|
||||
var wmiData = GetWmiProcessData();
|
||||
ScanTime = DateTime.UtcNow;
|
||||
|
||||
foreach (var process in System.Diagnostics.Process.GetProcesses())
|
||||
{
|
||||
ProcessInfo? processInfo;
|
||||
|
||||
if (process.Id == 0 || process.Id == 4)
|
||||
{
|
||||
process.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_processesBuffer.TryGetValue(process.Id, out var existingProcessInfo))
|
||||
{
|
||||
processInfo = existingProcessInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
processInfo = new ProcessInfo();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PopulateBaseProcessInfo(processInfo, process, wmiData);
|
||||
|
||||
if (_lastScanTime != DateTime.MinValue && Elapsed.TotalMilliseconds > 0)
|
||||
{
|
||||
processInfo.CpuUsage = (processInfo.ProcessorTime / Elapsed.TotalMilliseconds) / Environment.ProcessorCount * 100.0;
|
||||
if (processInfo.CpuUsage > 100.0) processInfo.CpuUsage = 100.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
processInfo.CpuUsage = 0.0;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get full information for process {ProcessId} ({ProcessName}).",
|
||||
process.Id, process.ProcessName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cur.TryAdd(process.Id, processInfo);
|
||||
process?.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_processesBuffer = new ConcurrentDictionary<int, ProcessInfo>(cur);
|
||||
}
|
||||
_logger.LogInformation("Process buffer updated, contains {Count} processes.", _processesBuffer.Count);
|
||||
_provider.CreateScanTask().Scan();
|
||||
_logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes.");
|
||||
}
|
||||
catch (ProcessMonitorException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unhandled error during process monitoring cycle.");
|
||||
_logger.LogError($"Unhandled error during process monitoring cycle: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> GetWmiProcessData()
|
||||
{
|
||||
var data = new Dictionary<int, (string?, string?, int)>();
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process"))
|
||||
using (ManagementObjectCollection processes = searcher.Get())
|
||||
{
|
||||
foreach (ManagementObject obj in processes)
|
||||
{
|
||||
int pid = Convert.ToInt32(obj["ProcessId"]);
|
||||
int ppid = Convert.ToInt32(obj["ParentProcessId"]);
|
||||
string? name = obj["Name"]?.ToString();
|
||||
string? cmd = obj["CommandLine"]?.ToString();
|
||||
|
||||
data[pid] = (name, cmd, ppid);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "The data of the WMI process could not be retrieved.");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer?.Dispose();
|
||||
|
23
Services/Scanners/AbstractCpuScanner.cs
Normal file
23
Services/Scanners/AbstractCpuScanner.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// File: Services/Scanners/AbstractCpuScanner.cs
|
||||
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public abstract class AbstractCpuScanner<T> : AbstractScanner<T> where T : new()
|
||||
{
|
||||
public static double CalcCpuUsage(double pTime, double elapsed)
|
||||
{
|
||||
double cpuUsage = 0;
|
||||
if (elapsed > 0)
|
||||
{
|
||||
cpuUsage = (pTime / elapsed) / Environment.ProcessorCount * 100.0;
|
||||
if (cpuUsage > 100.0) cpuUsage = 100.0;
|
||||
}
|
||||
return cpuUsage;
|
||||
}
|
||||
|
||||
public AbstractCpuScanner(IScanProvider scanner, LazyConcurrentContainer<T> container)
|
||||
: base(scanner, container) { }
|
||||
}
|
||||
}
|
66
Services/Scanners/AbstractScanner.cs
Normal file
66
Services/Scanners/AbstractScanner.cs
Normal file
@ -0,0 +1,66 @@
|
||||
// File: Services/Scanners/AbstractScanner.cs
|
||||
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public abstract class AbstractScanner<T> : IScannable where T : new()
|
||||
{
|
||||
internal IScanProvider _provider;
|
||||
LazyConcurrentContainer<T> _container;
|
||||
|
||||
abstract public ScanTarget Target { get; }
|
||||
|
||||
internal IScanProvider Provider { get => _provider; }
|
||||
internal LazyConcurrentContainer<T> Container { get => _container; }
|
||||
internal int Timeout { get => _provider.GetTimeout(Target); }
|
||||
|
||||
internal bool IsActual()
|
||||
{
|
||||
return (DateTime.UtcNow - Container.LastUpdate).TotalSeconds < Timeout;
|
||||
}
|
||||
internal bool GetFromCacheOrNew(long key, out T res)
|
||||
{
|
||||
var cache = Container.Container;
|
||||
|
||||
bool result = true;
|
||||
T? found;
|
||||
if (cache == null || !cache.TryGetValue(key, out found))
|
||||
{
|
||||
result = false;
|
||||
found = new();
|
||||
}
|
||||
res = found;
|
||||
return result;
|
||||
}
|
||||
|
||||
abstract internal bool Scan(out Dictionary<long, T>? data);
|
||||
|
||||
public void Scan()
|
||||
{
|
||||
bool success = false;
|
||||
if (Container.Container != null && IsActual()) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (success = Scan(out var data))
|
||||
{
|
||||
Container.SetContainer(data);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
Container.CleanContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractScanner(IScanProvider scanProvider, LazyConcurrentContainer<T> container)
|
||||
{
|
||||
_provider = scanProvider;
|
||||
_container = container;
|
||||
}
|
||||
}
|
||||
}
|
11
Services/Scanners/IScanProvider.cs
Normal file
11
Services/Scanners/IScanProvider.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// File: Services/Scanners/IScanProvider.cs
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public interface IScanProvider
|
||||
{
|
||||
public bool ProcessReadAccess { get; }
|
||||
public ILogger Logger { get; }
|
||||
public int GetTimeout(ScanTarget target);
|
||||
}
|
||||
}
|
10
Services/Scanners/IScannable.cs
Normal file
10
Services/Scanners/IScannable.cs
Normal file
@ -0,0 +1,10 @@
|
||||
// File: Services/Scanners/IScannable.cs
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public interface IScannable
|
||||
{
|
||||
public ScanTarget Target { get; }
|
||||
public void Scan();
|
||||
}
|
||||
}
|
61
Services/Scanners/MemoryRegionScanner.cs
Normal file
61
Services/Scanners/MemoryRegionScanner.cs
Normal file
@ -0,0 +1,61 @@
|
||||
// File: Services/Scanners/MemoryRegionScanner.cs
|
||||
|
||||
using WebmrAPI.Exceptions;
|
||||
using WebmrAPI.Models;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class MemoryRegionScanner : AbstractScanner<MemoryRegionInfo>
|
||||
{
|
||||
override public ScanTarget Target { get => ScanTarget.MemoryRegions; }
|
||||
internal int PID { get; private set; }
|
||||
|
||||
override internal bool Scan(out Dictionary<long, MemoryRegionInfo>? data)
|
||||
{
|
||||
data = new();
|
||||
|
||||
try
|
||||
{
|
||||
using (var process = new WindowsProcess(PID))
|
||||
{
|
||||
var regions = data;
|
||||
process.ForeachMemoryRegion(info =>
|
||||
{
|
||||
long addr = info.BaseAddress.ToInt64();
|
||||
regions.Add(addr, new MemoryRegionInfo
|
||||
{
|
||||
MemoryAddress = addr,
|
||||
MemorySize = info.RegionSize.ToUInt64(),
|
||||
MemoryState = info.State,
|
||||
MemoryPageProtection = info.PageProtection,
|
||||
MemoryType = info.Type
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (ProcessAccessDeniedException ex)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Access denied to process {PID} for memory region scanning. Error: {ex.Message}");
|
||||
}
|
||||
catch (MemoryRegionException ex)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Error scanning memory regions for PID {PID}. Error: {ex.Message}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogError($"An unexpected error occurred while scanning memory regions for PID {PID}. Error: {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public MemoryRegionScanner(IScanProvider scanner, LazyConcurrentContainer<MemoryRegionInfo> container, int pid)
|
||||
: base(scanner, container)
|
||||
{
|
||||
PID = pid;
|
||||
}
|
||||
}
|
||||
}
|
98
Services/Scanners/ModuleScanner.cs
Normal file
98
Services/Scanners/ModuleScanner.cs
Normal file
@ -0,0 +1,98 @@
|
||||
// File: Services/Scanners/ModuleScanner.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using WebmrAPI.Exceptions;
|
||||
using WebmrAPI.Models;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class ModuleScanner : AbstractScanner<ProcessModuleInfo>
|
||||
{
|
||||
override public ScanTarget Target { get => ScanTarget.Modules; }
|
||||
internal int PID { get; private set; }
|
||||
|
||||
override internal bool Scan(out Dictionary<long, ProcessModuleInfo>? data)
|
||||
{
|
||||
data = new();
|
||||
|
||||
try
|
||||
{
|
||||
using (var process = Process.GetProcessById(PID))
|
||||
{
|
||||
foreach (ProcessModule module in process.Modules)
|
||||
{
|
||||
long addr = module.BaseAddress.ToInt64();
|
||||
data.Add(addr, new ProcessModuleInfo
|
||||
{
|
||||
MemoryAddress = addr,
|
||||
ModuleName = module.ModuleName,
|
||||
FileName = module.FileName,
|
||||
MemorySize = (ulong)module.ModuleMemorySize,
|
||||
EntrypointRawAddress = module.EntryPointAddress.ToInt64()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Access denied to Process.Modules for PID {PID}. Attempting P/Invoke. Error: {ex.Message}");
|
||||
if (!Provider.ProcessReadAccess) return false;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Process with PID {PID} might have exited before module scanning. Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogError(ex, $"An unexpected error occurred while getting modules for PID {PID} using managed API.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var process = new WindowsProcess(PID, true))
|
||||
{
|
||||
var modules = data;
|
||||
process.ForeachLoadedModule(info =>
|
||||
{
|
||||
long addr = info.BaseAddress.ToInt64();
|
||||
modules.Add(addr, new ProcessModuleInfo
|
||||
{
|
||||
ModuleName = info.Name.ToString(),
|
||||
MemoryAddress = addr,
|
||||
FileName = info.FileName.ToString(),
|
||||
MemorySize = info.MemorySize,
|
||||
EntrypointRawAddress = info.EntryPointAddress.ToInt64()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (GettingModuleInfoException ex)
|
||||
{
|
||||
Provider.Logger.LogDebug(ex.Message);
|
||||
}
|
||||
catch (ProcessMonitorException ex)
|
||||
{
|
||||
Provider.Logger.LogError(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogError(ex, $"An error occurred while getting modules for PID {PID} using P/Invoke.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ModuleScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessModuleInfo> container, int pid)
|
||||
: base(scanner, container)
|
||||
{
|
||||
PID = pid;
|
||||
}
|
||||
}
|
||||
}
|
148
Services/Scanners/ProcessScanner.cs
Normal file
148
Services/Scanners/ProcessScanner.cs
Normal file
@ -0,0 +1,148 @@
|
||||
// File: Services/Scanners/ProcessScanner.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Management;
|
||||
using WebmrAPI.Models;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class ProcessScanner : AbstractCpuScanner<ProcessInfo>
|
||||
{
|
||||
override public ScanTarget Target { get => ScanTarget.Processes; }
|
||||
|
||||
private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> _wmi;
|
||||
|
||||
private Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> WmiData
|
||||
{
|
||||
get => _wmi.Result;
|
||||
}
|
||||
|
||||
async private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> GetWmiDataAsync()
|
||||
{
|
||||
var wmi = new Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process"))
|
||||
using (var processes = await Task.Run(() => { return searcher.Get(); }))
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (var obj in processes)
|
||||
{
|
||||
int pid = Convert.ToInt32(obj["ProcessId"]);
|
||||
int ppid = Convert.ToInt32(obj["ParentProcessId"]);
|
||||
string? name = obj["Name"]?.ToString();
|
||||
string? cmd = obj["CommandLine"]?.ToString();
|
||||
|
||||
wmi[pid] = (name, cmd, ppid);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes.");
|
||||
}
|
||||
|
||||
return wmi;
|
||||
}
|
||||
|
||||
private bool Populate(ProcessInfo info, Process process)
|
||||
{
|
||||
info.TotalProcessorTime = process.TotalProcessorTime;
|
||||
info.MemorySize = (ulong)process.WorkingSet64;
|
||||
info.ThreadCount = process.Threads.Count;
|
||||
|
||||
try
|
||||
{
|
||||
if (info.MemoryAddress == 0 && process.MainModule != null)
|
||||
{
|
||||
info.FileName = process.MainModule.FileName;
|
||||
info.MemoryAddress = process.MainModule.BaseAddress.ToInt64();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Process {process.ProcessName} (PID: {process.Id}), an error occurred while getting process address: {ex.Message}");
|
||||
}
|
||||
|
||||
if (info.MemoryAddress != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (WmiData.TryGetValue(process.Id, out var entry))
|
||||
{
|
||||
info.Name = entry.Name;
|
||||
info.CommandLine = entry.CommandLine;
|
||||
info.ParentPID = entry.ParentPID;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Name = process.ProcessName;
|
||||
Provider.Logger.LogDebug($"WMI data not found for PID {process.Id}. Falling back to Process.ProcessName.");
|
||||
}
|
||||
|
||||
if (info.StartTime == null)
|
||||
{
|
||||
try { info.StartTime = process.StartTime; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogDebug($"Could not get StartTime for PID {process.Id}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
process.Refresh();
|
||||
info.Status = process.Responding ? ProcessInfo.ProcessStatus.Running : ProcessInfo.ProcessStatus.NotResponding;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.Status = ProcessInfo.ProcessStatus.Undefined;
|
||||
Provider.Logger.LogDebug($"Could not get Status for PID {process.Id}: {ex.Message}");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override internal bool Scan(out Dictionary<long, ProcessInfo>? data)
|
||||
{
|
||||
data = new();
|
||||
|
||||
foreach (var process in Process.GetProcesses())
|
||||
using (process)
|
||||
{
|
||||
ProcessInfo? info;
|
||||
|
||||
if (process.Id == 0 || process.Id == 4)
|
||||
{
|
||||
process.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetFromCacheOrNew(process.Id, out info))
|
||||
{
|
||||
info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.PID = process.Id;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Populate(info, process))
|
||||
{
|
||||
data.Add(info.PID, info);
|
||||
}
|
||||
} catch (Exception) { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ProcessScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessInfo> container)
|
||||
: base(scanner, container)
|
||||
{
|
||||
_wmi = GetWmiDataAsync();
|
||||
}
|
||||
}
|
||||
}
|
78
Services/Scanners/ScanProvider.cs
Normal file
78
Services/Scanners/ScanProvider.cs
Normal file
@ -0,0 +1,78 @@
|
||||
// File: Services/Scanners/ScanProvider.cs
|
||||
|
||||
using WebmrAPI.Models;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class ScanProvider : IScanProvider
|
||||
{
|
||||
private ProcessMonitor _monitor;
|
||||
public bool ProcessReadAccess { get => _monitor.Config.AllowProcessReadAccess; }
|
||||
public ILogger Logger { get => _monitor.Logger; }
|
||||
|
||||
public int GetTimeout(ScanTarget target)
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case ScanTarget.Processes: return _monitor.Config.MemoryRegionScanTimeout;
|
||||
case ScanTarget.Modules: return _monitor.Config.MemoryRegionScanTimeout;
|
||||
case ScanTarget.Threads: return _monitor.Config.MemoryRegionScanTimeout;
|
||||
default: return _monitor.Config.ProcessScanInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public IScannable CreateScanTask()
|
||||
{
|
||||
return new ProcessScanner(this, _monitor.Processes);
|
||||
}
|
||||
|
||||
private IScannable CreateScanTask(ScanTarget target)
|
||||
{
|
||||
var scanner = new ScanQueue();
|
||||
|
||||
CreateScanTask().Scan();
|
||||
var data = _monitor.Processes.Values;
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
foreach (var process in data)
|
||||
{
|
||||
scanner.Add(CreateScanTask(process, target));
|
||||
}
|
||||
}
|
||||
return scanner;
|
||||
}
|
||||
|
||||
public IScannable CreateScanTask(ProcessInfo? process, ScanTarget target = ScanTarget.ProcessDetails)
|
||||
{
|
||||
var scanner = new ScanQueue();
|
||||
|
||||
if (target.HasFlag(ScanTarget.Processes))
|
||||
{
|
||||
return CreateScanTask(target^ScanTarget.Processes);
|
||||
}
|
||||
|
||||
if (target.HasFlag(ScanTarget.MemoryRegions) && process != null)
|
||||
{
|
||||
scanner.Add(new MemoryRegionScanner(this, process.MemoryRegionsContainer, process.PID));
|
||||
}
|
||||
|
||||
if (target.HasFlag(ScanTarget.Modules) && process != null)
|
||||
{
|
||||
scanner.Add(new ModuleScanner(this, process.ModulesContainer, process.PID));
|
||||
}
|
||||
|
||||
if (target.HasFlag(ScanTarget.Threads) && process != null)
|
||||
{
|
||||
scanner.Add(new ThreadScanner(this, process.ThreadsContainer, process.PID));
|
||||
}
|
||||
|
||||
return scanner;
|
||||
}
|
||||
|
||||
public ScanProvider(ProcessMonitor monitoringService)
|
||||
{
|
||||
_monitor = monitoringService;
|
||||
}
|
||||
}
|
||||
}
|
27
Services/Scanners/ScanQueue.cs
Normal file
27
Services/Scanners/ScanQueue.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// File: Services/Scanners/ScanQueue.cs
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class ScanQueue : IScannable
|
||||
{
|
||||
private ScanTarget _target = 0;
|
||||
public ScanTarget Target { get => _target; }
|
||||
|
||||
private Queue<IScannable> _queue = new();
|
||||
|
||||
public ScanQueue Add(IScannable item)
|
||||
{
|
||||
_queue.Enqueue(item);
|
||||
_target |= item.Target;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Scan()
|
||||
{
|
||||
while (_queue.Count > 0)
|
||||
{
|
||||
_queue.Dequeue().Scan();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
Services/Scanners/ScanTarget.cs
Normal file
16
Services/Scanners/ScanTarget.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// File: Services/Scanners/ScanTarget.cs
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
[Flags]
|
||||
public enum ScanTarget : byte
|
||||
{
|
||||
Processes = 0x01,
|
||||
MemoryRegions = 0x02,
|
||||
Modules = 0x04,
|
||||
Threads = 0x08,
|
||||
|
||||
ProcessDetails = MemoryRegions | Modules | Threads,
|
||||
All = Processes | ProcessDetails
|
||||
}
|
||||
}
|
65
Services/Scanners/ThreadScanner.cs
Normal file
65
Services/Scanners/ThreadScanner.cs
Normal file
@ -0,0 +1,65 @@
|
||||
// File: Services/Scanners/ThreadScanner.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using WebmrAPI.Models;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services.Scanners
|
||||
{
|
||||
public class ThreadScanner : AbstractCpuScanner<ProcessThreadInfo>
|
||||
{
|
||||
override public ScanTarget Target { get => ScanTarget.Threads; }
|
||||
internal int PID { get; private set; }
|
||||
|
||||
override internal bool Scan(out Dictionary<long, ProcessThreadInfo>? data)
|
||||
{
|
||||
data = new();
|
||||
|
||||
try
|
||||
{
|
||||
using (var process = Process.GetProcessById(PID))
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
{
|
||||
ProcessThreadInfo info;
|
||||
|
||||
if (GetFromCacheOrNew(thread.Id, out info))
|
||||
{
|
||||
info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.ID = thread.Id;
|
||||
}
|
||||
|
||||
info.BasePriority = thread.BasePriority;
|
||||
info.CurrentPriority = thread.CurrentPriority;
|
||||
info.TotalProcessorTime = thread.TotalProcessorTime;
|
||||
|
||||
data.Add(info.ID, info);
|
||||
}
|
||||
}
|
||||
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Access denied to process {PID} for thread scanning. Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Provider.Logger.LogWarning($"Process {PID} might have exited during thread enumeration. Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Provider.Logger.LogError($"An unexpected error occurred while scanning threads for PID {PID}. Error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public ThreadScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessThreadInfo> container, int pid)
|
||||
: base(scanner, container)
|
||||
{
|
||||
PID = pid;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,251 +0,0 @@
|
||||
// File: Services/WinApi.cs
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.Json.Serialization;
|
||||
using WebmrAPI.Utils;
|
||||
|
||||
namespace WebmrAPI.Services
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class WinApi
|
||||
{
|
||||
public const uint PROCESS_QUERY_INFORMATION = 0x00000400;
|
||||
public const uint PROCESS_VM_READ = 0x00000010;
|
||||
public const uint PROCESS_VM_OPERATION = 0x00000008;
|
||||
|
||||
public static int LastError { get => Marshal.GetLastWin32Error(); }
|
||||
public static uint MBISize { get => (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)); }
|
||||
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryState>))]
|
||||
public enum MemoryState : uint
|
||||
{
|
||||
Undefined = 0,
|
||||
Commit = 0x00001000,
|
||||
Reserve = 0x00002000,
|
||||
Free = 0x00010000,
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryType>))]
|
||||
public enum MemoryType : uint
|
||||
{
|
||||
Undefined = 0,
|
||||
Image = 0x01000000,
|
||||
Mapped = 0x00040000,
|
||||
Private = 0x00020000,
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryPageProtectionState>))]
|
||||
public enum MemoryPageProtectionState : uint
|
||||
{
|
||||
Undefined = 0,
|
||||
NoAccess = 0x0001,
|
||||
ReadOnly = 0x0002,
|
||||
ReadWrite = 0x0004,
|
||||
WriteCopy = 0x0008,
|
||||
Execute = 0x0010,
|
||||
ExecuteRead = 0x0020,
|
||||
ExecuteReadWrite = 0x0040,
|
||||
ExecuteWriteCopy = 0x0080,
|
||||
Guard = 0x0100,
|
||||
NoCache = 0x0200,
|
||||
WriteCombine = 0x0400,
|
||||
}
|
||||
|
||||
// --- P/Invoke Declarations ---
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MEMORY_BASIC_INFORMATION
|
||||
{
|
||||
public IntPtr BaseAddress;
|
||||
public IntPtr AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public ushort PartitionId;
|
||||
public UIntPtr RegionSize;
|
||||
public MemoryState State;
|
||||
public MemoryPageProtectionState PageProtection;
|
||||
public MemoryType Type;
|
||||
}
|
||||
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr OpenProcess(
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
uint dwProcessId
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-virtualqueryex
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern int VirtualQueryEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
out MEMORY_BASIC_INFORMATION lpBuffer,
|
||||
uint dwLength
|
||||
);
|
||||
|
||||
// --- Module-related P/Invokes (from psapi.dll and kernel32.dll) ---
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
public static extern bool EnumProcessModulesEx(
|
||||
IntPtr hProcess,
|
||||
[Out] IntPtr[] lphModule, // Array to receive module handles
|
||||
uint cb, // Size of the lphModule array, in bytes
|
||||
out uint lpcbNeeded, // Number of bytes required to store all module handles
|
||||
uint dwFilterFlag // Filter for modules (e.g., LIST_MODULES_ALL)
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexw
|
||||
[DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint GetModuleFileNameEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
[Out] System.Text.StringBuilder lpFilename,
|
||||
uint nSize
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamew
|
||||
[DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint GetModuleBaseName(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
[Out] System.Text.StringBuilder lpBaseName,
|
||||
uint nSize
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-_moduleinfo
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MODULEINFO
|
||||
{
|
||||
public IntPtr lpBaseOfDll;
|
||||
public uint SizeOfImage;
|
||||
public IntPtr EntryPoint;
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmoduleinformation
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
public static extern bool GetModuleInformation(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
out MODULEINFO lpmodinfo,
|
||||
uint cb
|
||||
);
|
||||
|
||||
// --- Thread-related P/Invokes (from kernel32.dll and ntdll.dll) ---
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr OpenThread(
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
uint dwThreadId
|
||||
);
|
||||
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationthread
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern int NtQueryInformationThread(
|
||||
IntPtr ThreadHandle,
|
||||
THREAD_INFO_CLASS ThreadInformationClass,
|
||||
IntPtr ThreadInformation, // Pointer to buffer
|
||||
uint ThreadInformationLength,
|
||||
out uint ReturnLength
|
||||
);
|
||||
|
||||
// Enum for ThreadInformationClass in NtQueryInformationThread
|
||||
// https://ntdoc.m417z.com/threadinfoclass
|
||||
public enum THREAD_INFO_CLASS : int
|
||||
{
|
||||
ThreadBasicInformation, // q: THREAD_BASIC_INFORMATION
|
||||
ThreadTimes, // q: KERNEL_USER_TIMES
|
||||
ThreadPriority, // s: KPRIORITY (requires SeIncreaseBasePriorityPrivilege)
|
||||
ThreadBasePriority, // s: KPRIORITY
|
||||
ThreadAffinityMask, // s: KAFFINITY
|
||||
ThreadImpersonationToken, // s: HANDLE
|
||||
ThreadDescriptorTableEntry, // q: DESCRIPTOR_TABLE_ENTRY (or WOW64_DESCRIPTOR_TABLE_ENTRY)
|
||||
ThreadEnableAlignmentFaultFixup, // s: BOOLEAN
|
||||
ThreadEventPair, // Obsolete
|
||||
ThreadQuerySetWin32StartAddress, // qs: PVOID (requires THREAD_Set_LIMITED_INFORMATION)
|
||||
ThreadZeroTlsCell, // s: ULONG // TlsIndex // 10
|
||||
ThreadPerformanceCount, // q: LARGE_INTEGER
|
||||
ThreadAmILastThread, // q: ULONG
|
||||
ThreadIdealProcessor, // s: ULONG
|
||||
ThreadPriorityBoost, // qs: ULONG
|
||||
ThreadSetTlsArrayAddress, // s: ULONG_PTR
|
||||
ThreadIsIoPending, // q: ULONG
|
||||
ThreadHideFromDebugger, // q: BOOLEAN; s: void
|
||||
ThreadBreakOnTermination, // qs: ULONG
|
||||
ThreadSwitchLegacyState, // s: void // NtCurrentThread // NPX/FPU
|
||||
ThreadIsTerminated, // q: ULONG // 20
|
||||
ThreadLastSystemCall, // q: THREAD_LAST_SYSCALL_INFORMATION
|
||||
ThreadIoPriority, // qs: IO_PRIORITY_HINT (requires SeIncreaseBasePriorityPrivilege)
|
||||
ThreadCycleTime, // q: THREAD_CYCLE_TIME_INFORMATION (requires THREAD_QUERY_LIMITED_INFORMATION)
|
||||
ThreadPagePriority, // qs: PAGE_PRIORITY_INFORMATION
|
||||
ThreadActualBasePriority, // s: LONG (requires SeIncreaseBasePriorityPrivilege)
|
||||
ThreadTebInformation, // q: THREAD_TEB_INFORMATION (requires THREAD_GET_CONTEXT + THREAD_SET_CONTEXT)
|
||||
ThreadCSwitchMon, // Obsolete
|
||||
ThreadCSwitchPmu, // Obsolete
|
||||
ThreadWow64Context, // qs: WOW64_CONTEXT, ARM_NT_CONTEXT since 20H1
|
||||
ThreadGroupInformation, // qs: GROUP_AFFINITY // 30
|
||||
ThreadUmsInformation, // q: THREAD_UMS_INFORMATION // Obsolete
|
||||
ThreadCounterProfiling, // q: BOOLEAN; s: THREAD_PROFILING_INFORMATION?
|
||||
ThreadIdealProcessorEx, // qs: PROCESSOR_NUMBER; s: previous PROCESSOR_NUMBER on return
|
||||
ThreadCpuAccountingInformation, // q: BOOLEAN; s: HANDLE (NtOpenSession) // NtCurrentThread // since WIN8
|
||||
ThreadSuspendCount, // q: ULONG // since WINBLUE
|
||||
ThreadHeterogeneousCpuPolicy, // q: KHETERO_CPU_POLICY // since THRESHOLD
|
||||
ThreadContainerId, // q: GUID
|
||||
ThreadNameInformation, // qs: THREAD_NAME_INFORMATION (requires THREAD_SET_LIMITED_INFORMATION)
|
||||
ThreadSelectedCpuSets, // q: ULONG[]
|
||||
ThreadSystemThreadInformation, // q: SYSTEM_THREAD_INFORMATION // 40
|
||||
ThreadActualGroupAffinity, // q: GROUP_AFFINITY // since THRESHOLD2
|
||||
ThreadDynamicCodePolicyInfo, // q: ULONG; s: ULONG (NtCurrentThread)
|
||||
ThreadExplicitCaseSensitivity, // qs: ULONG; s: 0 disables, otherwise enables // (requires SeDebugPrivilege and PsProtectedSignerAntimalware)
|
||||
ThreadWorkOnBehalfTicket, // ALPC_WORK_ON_BEHALF_TICKET // RTL_WORK_ON_BEHALF_TICKET_EX // NtCurrentThread
|
||||
ThreadSubsystemInformation, // q: SUBSYSTEM_INFORMATION_TYPE // since REDSTONE2
|
||||
ThreadDbgkWerReportActive, // s: ULONG; s: 0 disables, otherwise enables
|
||||
ThreadAttachContainer, // s: HANDLE (job object) // NtCurrentThread
|
||||
ThreadManageWritesToExecutableMemory, // MANAGE_WRITES_TO_EXECUTABLE_MEMORY // since REDSTONE3
|
||||
ThreadPowerThrottlingState, // qs: POWER_THROTTLING_THREAD_STATE // since REDSTONE3 (set), WIN11 22H2 (query)
|
||||
ThreadWorkloadClass, // THREAD_WORKLOAD_CLASS // since REDSTONE5 // 50
|
||||
ThreadCreateStateChange, // since WIN11
|
||||
ThreadApplyStateChange,
|
||||
ThreadStrongerBadHandleChecks, // s: ULONG // NtCurrentThread // since 22H1
|
||||
ThreadEffectiveIoPriority, // q: IO_PRIORITY_HINT
|
||||
ThreadEffectivePagePriority, // q: ULONG
|
||||
ThreadUpdateLockOwnership, // THREAD_LOCK_OWNERSHIP // since 24H2
|
||||
ThreadSchedulerSharedDataSlot, // SCHEDULER_SHARED_DATA_SLOT_INFORMATION
|
||||
ThreadTebInformationAtomic, // q: THREAD_TEB_INFORMATION (requires THREAD_GET_CONTEXT + THREAD_QUERY_INFORMATION)
|
||||
ThreadIndexInformation, // THREAD_INDEX_INFORMATION
|
||||
MaxThreadInfoClass
|
||||
}
|
||||
|
||||
// Structure for ThreadBasicInformation (if needed, though ThreadQuerySetWin32StartAddress directly gives address)
|
||||
// https://ntdoc.m417z.com/thread_basic_information
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct THREAD_BASIC_INFORMATION
|
||||
{
|
||||
public int ExitStatus;
|
||||
public IntPtr TebBaseAddress;
|
||||
public CLIENT_ID ClientId;
|
||||
public IntPtr AffinityMask;
|
||||
public int Priority;
|
||||
public int BasePriority;
|
||||
}
|
||||
|
||||
// https://ntdoc.m417z.com/client_id
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CLIENT_ID
|
||||
{
|
||||
public IntPtr UniqueProcess;
|
||||
public IntPtr UniqueThread;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
// File: Models/ConcurrentDTO.cs
|
||||
// File: Utils/ConcurrentObject.cs
|
||||
|
||||
namespace WebmrAPI.Models
|
||||
namespace WebmrAPI.Utils
|
||||
{
|
||||
public class ConcurrentDTO
|
||||
public class ConcurrentObject
|
||||
{
|
||||
internal readonly object _lock = new object();
|
||||
|
55
Utils/LazyConcurrentContainer.cs
Normal file
55
Utils/LazyConcurrentContainer.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// File: Utils/LazyConcurrentContainer.cs
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace WebmrAPI.Utils
|
||||
{
|
||||
public class LazyConcurrentContainer<T> : ConcurrentObject
|
||||
{
|
||||
private DateTime _lastScan = DateTime.MinValue;
|
||||
private DateTime _currentScan = DateTime.MinValue;
|
||||
private ConcurrentDictionary<long, T>? _container;
|
||||
|
||||
public DateTime LastUpdate
|
||||
{
|
||||
get => LockedGet(ref _lastScan);
|
||||
}
|
||||
|
||||
public TimeSpan Elapsed
|
||||
{
|
||||
get { lock (_lock) return _currentScan - _lastScan; }
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<long, T>? Container
|
||||
{
|
||||
get => LockedGet(ref _container);
|
||||
set
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_lastScan = _currentScan;
|
||||
_currentScan = DateTime.UtcNow;
|
||||
_container = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T>? Values
|
||||
{
|
||||
get => _container?.Values.ToList();
|
||||
}
|
||||
|
||||
public void SetContainer(Dictionary<long, T>? container)
|
||||
{
|
||||
Container = container != null ? new ConcurrentDictionary<long, T>(container) : null;
|
||||
}
|
||||
public void SetContainer(ConcurrentDictionary<long, T> container)
|
||||
{
|
||||
Container = container != null ? new ConcurrentDictionary<long, T>(container) : null;
|
||||
}
|
||||
public void CleanContainer()
|
||||
{
|
||||
Container = null;
|
||||
}
|
||||
}
|
||||
}
|
248
Utils/WindowsProcess.cs
Normal file
248
Utils/WindowsProcess.cs
Normal file
@ -0,0 +1,248 @@
|
||||
// File: Utils/WindowsProcess.cs
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.Json.Serialization;
|
||||
using WebmrAPI.Exceptions;
|
||||
using WebmrAPI.Models;
|
||||
|
||||
namespace WebmrAPI.Utils
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class WindowsProcess : IDisposable
|
||||
{
|
||||
private IntPtr _ptr = IntPtr.Zero;
|
||||
private int _pid = 0;
|
||||
private bool _readAccess = false;
|
||||
private static int LastError { get => Marshal.GetLastWin32Error(); }
|
||||
private static uint MBISize { get => (uint)Marshal.SizeOf(typeof(MemoryBasicInformation)); }
|
||||
|
||||
[Flags]
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryState>))]
|
||||
public enum MemoryState : uint
|
||||
{
|
||||
Commit = 0x00001000,
|
||||
Reserve = 0x00002000,
|
||||
Free = 0x00010000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryType>))]
|
||||
public enum MemoryType : uint
|
||||
{
|
||||
Image = 0x01000000,
|
||||
Mapped = 0x00040000,
|
||||
Private = 0x00020000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[JsonConverter(typeof(JsonEnumConverter<MemoryPageProtectionState>))]
|
||||
public enum MemoryPageProtectionState : uint
|
||||
{
|
||||
NoAccess = 0x0001,
|
||||
ReadOnly = 0x0002,
|
||||
ReadWrite = 0x0004,
|
||||
WriteCopy = 0x0008,
|
||||
Execute = 0x0010,
|
||||
ExecuteRead = 0x0020,
|
||||
ExecuteReadWrite = 0x0040,
|
||||
ExecuteWriteCopy = 0x0080,
|
||||
Guard = 0x0100,
|
||||
NoCache = 0x0200,
|
||||
WriteCombine = 0x0400,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ModuleFilterFlags : uint
|
||||
{
|
||||
Default = 0x00,
|
||||
Modules32Bit = 0x01,
|
||||
Modules64Bit = 0x02,
|
||||
ModulesAll = Modules32Bit | Modules64Bit,
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MemoryBasicInformation
|
||||
{
|
||||
public IntPtr BaseAddress;
|
||||
public IntPtr AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public ushort PartitionId;
|
||||
public UIntPtr RegionSize;
|
||||
public MemoryState State;
|
||||
public MemoryPageProtectionState PageProtection;
|
||||
public MemoryType Type;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ModuleInformation
|
||||
{
|
||||
public IntPtr BaseAddress;
|
||||
public uint MemorySize;
|
||||
public IntPtr EntryPointAddress;
|
||||
public System.Text.StringBuilder Name;
|
||||
public System.Text.StringBuilder FileName;
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(
|
||||
uint dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
|
||||
int dwProcessId
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-virtualqueryex
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern int VirtualQueryEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpAddress,
|
||||
out MemoryBasicInformation lpBuffer,
|
||||
uint dwLength
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
private static extern bool EnumProcessModulesEx(
|
||||
IntPtr hProcess,
|
||||
[Out] IntPtr[] lphModule,
|
||||
uint cb,
|
||||
out uint lpcbNeeded,
|
||||
ModuleFilterFlags dwFilterFlag
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexw
|
||||
[DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetModuleFileNameEx(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
[Out] System.Text.StringBuilder lpFilename,
|
||||
uint nSize
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamew
|
||||
[DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetModuleBaseName(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
[Out] System.Text.StringBuilder lpBaseName,
|
||||
uint nSize
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmoduleinformation
|
||||
[DllImport("psapi.dll", SetLastError = true)]
|
||||
private static extern bool GetModuleInformation(
|
||||
IntPtr hProcess,
|
||||
IntPtr hModule,
|
||||
out ModuleInformation lpmodinfo,
|
||||
uint cb
|
||||
);
|
||||
|
||||
public void ForeachMemoryRegion(Action<MemoryBasicInformation> callback, MemoryState filter = MemoryState.Commit | MemoryState.Reserve)
|
||||
{
|
||||
IntPtr cur = IntPtr.Zero;
|
||||
long next = 0;
|
||||
var regions = new Dictionary<long, MemoryRegionInfo>();
|
||||
|
||||
while ((next > cur.ToInt64() && next < 0x7fffffffffff) || next == 0)
|
||||
{
|
||||
MemoryBasicInformation info;
|
||||
|
||||
cur = new IntPtr(next);
|
||||
|
||||
if (VirtualQueryEx(_ptr, cur, out info, MBISize) == 0)
|
||||
{
|
||||
throw new MemoryRegionException($"WinAPI call 'VirtualQueryEx for PID {_pid} at address 0x{cur.ToInt64():X16}' failed.");
|
||||
}
|
||||
|
||||
if ((filter & info.State) != 0)
|
||||
{
|
||||
callback(info);
|
||||
}
|
||||
|
||||
next = info.BaseAddress.ToInt64() + (long)info.RegionSize.ToUInt64();
|
||||
}
|
||||
}
|
||||
|
||||
public void ForeachLoadedModule(Action<ModuleInformation> callback, ModuleFilterFlags filter = ModuleFilterFlags.ModulesAll, bool force = false, bool ignoreErrors = false)
|
||||
{
|
||||
if (!_readAccess && force)
|
||||
{
|
||||
Dispose();
|
||||
_readAccess = true;
|
||||
Open();
|
||||
}
|
||||
|
||||
IntPtr[] modules = new IntPtr[1024];
|
||||
uint nmemb;
|
||||
|
||||
if (EnumProcessModulesEx(_ptr, modules, (uint)(modules.Length * IntPtr.Size), out nmemb, filter))
|
||||
{
|
||||
uint count = nmemb / (uint)IntPtr.Size;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ModuleInformation info;
|
||||
|
||||
info.Name = new System.Text.StringBuilder(256);
|
||||
info.FileName = new System.Text.StringBuilder(1024);
|
||||
|
||||
if ((!GetModuleInformation(_ptr, modules[i], out info, (uint)(2 * IntPtr.Size + sizeof(uint))) ||
|
||||
GetModuleBaseName(_ptr, modules[i], info.Name, (uint)info.Name.Capacity) <= 0 ||
|
||||
GetModuleFileNameEx(_ptr, modules[i], info.FileName, (uint)info.FileName.Capacity) <= 0) && !ignoreErrors)
|
||||
{
|
||||
throw new GettingModuleInfoException($"Failed to get module info for module handle {modules[i]} in PID {_pid}. Error: {LastError}");
|
||||
}
|
||||
|
||||
callback(info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ProcessMonitorException($"EnumProcessModulesEx for PID {_pid} failed. Error: {LastError}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
var access = (uint)(_readAccess ? 0x00000010 : 0) | 0x00000400;
|
||||
_ptr = OpenProcess(access, false, _pid);
|
||||
|
||||
if (_ptr == IntPtr.Zero)
|
||||
{
|
||||
var errno = LastError;
|
||||
|
||||
if (errno == 5)
|
||||
{
|
||||
throw new ProcessAccessDeniedException($"Access denied to process {_pid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ProcessMonitorException($"WinAPI call 'OpenProcess for PID {_pid}' failed. Error: {errno}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WindowsProcess(int pid, bool readAccess = false)
|
||||
{
|
||||
_readAccess = readAccess;
|
||||
_pid = pid;
|
||||
Open();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ptr != IntPtr.Zero) CloseHandle(_ptr);
|
||||
}
|
||||
|
||||
~WindowsProcess()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<Version>0.1.1</Version>
|
||||
<Version>0.1.2</Version>
|
||||
<Company>OpenSource</Company>
|
||||
<Product>Process Monitoring Agent</Product>
|
||||
<Description>A service for detailed monitoring processes and memory regions.</Description>
|
||||
|
Loading…
Reference in New Issue
Block a user