特定のプロセスが開いているファイルの一覧を取得する
そんなん、下の奴を見れば終わり:
っていう訳にもいきませぬ。
上の奴からたどれる範囲のコードでは、
32bit PID値に未対応
SYSTEM_HANDLE ではなく、SYSTEM_EXTENDED_HANDLE を使う。
ソース
次の様な使い方を想定:
foreach (var fn in ProcessUtils.EnumFilesOpened(hwnd)) { Console.WriteLine(fn); }
全ソース。あんまりコメントなし:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace Handles { public static class ProcessUtils { // HOWTO: Enumerate handles // http://forum.sysinternals.com/howto-enumerate-handles_topic18892.html public static IEnumerable<string> EnumFilesOpened(IntPtr hwnd) { uint pid; GetWindowThreadProcessId(hwnd, out pid); using (var proc = Process.GetProcessById((int)pid)) { IntPtr hProcess = proc.Handle; foreach (var shi in EnumHandles((int)pid)) { IntPtr hObj = IntPtr.Zero; try { if (!NT_SUCCESS(NtDuplicateObject(hProcess, shi.HandleValue, Process.GetCurrentProcess().Handle, out hObj, 0, 0, 0))) { continue; } using (var nto1 = new NtObject(hObj, ObjectInformationClass.ObjectTypeInformation, typeof(OBJECT_TYPE_INFORMATION))) { var oti = ObjectTypeInformationFromBuffer(nto1.Buffer); if (oti.Name.ToString() != "File") continue; } // this can lock NtQueryInformationFile (blocking IO named pipes), and // sometimes also NtQueryObject as well. // It is recommended to skip this granted access, // although a correct explanation is still to be found. // if (shi.GrantedAccess == 0x0012019f // seems mandatory (named pipes) || shi.GrantedAccess == 0x001a019f // blocking named pipe (not a file anyways) // Ignore certain flags combinations that are // frequently associated with system files and // folders. This gives us a large performance // advantage at the cost of some open file handles // potentially being missed. Since we don't have // control over what happens shortly after, we // cannot be perfect anyway. || shi.GrantedAccess == 0x00100000 // SYNCHRONIZE only || shi.GrantedAccess == 0x00160001 // used on directories || shi.GrantedAccess == 0x00100001 // used on directories || shi.GrantedAccess == 0x00100020) // used on SxS files { continue; } using (var nto2 = new NtObject(hObj, ObjectInformationClass.ObjectNameInformation, typeof(OBJECT_NAME_INFORMATION))) { var oni = ObjectNameInformationFromBuffer(nto2.Buffer); yield return GetRegularFileNameFromDevice(oni.Name.ToString()); } } finally { CloseHandle(hObj); } } } } /// <summary> /// Works much like as <c>(OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION))</c> /// </summary> /// <param name="buffer">Pointer to byte buffer of OBJECT_TYPE_INFORMATION.</param> /// <returns>C# interpretation of OBJECT_TYPE_INFORMATION; it contains references to the buffer /// and the buffer should not be released until finishing access to the structure.</returns> static OBJECT_TYPE_INFORMATION ObjectTypeInformationFromBuffer(IntPtr buffer) { #if USE_SAFE_CODE_ONLY return (OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION)); #else unsafe { return *(OBJECT_TYPE_INFORMATION*)buffer.ToPointer(); } #endif } /// <summary> /// Works much like as <c>(OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION))</c> /// </summary> /// <param name="buffer">Pointer to byte buffer of OBJECT_NAME_INFORMATION.</param> /// <returns>C# interpretation of OBJECT_NAME_INFORMATION; it contains references to the buffer /// and the buffer should not be released until finishing access to the structure.</returns> static OBJECT_NAME_INFORMATION ObjectNameInformationFromBuffer(IntPtr buffer) { #if USE_SAFE_CODE_ONLY return (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION)); #else unsafe { return *(OBJECT_NAME_INFORMATION*)buffer.ToPointer(); } #endif } class NtObject : IDisposable { public NtObject(IntPtr hObj, ObjectInformationClass infoClass, Type type) { Init(hObj, infoClass, Marshal.SizeOf(type)); } public NtObject(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize) { Init(hObj, infoClass, estimatedSize); } public void Init(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize) { Close(); // NOTE: // Buffer may be referenced by certain fields in m_obj and should not be // released before releasing m_obj. Buffer = Query(hObj, infoClass, estimatedSize); } public void Close() { if (Buffer != IntPtr.Zero) { Marshal.FreeCoTaskMem(Buffer); Buffer = IntPtr.Zero; } } public void Dispose() { Close(); } /// <summary> /// Return the buffer. You can use <see cref="Marshal.PtrToStructure"/> to get the actual /// structure from the buffer. /// Basically I want to provide NTObject<T> but C# generics does not allow me to /// use T* pointer if T is a generic parameter. /// </summary> public IntPtr Buffer { get; private set; } public static IntPtr Query(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize) { int size = estimatedSize; IntPtr buf = Marshal.AllocCoTaskMem(size); int retsize = 0; while (true) { var ret = NtQueryObject(hObj, infoClass, buf, size, out retsize); if (NT_SUCCESS(ret)) return buf; if (ret == NT_STATUS.INFO_LENGTH_MISMATCH || ret == NT_STATUS.BUFFER_OVERFLOW) { buf = Marshal.ReAllocCoTaskMem(buf, retsize); size = retsize; } else { Marshal.FreeCoTaskMem(buf); return IntPtr.Zero; } } } } static readonly string NETWORK_PREFIX = @"\Device\Mup\"; static string GetRegularFileNameFromDevice(string strRawName) { if (strRawName.StartsWith(NETWORK_PREFIX)) return @"\\" + strRawName.Substring(NETWORK_PREFIX.Length); string strFileName = strRawName; foreach (var drvPath in Environment.GetLogicalDrives()) { var drv = drvPath.Substring(0, 2); var sb = new StringBuilder(MAX_PATH); if (QueryDosDevice(drv, sb, MAX_PATH) == 0) return strRawName; string drvRoot = sb.ToString(); if (strFileName.StartsWith(drvRoot)) { strFileName = drv + strFileName.Substring(drvRoot.Length); break; } } return strFileName; } static SYSTEM_EXTENDED_HANDLE SystemExtendedHandleFromPtr(IntPtr ptr, int offset) { #if USE_SAFE_CODE_ONLY return (SYSTEM_EXTENDED_HANDLE)Marshal.PtrToStructure(buffer.Offset(offset), typeof(SYSTEM_EXTENDED_HANDLE)); #else unsafe { var p = (byte*)ptr.ToPointer() + offset; return *(SYSTEM_EXTENDED_HANDLE*)p; } #endif } static int lastSizeUsed = 0x10000; static IEnumerable<SYSTEM_EXTENDED_HANDLE> EnumHandles(int processId) { int size = lastSizeUsed; IntPtr buffer = Marshal.AllocCoTaskMem(size); try { int required; while (NtQuerySystemInformation(SystemExtendedHandleInformation, buffer, size, out required) == NT_STATUS.INFO_LENGTH_MISMATCH) { size = required; buffer = Marshal.ReAllocCoTaskMem(buffer, size); } // FIXME: it should be race condition safe... if (lastSizeUsed < size) lastSizeUsed = size; // sizeof(SYSTEM_HANDLE) is 16 on 32-bit and 42 on 64-bit due to padding issues int entrySize = Marshal.SizeOf(typeof(SYSTEM_EXTENDED_HANDLE)); int offset = Marshal.SizeOf(typeof(IntPtr)) * 2; int handleCount = Marshal.ReadInt32(buffer); for (int i = 0; i < handleCount; i++) { var shi = SystemExtendedHandleFromPtr(buffer, offset + entrySize * i); if (shi.UniqueProcessId != new IntPtr(processId)) continue; yield return shi; } } finally { if (buffer != IntPtr.Zero) Marshal.FreeCoTaskMem(buffer); } } static string GetProcessCommandLine(uint processId) { IntPtr hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId); if (hProcess == IntPtr.Zero) return string.Empty; try { return GetProcessCommandLine(hProcess); } catch { return string.Empty; } finally { CloseHandle(hProcess); } } static string GetProcessCommandLine(IntPtr hProcess) { int size; PROCESS_BASIC_INFORMATION pbi; int status = NtQueryInformationProcess( hProcess, PROCESSINFOCLASS.ProcessBasicInformation, out pbi, Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)), out size); int err = RtlNtStatusToDosError(status); if (err != 0) throw new Win32Exception(err); // PEB var peb = ObjectFromProcessMemory<PEB>(hProcess, pbi.PebBaseAddress); var upp = ObjectFromProcessMemory<RTL_USER_PROCESS_PARAMETERS>(hProcess, peb.ProcessParameters); if (upp.CommandLine.Length == 0) return string.Empty; return Encoding.Unicode.GetString(ReadProcessMemory(hProcess, upp.CommandLine.Buffer, upp.CommandLine.Length)); } static Type ObjectFromProcessMemory<Type>(IntPtr hProcess, IntPtr lpBaseAddress) where Type : struct { return (Type)ObjectFromProcessMemory(hProcess, lpBaseAddress, typeof(Type)); } static object ObjectFromProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Type type) { int size = Marshal.SizeOf(type); return BufferToStructure(ReadProcessMemory(hProcess, lpBaseAddress, size), type); } static byte[] ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int size) { byte[] buffer = new byte[size]; IntPtr bytesRead; if (ReadProcessMemory(hProcess, lpBaseAddress, buffer, new IntPtr(size), out bytesRead)) { if (bytesRead.ToInt32() < size) throw new ApplicationException( string.Format("Read only {0} of {1} bytes", bytesRead, size)); return buffer; } throw new Win32Exception(); } static object BufferToStructure(byte[] buffer, Type type) { unsafe { fixed (byte* p = buffer) { return Marshal.PtrToStructure(new IntPtr(p), type); } } } [StructLayout(LayoutKind.Sequential)] struct PROCESS_BASIC_INFORMATION { IntPtr Reserved1; public IntPtr PebBaseAddress; // PEB* IntPtr reserved2_0; IntPtr reserved2_1; public IntPtr UniqueProcessId; // ULONG_PTR IntPtr Reserved3; } [StructLayout(LayoutKind.Sequential)] struct PEB { IntPtr Reserved1; IntPtr Reserved2; IntPtr Reserved3; public IntPtr Ldr; // struct PEB_LDR_DATA* public IntPtr ProcessParameters; // RTL_USER_PROCESS_PARAMETERS* // ... } [StructLayout(LayoutKind.Sequential)] struct RTL_USER_PROCESS_PARAMETERS { byte b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15; IntPtr ip0, ip1, ip2, ip3, ip4, ip5, ip6, ip7, ip8, ip9; public UNICODE_STRING ImagePathName; public UNICODE_STRING CommandLine; } enum PROCESSINFOCLASS { ProcessBasicInformation = 0, ProcessDebugPort = 7, ProcessWow64Information = 26, ProcessImageFileName = 27, } [DllImport("ntdll.dll")] static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic, out PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize); [DllImport("ntdll.dll")] static extern int RtlNtStatusToDosError(int Status); [DllImport("kernel32.dll", SetLastError = true)] static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr OpenProcess( uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); const uint PROCESS_DUP_HANDLE = 0x0040; const uint PROCESS_QUERY_INFORMATION = 0x0400U; const uint PROCESS_VM_READ = 0x0010U; [DllImport("kernel32.dll")] static extern int CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", SetLastError = true)] public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); const int MAX_PATH = 260; [DllImport("ntdll.dll")] static extern NT_STATUS NtQuerySystemInformation( int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength); const int SystemHandleInformation = 16; struct SYSTEM_EXTENDED_HANDLE { public IntPtr Object; public IntPtr UniqueProcessId; public IntPtr HandleValue; public uint GrantedAccess; public ushort CreatorBackTraceIndex; public ushort ObjectTypeIndex; public uint HandleAttributes; public uint Reserved; } const int SystemExtendedHandleInformation = 64; enum NT_STATUS : uint { SUCCESS = 0x00000000, BUFFER_OVERFLOW = 0x80000005, INFO_LENGTH_MISMATCH = 0xC0000004 } static bool NT_SUCCESS(NT_STATUS status) { return ((uint)status & 0x80000000) == 0; } [DllImport("ntdll.dll")] static extern NT_STATUS NtDuplicateObject( IntPtr SourceProcessHandle, IntPtr SourceHandle, IntPtr TargetProcessHandle, out IntPtr TargetHandle, uint DesiredAccess, uint Attributes, uint Options); [DllImport("ntdll.dll")] static extern NT_STATUS NtQueryObject( IntPtr ObjectHandle, ObjectInformationClass ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, out int returnLength); enum ObjectInformationClass : int { ObjectBasicInformation = 0, ObjectNameInformation = 1, ObjectTypeInformation = 2, ObjectAllTypesInformation = 3, ObjectHandleInformation = 4 } [StructLayout(LayoutKind.Sequential)] struct OBJECT_NAME_INFORMATION { // Information Class 1 public UNICODE_STRING Name; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct UNICODE_STRING { // NOTE: // UNICODE_STRING should be 8 byte on 32-bit and 16 byte on 64-bit // and reserved will condition it correctly. private IntPtr reserved; public IntPtr Buffer; public ushort Length { get { return (ushort)(reserved.ToInt64() & 0xffff); } } public ushort MaximumLength { get { return (ushort)(reserved.ToInt64() >> 16); } } public override string ToString() { if (Buffer == IntPtr.Zero) return ""; return Marshal.PtrToStringUni(Buffer, Wcslen()); } /// <summary> /// Calculate string length in C's wcslen compatible way. /// </summary> /// <returns>Length of the string.</returns> public int Wcslen() { unsafe { ushort* p = (ushort*)Buffer.ToPointer(); for (ushort i = 0; i < Length; i++) { if (p[i] == 0) return i; } return Length; } } } [StructLayout(LayoutKind.Sequential)] struct GENERIC_MAPPING { public int GenericRead; public int GenericWrite; public int GenericExecute; public int GenericAll; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct OBJECT_TYPE_INFORMATION { // Information Class 2 public UNICODE_STRING Name; public uint TotalNumberOfObjects; public uint TotalNumberOfHandles; public uint TotalPagedPoolUsage; public uint TotalNonPagedPoolUsage; public uint TotalNamePoolUsage; public uint TotalHandleTableUsage; public uint HighWaterNumberOfObjects; public uint HighWaterNumberOfHandles; public uint HighWaterPagedPoolUsage; public uint HighWaterNonPagedPoolUsage; public uint HighWaterNamePoolUsage; public uint HighWaterHandleTableUsage; public uint InvalidAttributes; public GENERIC_MAPPING GenericMapping; public uint ValidAccess; public byte SecurityRequired; public byte MaintainHandleCount; public ushort MaintainTypeList; public int PoolType; public int PagedPoolUsage; public int NonPagedPoolUsage; } } /// <summary> /// Extension methods for <see cref="IntPtr"/>. /// </summary> static class Helper { /// <summary> /// Offset the specified pointer. /// </summary> /// <param name="ptr">Base pointer.</param> /// <param name="offset">Offset in bytes.</param> /// <returns>New pointer.</returns> public static IntPtr Offset(this IntPtr ptr, int offset) { return new IntPtr(ptr.ToInt64() + offset); } /// <summary> /// Size of <see cref="IntPtr"/> value. /// </summary> public static readonly int PTR_SIZE = Marshal.SizeOf(typeof(IntPtr)); } }