성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] 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...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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# - double 값에 대한 windbg 확인</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;' > class Program { static void Main(string[] args) { double xd = -2.12200016492843E-314; Output(xd); } private static void Output(double xd) { Console.WriteLine(xd); Console.ReadLine(); } } </pre> <br /> Console.ReadLine까지 실행되었을 때 windbg로 attach시켜 봅니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft (R) Windows Debugger Version 6.2.9200.20512 X86 Copyright (c) Microsoft Corporation. All rights reserved. *** wait with pending attach Symbol search path is: SRV*e:\Symbols*http://msdl.microsoft.com/download/symbols Executable search path is: ModLoad: 00e70000 00e78000 d:\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe ModLoad: 76fc0000 77128000 C:\WINDOWS\SYSTEM32\ntdll.dll ModLoad: 73e20000 73e76000 C:\WINDOWS\SYSTEM32\MSCOREE.DLL ModLoad: 76770000 768b0000 C:\WINDOWS\SYSTEM32\KERNEL32.dll ...[생략]... \b23c1312ec0a64893e596e2fc2aa875b\System.Core.ni.dll (1e0c.2c60): Break instruction exception - code 80000003 (first chance) eax=7e4a3000 ebx=00000000 ecx=00000000 edx=7706fdc4 esi=00000000 edi=00000000 eip=76fe879c esp=056bf934 ebp=056bf960 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 ntdll!DbgBreakPoint: 76fe879c cc int 3 <span style='color: blue; font-weight: bold'>0:006> .loadby sos clr</span> </pre> <br /> 오호... 운이 나쁘게도 6번 스레드를 windbg가 잡아버렸군요. 우리가 원하는 CLR 스레드로 이동하기 위해서 우선 스레드 목록을 확인합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:006> <span style='color: blue; font-weight: bold'>!threads</span> ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception <span style='color: blue; font-weight: bold'>0 1 3b1c</span> 013c66e0 2a020 Preemptive 02EC52D8:00000000 013bd818 1 MTA 3 2 f1c 013d54b0 2b220 Preemptive 00000000:00000000 013bd818 0 MTA (Finalizer) </pre> <br /> windbg에서는 OS의 ID가 중요하므로 3b1c 값으로 스레드 변경을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:006> <span style='color: blue; font-weight: bold'>~~[3b1c]s</span> eax=00000000 ebx=00000024 ecx=00000000 edx=00000000 esi=00fff34c edi=00000000 eip=76ffbc5c esp=00fff234 ebp=00fff294 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 ntdll!NtReadFile+0xc: 76ffbc5c c22400 ret 24h <span style='color: blue; font-weight: bold'>0:000</span>> </pre> <br /> 0:000으로 바뀌었죠. ^^ 이제 !clrstack 명령을 내리면 다음과 같이 나옵니다.<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'>!clrstack</span> OS Thread Id: 0x3b1c (0) Child SP IP Call Site 00fff2b4 76ffbc5c [InlinedCallFrame: 00fff2b4] 00fff2b0 7292b8cf DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 00fff2b4 7307a5f4 [InlinedCallFrame: 00fff2b4] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 00fff318 7307a5f4 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) 00fff34c 7307a4fb System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) 00fff36c 728c6768 System.IO.StreamReader.ReadBuffer() 00fff380 7288838f System.IO.StreamReader.ReadLine() 00fff39c 730806ed System.IO.TextReader+SyncTextReader.ReadLine() 00fff3ac 72f88fb2 System.Console.ReadLine() <span style='color: blue; font-weight: bold'>00fff3b4 012d0a34 ConsoleApplication1.Program.Output(Double) [d:\ConsoleApplication1\Program.cs @ 91] 00fff3c8 012d0597 ConsoleApplication1.Program.Main(System.String[]) [d:\ConsoleApplication1\Program.cs @ 81]</span> 00fff660 73702652 [GCFrame: 00fff660] </pre> <br /> 각 메서드의 호출에 전달된 파라미터 값도 확인하는 것이 가능합니다. 단지 -p 옵션만 추가해 주면 됩니다. ^^<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'>!clrstack -p</span> OS Thread Id: 0x3b1c (0) Child SP IP Call Site 00fff2b4 76ffbc5c [InlinedCallFrame: 00fff2b4] ...[생략].. 00fff39c 730806ed System.IO.TextReader+SyncTextReader.ReadLine() PARAMETERS: this (<CLR reg>) = 0x02ec52c8 00fff3ac 72f88fb2 System.Console.ReadLine() <span style='color: blue; font-weight: bold'>00fff3b4 012d0a34 ConsoleApplication1.Program.Output(Double) [d:\ConsoleApplication1\Program.cs @ 91] PARAMETERS: xd (0x00fff3c0) = 0x00002295</span> 00fff3c8 012d0597 ConsoleApplication1.Program.Main(System.String[]) [d:\ConsoleApplication1\Program.cs @ 81] PARAMETERS: args (0x00fff4cc) = 0x02ec2448 00fff660 73702652 [GCFrame: 00fff660] </pre> <br /> 보시는 것처럼, Output 메서드에 전달된 double 인자의 값이 0x00002295입니다. double 인자는 IEEE 부동 소수점 표준을 따르므로 -2.12200016492843E-314 값이 0x2295로 나온 것입니다. (double은 8바이트이긴 한데 !clrstack 명령어는 타입 고려를 하지 않아 4바이트 내용만 출력합니다.)<br /> <br /> 바이너리 표기를 직접 확인하는 것도 가능합니다. 위의 출력에서 "xd (0x00fff3c0)"라고 되어 있는데 0x00fff3c0 값이 바로 double 값이 담겨있는 메모리의 주소입니다. 따라서 다음과 같이 덤프할 수 있습니다.<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'>db 0x00fff3c0</span> 00fff3c0 <span style='color: blue; font-weight: bold'>95 22 00 00 01 00 00 80</span>-5c 4e ec 02 b0 4d ec 02 ."......\N...M.. 00fff3d0 08 4d ec 02 94 4c ec 02-a4 4b ec 02 54 4b ec 02 .M...L...K..TK.. 00fff3e0 1c 4b ec 02 08 4b ec 02-bc 4a ec 02 84 4a ec 02 .K...K...J...J.. 00fff3f0 70 4a ec 02 b8 49 ec 02-68 49 ec 02 ec 48 ec 02 pJ...I..hI...H.. 00fff400 9c 48 ec 02 20 48 ec 02-d0 47 ec 02 e0 24 ec 02 .H.. H...G...$.. 00fff410 90 24 ec 02 5c 4e ec 02-08 4b ec 02 84 4a ec 02 .$..\N...K...J.. 00fff420 70 4a ec 02 95 22 00 00-01 00 00 80 70 4e ec 02 pJ..."......pN.. 00fff430 d3 ff ff ff 1c 00 00 00-c4 4d ec 02 24 d7 12 00 .........M..$... </pre> <br /> 아하~~~ 엔디안 문제가 있으니 8바이트씩 묶어서 출력하는 것이 더 낫겠군요.<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'>dq 0x00fff3c0</span> 00fff3c0 <span style='color: blue; font-weight: bold'>80000001`00002295</span> 02ec4db0`02ec4e5c 00fff3d0 02ec4c94`02ec4d08 02ec4b54`02ec4ba4 00fff3e0 02ec4b08`02ec4b1c 02ec4a84`02ec4abc 00fff3f0 02ec49b8`02ec4a70 02ec48ec`02ec4968 00fff400 02ec4820`02ec489c 02ec24e0`02ec47d0 00fff410 02ec4e5c`02ec2490 02ec4a84`02ec4b08 00fff420 00002295`02ec4a70 02ec4e70`80000001 00fff430 0000001c`ffffffd3 0012d724`02ec4dc4 </pre> <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;' > ulong xd2 = <span style='color: blue; font-weight: bold'>0x8000000100002295</span>; byte [] tmpBytes = BitConverter.GetBytes(xd2); Console.WriteLine(BitConverter.ToDouble(tmpBytes, 0)); // 출력결과 <span style='color: blue; font-weight: bold'>-2.12200016492843E-314</span> </pre> <br /> <hr style='width: 50%' /><br /> <br /> "clrstack -p"로 쉽게 인자 값을 확인할 수 있지만 가끔은 unmanaged로 접근해야 하는 경우가 있습니다. 예를 들어, RCW를 이용해 COM 개체의 메서드를 호출하는 경우에는 "clrstack -p"로 호출 인자가 출력되지 않습니다.<br /> <br /> 그런 경우 windbg에서 "View" / "Call Stack" 메뉴를 선택하면 다음과 같은 목록을 볼 수 있습니다.<br /> <br /> <img alt='windbg_ebp_1.png' src='/SysWebRes/bbs/windbg_ebp_1.png' /> <br /><br /> Output 메서드가 COM 개체의 호출이라고 가정한 경우 !clrstack의 출력으로부터 IP(Instruction Pointer) 주소를 얻을 수 있고,<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> !clrstack OS Thread Id: 0x3b1c (0) Child SP IP Call Site ..[생략]... 00fff3ac 72f88fb2 System.Console.ReadLine() 00fff3b4 <span style='color: blue; font-weight: bold'>012d0a34</span> ConsoleApplication1.Program.Output(Double) [d:\ConsoleApplication1\Program.cs @ 91] 00fff3c8 012d0597 ConsoleApplication1.Program.Main(System.String[]) [d:\ConsoleApplication1\Program.cs @ 81] 00fff660 73702652 [GCFrame: 00fff660] </pre> <br /> "Call Stack" 뷰에서는 !clrstack 출력이 아닌 네이티브 호출 스택을 보여주므로 012d0a34 값 자체를 메서드 명 위치에서 찾으면 됩니다. 그럼 다음의 프레임이 우리가 찾는 Output 메서드에 해당하죠.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ChildEBP RetAddr Args to Child Method Name ...[생략]... <span style='color: blue; font-weight: bold'>00fff3b8</span> 012d0597 <span style='color: blue; font-weight: bold'>00002295 80000001</span> 02ec4e5c <span style='color: blue; font-weight: bold'>0x12d0a34</span> ...[생략]... </pre> <br /> 친절하게도 Args 목록이 나오는데 windbg는 해당 데이터 타입을 모르기 때문에 32비트 응용 프로그램인 경우 4바이트씩 잘라서 인자를 보여주고 있습니다. 기본적으로 3개의 인자를 이렇게 보여주고 있는데, EBP 레지스터를 이용하면 좀 더 덤프를 하는 것도 가능합니다. C/C++에 익숙한 분들이라면 (32비트인 경우) EBP + 8의 주소를 시작으로 인자가 나열된다는 것을 잘 아시겠죠! ^^ 따라서 ChildEBP 주소로 이 처리를 할 수 있습니다.<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'>dq 00fff3b8 + 8</span> 00fff3c0 <span style='color: blue; font-weight: bold'>80000001`00002295</span> 02ec4db0`02ec4e5c 00fff3d0 02ec4c94`02ec4d08 02ec4b54`02ec4ba4 00fff3e0 02ec4b08`02ec4b1c 02ec4a84`02ec4abc 00fff3f0 02ec49b8`02ec4a70 02ec48ec`02ec4968 00fff400 02ec4820`02ec489c 02ec24e0`02ec47d0 00fff410 02ec4e5c`02ec2490 02ec4a84`02ec4b08 00fff420 00002295`02ec4a70 02ec4e70`80000001 00fff430 0000001c`ffffffd3 0012d724`02ec4dc4 </pre> <br /> 당연히 이 주소는 이전의 !clrstack -p로 살펴봤던 파라미터의 주소값과 일치하므로 출력 결과가 똑같습니다.<br /> <br /> (사실, 위의 과정은 "Call Stack" 뷰가 아니라 단순히 k 명령어를 통해서도 할 수 있습니다. ^^)<br /> <br /> <hr style='width: 50%' /><br /> <br /> double 값이 일반적인 숫자 데이터와는 인코딩 방식이 다르기 때문에 한가지 유의할 사항이 있습니다. 가령 다음과 같이 덤프된 결과를 얻은 경우,<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> dq 12cea8 + 8 0012ceb0 <span style='color: blue; font-weight: bold'>ffffffe7`00000004</span> 0012d728`0012d724 0012cec0 0000001c`ffffffd3 80000001`00002295 0012ced0 79f3b1a2`0012d248 79e992fb`ffffffff 0012cee0 00000010`79e96be6 00000000`0e2cfcb8 0012cef0 0012d254`80000000 79e7f1a8`79e96bfb 0012cf00 13f37b88`79e96a55 0012d3b8`0012d560 0012cf10 00000000`0012d560 79e9d605`00000000 0012cf20 00000000`00000000 00000000`01566394 </pre> <br /> 결과가 "ffffffe7`00000004"로 나왔는데 이 값이 일반적인 숫자라면 넘어갈 수 있는 문제이지만 double 형 타입인 경우에는 그렇지 않습니다. 값을 직접 확인해 보면 어떨까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ulong xd2 = 0xffffffe700000004; byte [] tmpBytes = BitConverter.GetBytes(xd2); Console.WriteLine(BitConverter.ToDouble(tmpBytes, 0)); // 출력결과 NaN </pre> <br /> NaN이 나왔으니 유효한 double 값이 아니었던 것입니다. 이 문제는 실제로 한 고객사의 덤프를 분석해서 나온 결과입니다. double 값이 이렇게 전달되는 것을 모르고 그동안 응용 프로그램이 실행되다가 어느 순간 이런 잘못된 입력이 들어왔을 때 프로그램이 오동작을 일으키게 된 것입니다. (입력값을 검사하는 코드가 왜 중요한지 알게 되는 순간입니다. ^^)<br /> <br /> 마지막으로 C#의 double에 대한 특이값들은 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > double t = 0; double t1 = double.NaN; double t2 = double.PositiveInfinity; double t3 = double.NegativeInfinity; Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(t))); Console.WriteLine(t); Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(t1))); Console.WriteLine(t1); Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(t2))); Console.WriteLine(t2); Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(t3))); Console.WriteLine(t3); // 출력결과: 0 : 00-00-00-00-00-00-00-00 NaN: 00-00-00-00-00-00-F8-FF (0xfff8000000000000) +∞ : 00-00-00-00-00-00-F0-7F (0x7ff0000000000000) -∞ : 00-00-00-00-00-00-F0-FF (0xfff0000000000000) </pre> <br /> 보시면 NaN 값이 0xfff8000000000000로 나오는데 이 값만을 기억하고 NaN 판정을 내리면 안된다는 사실!<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1426
(왼쪽의 숫자를 입력해야 합니다.)