성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 닷넷에서 프로세스가 열고 있는 파일 목록을 구하는 방법</h1> <p> 프로세스(EXE)에서 열고 있는 파일을 열거하는 방법이 마이크로소프트 코드 사이트에 공개되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > File handle operations demo (CppFileHandle) ; https://code.msdn.microsoft.com/windowsapps/CppFileHandle-03c8ea0b </pre> <br /> 위의 방법은 C++ 코드인데 이를 보고 PInvoke를 이용해 C#으로 변경할 수 있겠지만 이미 그런 작업을 해둔 분이 계십니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Enumerate Open/Locked File Handles by Process ; https://gallery.technet.microsoft.com/scriptcenter/Open-Locked-File-Handles-faffd369 </pre> <br /> 그냥 가져다 쓰시면 됩니다. ^^<br /> <br /> 방법은 대략 다음과 같은 단계로 요약됩니다.<br /> <br /> <ol> <li>임의의 크기로 메모리 할당을 받는다.</li> <li>1번 과정에서 할당받은 영역을 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation'>NtQuerySystemInformation</a>에 전달. 만약 핸들 정보를 담기에 메모리가 부족하면 4번째 인자에 필요한 메모리의 크기와 함께 STATUS_INFO_LENGTH_MISMATCH를 반환 (성공한 경우 STATUS_SUCCESS(0)을 반환)</li> <li>2번 과정에서 STATUS_INFO_LENGTH_MISMATCH가 반환된 경우, 4번째 인자에 해당하는 크기의 메모리를 다시 할당받아 NtQuerySystemInformation에 전달. 이번에도 STATUS_INFO_LENGTH_MISMATCH 반환이 나올 수 있는데, 왜냐하면 1번 호출한 이후 그 와중에 새롭게 핸들이 열렸다면 메모리 크기가 늘어나기 때문. </li> </ol> <br /> 그런데, 위의 소스코드 모두 특정 환경에서 두 번째 NtQuerySystemInformation의 반환값이 STATUS_SUCCESS임에도 불구하고 핸들의 수가 764504201192와 같은 식의 말도 안되는 값이 나오는 경우가 있었습니다. 100% 재현되는 환경은 다음과 같습니다.<br /> <br /> <ul> <li>윈도우 서버 2012 클린 설치</li> <li>.NET 4.5로 컴파일(.NET 4.0 이하로 컴파일하면 정상 동작)</li> </ul> <br /> 위의 이유 때문에 ^^ 지난 글이 써진 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 4.0과 .NET 4.5의 컴파일 결과 차이점 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/10831'>http://www.sysnet.pe.kr/2/0/10831</a> </pre> <br /> 일단, "High Entropy Virtual Addresses" 옵션이 영향을 주긴 했지만 근본적인 문제는 그것이 아니었습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그럼, 원인 파악을 좀 해볼까요? ^^<br /> <br /> 문제를 쉽게 하기 위해 C++로 다음의 코드를 작성했습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int length = 0x1000; LPVOID pVoid = ::GlobalAlloc(0, length); ULONG returnLength = 0; NTSTATUS ntResult = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)16, (LPVOID)pVoid, length, &returnLength); if (ntResult == STATUS_INFO_LENGTH_MISMATCH) { // Round required memory up to the nearest 64KB boundary. length = ((returnLength + 0xffff) & ~0xffff); } ::GlobalFree(pVoid); pVoid = ::GlobalAlloc(0, length); ntResult = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)16, (LPVOID)pVoid, length, &returnLength); if (ntResult == STATUS_SUCCESS) { <span style='color: blue; font-weight: bold'>__int64 *lResult = (__int64 *)pVoid; printf("%I64d\n", *lResult); // 이 값이 바로 열린 핸들의 수를 나타냄</span> } else { printf("ntquery failed: %d\n", ntResult); } </pre> <br /> 위의 코드를 컴파일해 윈도우 서버 2012 (클린 설치)에서 실행하면 문제가 발생하는 것을 확인할 수 있습니다. 위에서는 GlobalAlloc을 사용했지만 "<a target='tab' href='https://code.msdn.microsoft.com/windowsapps/CppFileHandle-03c8ea0b'>File handle operations demo (CppFileHandle)</a>" 예제에서처럼 "HeapAlloc(GetProcessHeap(), 0, nSize);" 코드를 사용해도 마찬가지입니다.<br /> <br /> 제가 가진 윈도우 10에서 실행하면 "123995"와 같은 수가 나오지만 윈도우 서버 2012 R2에서는 "6944656590838556396"와 같은 황당한 수가 나옵니다. 상식적으로 저렇게 많은 핸들이 열렸을 리도 없고 실제로 저 수만 믿고 핸들 정보를 열람하게 되면 STATUS_ACCESS_VIOLATION(0xC0000005) 오류가 발생하게 됩니다.<br /> <br /> 문제인 즉!<br /> <br /> 할당된 메모리에 대한 0 초기화가 이뤄져야 한다는 것입니다. 테스트를 위해 두 번째 GlobalAlloc(또는 HeapAlloc) 다음에 다음과 같은 코드를 추가하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > bool allZero = true; BYTE *pBytes = (BYTE *)pVoid; for (int i = 0; i < length; i++) { if (*(pBytes) != 0) { allZero = false; break; } } printf("AllZero == %s\n", (allZero == true) ? "true" : "false"); </pre> <br /> 윈도우 서버 2012 R2(클린 설치)에서 실행하면 "AllZero == false"가 출력되지만 다른 운영체제에서는 "AllZero == true"가 나옵니다.<br /> <br /> 어쩌면 이건 확률상의 문제일지도 모릅니다. 메모리 할당 함수는 일반적으로 VirtualAlloc으로 큰 영역을 할당받고 그 영역 내에서 GlobalAlloc/HeapAlloc과 같은 메모리 할당 함수가 실제로 필요한 영역의 메모리를 요구하게 됩니다. 운이 좋게도 다른 환경에서는 항상 GlobalAlloc/HeapAlloc으로 요구된 메모리가 기존 할당해 놓은 VirtualAlloc의 영역을 초과해 새롭게 "VirtualAlloc(nullptr, length, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);"과 같은 식의 호출을 유발, VirtualAlloc에 의해 최초 메모리가 할당되었을 때는 0으로 초기화가 때문에 문제 없이 NtQuerySystemInformation 호출이 성공한 경우일 수 있습니다.<br /> <br /> 반면, 윈도우 서버 2012 R2에서는 요청된 2번째 GlobalAlloc/HeapAlloc에서 새로운 메모리가 아닌 기존에 이미 사용중이던 영역의 메모리가 재사용되었고 그곳은 0으로 초기화되지 않았기 때문에 NtQuerySystemInformation 호출에 실패한 것으로 볼 수 있습니다.<br /> <br /> 실제로 윈도우 서버 2012 R2 이외의 운영체제에서 GlobalAlloc/HeapAlloc으로 할당받았던 메모리에 다음과 같은 식의 쓰레기 값을 채워넣으면 동일한 현상이 재현됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > pVoid = ::HeapAlloc(GetProcessHeap(), 0, length); BYTE *pBytes = (BYTE *)pVoid; for (int i = 0; i < length; i++) { *(pBytes + i) = 0x60; } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 결국 해결방법은 ^^ 본문에 나온 것처럼 그냥 0으로 초기화만 해주면 됩니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > pVoid = ::GlobalAlloc(0, length); // 또는 pVoid = ::HeapAlloc(GetProcessHeap(), 0, length); memset(pVoid, 0, length); </pre> <br /> 또는 각각의 함수에서 제공하는 0 초기화 옵션을 사용하면 됩니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > pVoid = ::GlobalAlloc(GMEM_ZEROINIT, length); // 또는 pVoid = ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, length); </pre> <br /> 닷넷의 경우 동일한 크기의 byte 배열을 만들어 Marshal.Copy를 하는 방법이 있으나 기왕 Win32 API를 interop해서 호출하는 것이므로 차라리 ZeroMemory Win32 API로 해결하는 것이 더 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativememory.clear?view=net-8.0'>NativeMemory.Clear</a> // .NET Core 3 미만의 경우 [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)] internal static extern void ZeroMemory(IntPtr dest, IntPtr size); ptr = Marshal.AllocHGlobal(length); ZeroMemory(ptr, new IntPtr(length)); </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .NET Core 3+의 경우 ptr = Marshal.AllocHGlobal(length); Unsafe.InitBlockUnaligned((byte)ptr.ToPointer(), 0, length); </pre> <br /> 정리하면, NtQuerySystemInformation 함수에 전달할 메모리는 반드시 0으로 초기화 해줄 것!<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=944&boardid=331301885'>첨부한 파일은 이 글의 테스트 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2039
(왼쪽의 숫자를 입력해야 합니다.)