From 7543d9d4add3e1fb12c54bcc876bcedf4d92e4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=B3=E5=A4=B4?= Date: Mon, 22 Jul 2024 14:48:22 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E8=AF=BB=E5=8F=96PEB?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BD=93=EF=BC=8C=E9=81=BF=E5=85=8D=E7=A1=AC?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=81=8F=E7=A7=BB=E9=87=8F=E6=89=80=E5=B8=A6?= =?UTF-8?q?=E6=9D=A5=E7=9A=84x86/x64=E6=95=B0=E5=80=BC=E5=81=8F=E5=B7=AE?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewLife.Core/Extension/ProcessHelper.cs | 175 ++++++++++++------ .../Extension/ProcessHelperTests.cs | 22 ++- 2 files changed, 137 insertions(+), 60 deletions(-) diff --git a/NewLife.Core/Extension/ProcessHelper.cs b/NewLife.Core/Extension/ProcessHelper.cs index a0fd80412..590e0dc89 100644 --- a/NewLife.Core/Extension/ProcessHelper.cs +++ b/NewLife.Core/Extension/ProcessHelper.cs @@ -95,6 +95,32 @@ public static String GetProcessName2(this Process process) // } //} + /// 获取指定进程的命令行参数 + /// + /// + public static String? GetCommandLine(Int32 processId) + { + if (Runtime.Linux) + { + try + { + var file = $"/proc/{processId}/cmdline"; + if (File.Exists(file)) + { + var lines = File.ReadAllText(file).Trim('\0', ' ').Split('\0'); + return lines.Join(" "); + } + } + catch { } + } + else if (Runtime.Windows) + { + return GetCommandLineOnWindows(processId); + } + + return null; + } + /// 获取指定进程的命令行参数 /// /// @@ -116,47 +142,85 @@ public static String GetProcessName2(this Process process) } else if (Runtime.Windows) { - var processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId); - if (processHandle == IntPtr.Zero) - return null; + var str = GetCommandLineOnWindows(processId); + if (str.IsNullOrEmpty()) return []; - try - { - var pbi = new PROCESS_BASIC_INFORMATION(); - var status = NtQueryInformationProcess(processHandle, 0, ref pbi, (UInt32)Marshal.SizeOf(pbi), out _); - if (status != 0) return null; + // 分割参数,特殊支持双引号 + return CommandParser.Split(str); + } + + return null; + } - var buffer = new Byte[IntPtr.Size]; - var rs = ReadProcessMemory(processHandle, pbi.PebBaseAddress + 0x20, buffer, (UInt32)buffer.Length, out _); - if (!rs) return null; + private static String? GetCommandLineOnWindows(Int32 processId) + { + var processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId); + if (processHandle == IntPtr.Zero) + return null; + + try + { + var pbi = new PROCESS_BASIC_INFORMATION(); + var status = NtQueryInformationProcess(processHandle, 0, ref pbi, (UInt32)Marshal.SizeOf(pbi), out _); + if (status != 0) return null; - var paramAddress = (IntPtr)BitConverter.ToInt64(buffer, 0); - buffer = new Byte[Marshal.SizeOf(typeof(UNICODE_STRING))]; - rs = ReadProcessMemory(processHandle, paramAddress + 0x70, buffer, (UInt32)buffer.Length, out _); - if (!rs) return null; + var rs = ReadStruct(processHandle, pbi.PebBaseAddress, out var peb); + if (!rs) return null; - var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); - var us = (UNICODE_STRING)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(UNICODE_STRING))!; - handle.Free(); + rs = ReadStruct(processHandle, peb.ProcessParameters, out var upp); + if (!rs) return null; - buffer = new Byte[us.Length]; - rs = ReadProcessMemory(processHandle, us.Buffer, buffer, (UInt32)buffer.Length, out _); - if (!rs) return null; + rs = ReadStringUni(processHandle, upp.CommandLine, out var commandLine); + if (!rs) return null; - var commandLine = Encoding.Unicode.GetString(buffer); - var str = commandLine.TrimEnd('\0'); - if (str.IsNullOrEmpty()) return []; + return commandLine?.TrimEnd('\0'); + } + finally + { + CloseHandle(processHandle); + } + } - // 分割参数,特殊支持双引号 - return CommandParser.Split(str); + private static Boolean ReadStruct(IntPtr hProcess, IntPtr lpBaseAddress, out T val) + { + val = default!; + var size = Marshal.SizeOf(typeof(T)); + var ptr = Marshal.AllocHGlobal(size); + try + { + if (ReadProcessMemory(hProcess, lpBaseAddress, ptr, (UInt32)size, out var len) && len == size) + { + val = (T)Marshal.PtrToStructure(ptr, typeof(T))!; + return true; } - finally + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return false; + } + + private static Boolean ReadStringUni(IntPtr hProcess, UNICODE_STRING us, out String? val) + { + val = default; + var size = us.MaximumLength; + var ptr = Marshal.AllocHGlobal(size); + try + { + if (ReadProcessMemory(hProcess, us.Buffer, ptr, size, out var len) && len == size) { - CloseHandle(processHandle); + val = Marshal.PtrToStringUni(ptr); + return true; } } + finally + { + Marshal.FreeHGlobal(ptr); + } - return null; + return false; } #endregion @@ -400,27 +464,15 @@ public static Process ShellExecute(this String fileName, String? arguments = nul [DllImport("kernel32.dll", SetLastError = true)] static extern Boolean CloseHandle(IntPtr hObject); - [DllImport("psapi.dll", SetLastError = true)] - static extern Boolean EnumProcessModules(IntPtr hProcess, [Out] IntPtr[] lphModule, UInt32 cb, out UInt32 lpcbNeeded); - - [DllImport("psapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] - static extern UInt32 GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, StringBuilder lpBaseName, Int32 nSize); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern UInt32 QueryFullProcessImageName(IntPtr hProcess, Int32 dwFlags, StringBuilder lpExeName, ref Int32 lpdwSize); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private extern static Boolean DuplicateTokenEx(IntPtr hExistingToken, UInt32 dwDesiredAccess, IntPtr lpTokenAttributes, Int32 ImpersonationLevel, Int32 TokenType, out IntPtr phNewToken); - [DllImport("ntdll.dll")] private static extern Int32 NtQueryInformationProcess(IntPtr processHandle, Int32 processInformationClass, ref PROCESS_BASIC_INFORMATION processInformation, UInt32 processInformationLength, out UInt32 returnLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern Boolean ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] Byte[] lpBuffer, UInt32 size, out UInt32 lpNumberOfBytesRead); + [DllImport("kernel32.dll")] + private static extern Boolean ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, UInt32 nSize, out UInt32 lpNumberOfBytesRead); + const UInt32 PROCESS_QUERY_INFORMATION = 0x0400; const UInt32 PROCESS_VM_READ = 0x0010; @@ -429,10 +481,10 @@ private struct PROCESS_BASIC_INFORMATION { public IntPtr Reserved1; public IntPtr PebBaseAddress; - public IntPtr Reserved2; - public IntPtr Reserved3; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public IntPtr[] Reserved2; public IntPtr UniqueProcessId; - public IntPtr Reserved4; + public IntPtr Reserved3; } [StructLayout(LayoutKind.Sequential)] @@ -443,19 +495,26 @@ private struct UNICODE_STRING public IntPtr Buffer; } + // This is not the real struct! + // I faked it to get ProcessParameters address. + // Actual struct definition: + // https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb + [StructLayout(LayoutKind.Sequential)] + private struct PEB + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public IntPtr[] Reserved; + public IntPtr ProcessParameters; + } + [StructLayout(LayoutKind.Sequential)] - private struct RTL_USER_PROCESS_PARAMETERS + private struct RtlUserProcessParameters { - public Byte Reserved1; - public Byte Reserved2; - public UInt16 Reserved3; - public IntPtr Reserved4; - public IntPtr Reserved5; - public IntPtr Reserved6; - public IntPtr Reserved7; - public IntPtr Reserved8; - public IntPtr Reserved9; - public IntPtr Reserved10; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public Byte[] Reserved1; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public IntPtr[] Reserved2; + public UNICODE_STRING ImagePathName; public UNICODE_STRING CommandLine; } #endregion diff --git a/XUnitTest.Core/Extension/ProcessHelperTests.cs b/XUnitTest.Core/Extension/ProcessHelperTests.cs index 6e677aca1..df2dbf266 100644 --- a/XUnitTest.Core/Extension/ProcessHelperTests.cs +++ b/XUnitTest.Core/Extension/ProcessHelperTests.cs @@ -8,14 +8,32 @@ namespace XUnitTest.Extension; public class ProcessHelperTests { [Fact] - public void GetCommandLineArgs() + public void GetCommandLine() { foreach (var item in Process.GetProcesses()) { if (item.ProcessName == "dotnet") { - var cmd = ProcessHelper.GetCommandLineArgs(item.Id); + var cmd = ProcessHelper.GetCommandLine(item.Id); XTrace.WriteLine("{0}: {1}", item.ProcessName, cmd); + + Assert.Contains("dotnet.exe", cmd); + } + } + } + + [Fact] + public void GetCommandLineArgs() + { + foreach (var item in Process.GetProcesses()) + { + if (item.ProcessName == "dotnet") + { + var cmds = ProcessHelper.GetCommandLineArgs(item.Id); + XTrace.WriteLine("{0}: {1}", item.ProcessName, cmds.Join(" ")); + + Assert.True(cmds.Length >= 2); + Assert.EndsWith("dotnet.exe", cmds[0]); } } }