성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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'>ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인</h1> <p> 지난 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - 닷넷 응용 프로그램의 메모리 누수 분석 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11808'>http://www.sysnet.pe.kr/2/0/11808</a> </pre> <br /> 문자열이 쌓이고 있는 경우를 살펴봤는데, 분석 시 약간 아쉬운 점이 있습니다. 그 덤프의 힙 상태를 다시 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpheap -stat</span> Statistics: MT Count TotalSize Class Name 00007ff90f456748 1 24 Elasticsearch.Net.ElasticsearchDefaultSerializer 00007ff90f192f78 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Runtime.Caching.MemoryCache, System.Runtime.Caching]] 00007ff90f143038 1 24 System.Collections.Generic.Dictionary`2+ValueCollection[[System.String, mscorlib],[OracleInternal.SelfTuning.OracleTuner+OracleTunerInput, Oracle.ManagedDataAccess]] 00007ff90f0120b8 1 24 System.Net.SSPISecureChannelType 00007ff90f011e78 1 24 System.Net.SSPIAuthType ...[생략]... 00007ff90d3869b8 12753 5127112 System.Int32[] 00007ff90d4ba6e0 159 14555944 System.Int64[] 00007ff90d1aba28 148673 16952928 System.Object[] 00007ff90d1fe828 75889 20114243 System.Byte[] 00007ff90ebd41e0 376168 36112128 TestApi.Data.AddrInfo <span style='color: blue; font-weight: bold'>00007ff90d1f46f0 2726523 208786640 System.String</span> 000000b69ba94870 33952 274313352 Free Total 4063087 objects Fragmented blocks larger than 0.5 MB: Addr Size Followed by 000000b6a40f38b0 11.4MB 000000b6a4c4d630 System.Byte[] </pre> <br /> System.String을 소유하고 있는 객체로 (사용자가 만든 타입인) TestApi.Dat.AddrInfo 인스턴스가 많은 걸로 쉽게 짐작을 했는데요. 이런 식으로 찾아낼 수 없는 경우도 더러 있습니다. 예를 들어, 다음의 소스 코드를 덤프 떠서 살펴보면 어떨까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; namespace ConsoleApp1 { class Program { static List<string> _list = new List<string>(); static void Main(string[] args) { CreateObjects(); GC.Collect(2); GC.Collect(2); Console.WriteLine("Dump!"); Console.ReadKey(); } private static void CreateObjects() { for (int i = 0; i < 100000; i ++) { _list.Add(Guid.NewGuid().ToString()); } } } } </pre> <br /> string에 대한 소유를 1개의 List 객체가 담당하고 있으므로 heap 상태를 봐도 그 소유자를 쉽게 확인할 수 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpheap -stat</span> Statistics: MT Count TotalSize Class Name 719025c4 1 12 System.Collections.Generic.GenericEqualityComparer`1[[System.String, mscorlib]] 71900414 1 12 System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorlib]] 718fde9c 1 16 System.Security.Policy.AssemblyEvidenceFactory 7190be18 1 20 System.IO.Stream+NullStream 71901888 1 20 Microsoft.Win32.SafeHandles.SafeFileHandle 718fdde8 1 20 Microsoft.Win32.SafeHandles.SafePEFileHandle 7190c3dc 1 24 System.IO.TextWriter+SyncTextWriter 71902298 1 24 System.Version 71900994 2 24 System.Int32 71461e00 1 24 System.Collections.Generic.List`1[[System.String, mscorlib]] 7190c2e4 1 28 System.IO.__ConsoleStream 7190c29c 1 28 Microsoft.Win32.Win32Native+InputRecord ...[생략]... 71900958 6 452 System.Int32[] 718fff54 22 616 System.RuntimeType 718ff54c 6 626 System.Char[] 7190316c 1 783 System.Byte[] 71901e50 3 924 System.Globalization.CultureData 718fef34 4 17604 System.Object[] 718ff698 19 524868 System.String[] 00f2b548 24 525636 Free <span style='color: blue; font-weight: bold'>718feb40 100154 8604850 System.String</span> Total 100299 objects </pre> <br /> 물론, "!dumpheap -mt 718feb40" 명령어를 이용해 문자열 인스턴스를 나열할 수 있지만 windbg에서 이 명령어를 수행하면 10만 개가 넘는 인스턴스를 출력하느라 한 세월이 걸립니다. 게다가 그중에서 몇 개를 골라 "!gcroot"로 찾아볼 수 있겠지만 무작위로 선정하는 것이므로 운이 좋아야 List를 가리키는 객체를 알아낼 수 있습니다. 또한, 그 객체가 문자열을 보관하는 (Leak이 없는) 또 다른 List 객체일 수도 있다는 점을 감안하면 찾기가 그리 쉽지 않습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이런 상황을 해결하려면, windbg에서 스크립트를 이용해 10만 개의 System.String에 대한 참조를 유지하고 있는 객체 리스트를 작성하는 것입니다. 대표적으로 pykd를 이용하면 될 텐데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg에서 python 스크립트 실행하는 방법 - pykd ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11227'>http://www.sysnet.pe.kr/2/0/11227</a> </pre> <br /> 아쉽게도 실제로 수행해보면 출력 결과에 대해 일일이 sos.dll의 명령어로 다시 수행하는 방식이어서 전체 과정이 꽤나 긴 시간을 요구하게 됩니다. 그래서 좀 더 빠른 방법을 찾게 되는데, 바로 그 해답이 ClrMD입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft.Diagnostics.Runtime Install-Package Microsoft.Diagnostics.Runtime -Version 1.0.2 </pre> <br /> 아울러 이미 제공되고 있는 풍부한 예제 코드에 따라,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Tutorials - 1. Getting Started - A brief introduction to the API and how to create a CLRRuntime instance. ; <a target='tab' href='https://github.com/Microsoft/clrmd/blob/master/Documentation/GettingStarted.md'>https://github.com/Microsoft/clrmd/blob/master/Documentation/GettingStarted.md</a> Tutorials - 3. Walking objects on the GC heap, working with types in CLR MD. ; <a target='tab' href='https://github.com/Microsoft/clrmd/blob/master/Documentation/WalkingTheHeap.md'>https://github.com/Microsoft/clrmd/blob/master/Documentation/WalkingTheHeap.md</a> </pre> <br /> 이거저거 짜깁기하면 우리가 원하는 목적의 코드를 다음과 같이 간단하게 만들 수 있습니다.<br /> <br /> <pre style='height: 400px; margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using Microsoft.Diagnostics.Runtime; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; namespace RefOwner { class Program { static string _platformPostfix = (IntPtr.Size == 4) ? "32" : "64"; static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("RefOwner" + _platformPostfix + " [/d] [dumppath] [typename | address]"); Console.WriteLine("[Sample]"); Console.WriteLine("\tRefOwner" + _platformPostfix + " c:\\temp\\test.dmp System.String"); Console.WriteLine("\tRefOwner" + _platformPostfix + " /d c:\\temp\\test.dmp System.String"); return; } string filePath = (args.Length == 3) ? args[1] : args[0]; if (File.Exists(filePath) == false) { Console.WriteLine("FILE-NOT-FOUND: " + filePath); return; } string basePath = Path.GetDirectoryName(filePath); string dmpPath = filePath; string dacPath = Path.Combine(basePath, "mscordacwks.dll"); string typeNameOrAddress = (args.Length == 3) ? args[2] : args[1]; bool detailed = args.Length == 3 && args[0] == "/d"; using (DataTarget dataTarget = DataTarget.LoadCrashDump(dmpPath)) { foreach (ClrInfo version in dataTarget.ClrVersions) { Console.WriteLine("Found CLR Version: " + version.Version); // This is the data needed to request the dac from the symbol server: ModuleInfo dacInfo = version.DacInfo; Console.WriteLine("Filesize: {0:X}", dacInfo.FileSize); Console.WriteLine("Timestamp: {0:X}", dacInfo.TimeStamp); Console.WriteLine("Dac File: {0}", dacInfo.FileName); string dacLocation = version.LocalMatchingDac; if (!string.IsNullOrEmpty(dacLocation)) Console.WriteLine("Local dac location: " + dacLocation); Console.WriteLine(); ClrRuntime runtime; if (File.Exists(dacPath) == false) { runtime = version.CreateRuntime(); } else { runtime = version.CreateRuntime(dacPath); } // DumpHeapStat(runtime); // Console.WriteLine(); DumpHeapRefHierachy(runtime, typeNameOrAddress, detailed, 0); } } } private static void DumpHeapRefHierachy(ClrRuntime runtime, string typeNameOrAddress, bool detailed, int depth) { HashSet<ulong> instances = GetObjectListByName(runtime, typeNameOrAddress); Console.WriteLine(typeNameOrAddress + ", # of instances: " + instances.Count); Dictionary<string, HeapObjectCounter> owners = GetObjectOwners(runtime, instances); List<ObjectHistogramItem> histogram = new List<ObjectHistogramItem>(); foreach (var owner in owners) { ObjectHistogramItem item = new ObjectHistogramItem { Key = owner.Key, Counter = owner.Value }; histogram.Add(item); } histogram.Sort(); int count = 0; foreach (var item in histogram) { Console.WriteLine($"{item.Counter.Total} {item.Key}({item.Counter.OwnerCount})"); if (detailed == true) { foreach (var instance in item.Counter) { Console.WriteLine($"\t[{instance.Key:x}, {instance.Value}]"); } } count += item.Counter.Total; } Console.WriteLine("Total: " + count); } private static ulong GetFieldAddressValue(ClrRuntime runtime, ClrInstanceField field, ulong addr) { ulong fieldAddress = field.GetAddress(addr); return ReadMemory(runtime, fieldAddress); } private static ulong ReadMemory(ClrRuntime runtime, ulong address) { bool x86 = IntPtr.Size == 4; unsafe { byte[] refAddr = null; if (x86 == true) { refAddr = new byte[4]; } else { refAddr = new byte[8]; } runtime.Heap.ReadMemory(address, refAddr, 0, IntPtr.Size); if (x86 == true) { return BitConverter.ToUInt32(refAddr, 0); } else { return BitConverter.ToUInt64(refAddr, 0); } } } private static Dictionary<string, HeapObjectCounter> GetObjectOwners(ClrRuntime runtime, HashSet<ulong> instances) { Dictionary<string, HeapObjectCounter> dict = new Dictionary<string, HeapObjectCounter>(); if (!runtime.Heap.CanWalkHeap) { Console.WriteLine("Cannot walk the heap!"); } else { foreach (ClrSegment seg in runtime.Heap.Segments) { for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj)) { ClrType type = runtime.Heap.GetObjectType(obj); if (type == null || type.IsFree == true) { continue; } string typeName = type.ToString(); int gen = runtime.Heap.GetGeneration(obj); if (gen != 2) { continue; } if (type.IsArray == true) { int arrayLength = type.GetArrayLength(obj); for (int i = 0; i < arrayLength; i++) { ulong elemAddress = type.GetArrayElementAddress(obj, i); ClrType elemType = type.ComponentType; if (elemType.IsValueClass == false) { ulong elemRef = ReadMemory(runtime, elemAddress); if (instances.Contains(elemRef) == true) { if (dict.ContainsKey(typeName) == true) { dict[typeName].AddCount(obj); } else { dict.Add(typeName, new HeapObjectCounter(obj)); } } } else { AddItem(type, typeName, obj); } } } else { AddItem(type, typeName, obj); } } } } return dict; void AddItem(ClrType type, string typeName, ulong objAddress) { foreach (ClrInstanceField field in type.Fields) { ulong fieldAddress = GetFieldAddressValue(runtime, field, objAddress); if (instances.Contains(fieldAddress) == true) { if (dict.ContainsKey(typeName) == true) { dict[typeName].AddCount(objAddress); } else { dict.Add(typeName, new HeapObjectCounter(objAddress)); } } } } } private static HashSet<ulong> GetObjectListByName(ClrRuntime runtime, string typeNameOrAddress) { HashSet<ulong> list = new HashSet<ulong>(); ulong result; if (UInt64.TryParse(typeNameOrAddress, System.Globalization.NumberStyles.AllowHexSpecifier, null, out result) == true) { list.Add(result); return list; } if (!runtime.Heap.CanWalkHeap) { Console.WriteLine("Cannot walk the heap!"); } else { foreach (ClrSegment seg in runtime.Heap.Segments) { for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj)) { ClrType type = runtime.Heap.GetObjectType(obj); if (type == null) { continue; } int gen = runtime.Heap.GetGeneration(obj); if (gen != 2) { continue; } if (type.ToString() != typeNameOrAddress) { continue; } list.Add(obj); } } } return list; } private static void DumpHeapObject(ClrRuntime runtime) { if (!runtime.Heap.CanWalkHeap) { Console.WriteLine("Cannot walk the heap!"); } else { foreach (ClrSegment seg in runtime.Heap.Segments) { for (ulong obj = seg.FirstObject; obj != 0; obj = seg.NextObject(obj)) { ClrType type = runtime.Heap.GetObjectType(obj); if (type == null) { continue; } ulong size = type.GetSize(obj); Console.WriteLine("{0,12:X} {1,8:n0} {2,1:n0} {3}", obj, size, seg.GetGeneration(obj), type.Name); } } } } private static void DumpHeapStat(ClrRuntime runtime) { Console.WriteLine("{0,12} {1,12} {2,12} {3,12} {4,4} {5}", "Start", "End", "CommittedEnd", "ReservedEnd", "Heap", "Type"); foreach (ClrSegment segment in runtime.Heap.Segments) { string type; if (segment.IsEphemeral) type = "Ephemeral"; else if (segment.IsLarge) type = "Large"; else type = "Gen2"; Console.WriteLine("{0,12:X} {1,12:X} {2,12:X} {3,12:X} {4,4} {5}", segment.Start, segment.End, segment.CommittedEnd, segment.ReservedEnd, segment.ProcessorAffinity, type); } ClrHeap heap = runtime.Heap; foreach (var item in (from seg in heap.Segments group seg by seg.ProcessorAffinity into g orderby g.Key select new { Heap = g.Key, Size = g.Sum(p => (uint)p.Length) })) { Console.WriteLine("Heap {0,2}: {1:n0} bytes", item.Heap, item.Size); } } } public class ObjectHistogramItem : IComparable<ObjectHistogramItem> { public string Key; public HeapObjectCounter Counter; public int CompareTo(ObjectHistogramItem other) { return Counter.Total.CompareTo(other.Counter.Total); } } public class HeapObjectCounter : IEnumerable<KeyValuePair<ulong, int>> { Dictionary<ulong, int> _owners = new Dictionary<ulong, int>(); int _total; public HeapObjectCounter(ulong address) { AddCount(address); } public int OwnerCount { get { return _owners.Keys.Count; } } public int Total { get { return _total; // _owners.Values.Sum(); } } public IEnumerator<KeyValuePair<ulong, int>> GetEnumerator() { return this._owners.GetEnumerator(); } internal void AddCount(ulong objAddress) { if (_owners.ContainsKey(objAddress) == true) { _owners[objAddress]++; } else { _owners[objAddress] = 1; } _total++; } IEnumerator IEnumerable.GetEnumerator() { return this._owners.GetEnumerator(); } } } </pre> <br /> 그래서 위의 프로그램을 이 글에서 만든 예제 프로젝트의 덤프에 대해 실행하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp\RefOwner\bin> <span style='color: blue; font-weight: bold'>RefOwner32.exe c:\tmp\ConsoleApp1.dmp System.String</span> Found CLR Version: v4.7.3260.00 Filesize: 6EF000 Timestamp: 5BB7BCB7 Dac File: mscordacwks_X86_X86_4.7.3260.00.dll Local dac location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll <span style='color: blue; font-weight: bold'>System.String, # of instances: 100034</span> 1 System.AppDomainSetup(1) 31 System.Object[](3) <span style='color: blue; font-weight: bold'>100003 System.String[](2)</span> Total: 100035 </pre> <br /> 총 100,034개의 문자열 인스턴스 중에 무려 100,003개가 단 2개의 System.String[] 객체에 참조되고 있는 것을 볼 수 있습니다. 여기서 "/d" 옵션을 주면 2개의 System.String[]에 대한 인스턴스 주소와 그것들이 각각 참조하고 있는 System.String 객체의 수를 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp\RefOwner\bin> <span style='color: blue; font-weight: bold'>RefOwner32.exe /d c:\tmp\ConsoleApp1.dmp System.String</span> ...[생략]... System.String, # of instances: 100034 System.String, # of instances: 100034 1 System.AppDomainSetup(1) [2b714e0, 1] 31 System.Object[](3) [3b71020, 1] [3b72300, 2] [3b72520, 28] 100003 System.String[](2) <span style='color: blue; font-weight: bold'>[2b7156c, 3] [3bd5570, 100000]</span> Total: 100035 </pre> <br /> 보는 바와 같이, 두 번째 인스턴스인 3bd5570 객체가 100,000개의 String을 담고 있는데, 테스트 코드에서 100,000번의 루프로 Guid 문자열을 담은 것과 일치하는군요. ^^<br /> <br /> windbg에서 위의 주소에 따라 dump를 해보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!do 3bd5570</span> Name: System.String[] MethodTable: 718ff698 EEClass: 714d4b80 Size: 524300(0x8000c) bytes Array: Rank 1, Number of elements <span style='color: blue; font-weight: bold'>131072</span>, Type CLASS (Print Array) Fields: None </pre> <br /> 배열의 크기 자체는 131,072라고 합니다. 즉, 그 배열 중에 String이 할당된 것은 100,000개이고 이후의 요소들은 모두 null입니다. 그럼, 여기서 다시 3bd5570 객체의 소유주를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > c:\temp\RefOwner\bin\x86> <span style='color: blue; font-weight: bold'>RefOwner32.exe /d c:\tmp\ConsoleApp1.dmp 3bd5570</span> Found CLR Version: v4.7.3260.00 Filesize: 6EF000 Timestamp: 5BB7BCB7 Dac File: mscordacwks_X86_X86_4.7.3260.00.dll Local dac location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll 3bd5570, # of instances: 1 <span style='color: blue; font-weight: bold'>1 System.Collections.Generic.List<System.String>(1)</span> [2b720a4, 1] Total: 1 </pre> <br /> 답이 나왔습니다. (만들어 놓고 보니 그런대로 쓸만한 도구가 된 듯합니다. ^^)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, ClrMD 라이브러리 사용 시 다음과 같은 오류가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.InvalidOperationException HResult=0x80131509 Message=Mismatched architecture between this process and the dac. Source=Microsoft.Diagnostics.Runtime StackTrace: at Microsoft.Diagnostics.Runtime.DataTarget.ConstructRuntime(ClrInfo clrInfo, String dac) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/DataTarget.cs:line 326 at Microsoft.Diagnostics.Runtime.DataTarget.CreateRuntime(ClrInfo clrInfo, String dacFilename, Boolean ignoreMismatch) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/DataTarget.cs:line 320 at Microsoft.Diagnostics.Runtime.ClrInfo.CreateRuntime(String dacFilename, Boolean ignoreMismatch) in /_/src/Microsoft.Diagnostics.Runtime/src/DataTargets/ClrInfo.cs:line 115 at RefOwner.Program.Main(String[] args) in c:\temp\RefOwner\RefOwner\RefOwner\Program.cs:line 39 </pre> <br /> 이유는? 현재 프로세스는 x86이면서, 분석 대상이 되는 덤프는 x64 프로세스를 뜬 경우에 해당합니다. 따라서 분석하려는 프로세스를 x64로 빌드해 다시 실행하면 정상적으로 동작합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 글의 소스 코드는 <a target='tab' href='https://github.com/stjeong/RefOwner/'>https://github.com/stjeong/RefOwner/</a>에도 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6685
(왼쪽의 숫자를 입력해야 합니다.)