성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Visual Studio의 .NET Disassembly 창의 call 호출에 사용되는 주소의 의미는?</div> <br /> 아~~~ 토요일의 여유로움! ^^ 그래서 오늘은, 그동안 궁금했던 것 한가지를 파헤쳐 보기로 했습니다.<br /> <br /> 참고로, 이 글을 보기 전에 아래의 글을 한번 읽어보시는 것도 도움이 되겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > windbg로 확인하는 .NET CLR 메서드 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/940'>http://www.sysnet.pe.kr/2/0/940</a> </pre> <br /> 예제를 위해, 다음과 같이 간단한 코드를 한번 만들어서 시작해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > === .NET 4.0 Console Application === static void Main(string[] args) { SqlConnection connection = new SqlConnection(); <b style='COLOR: blue'>connection.CreateCommand();</b> } </pre> <br /> 이를 disassembly 창으로 보면 다음과 같이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > --- D:\...[생략]...\ConsoleApplication1\Program.cs 14: { 00000000 55 push ebp 00000001 8B EC mov ebp,esp 00000003 83 EC 0C sub esp,0Ch 00000006 89 4D FC mov dword ptr [ebp-4],ecx 00000009 83 3D 3C 31 19 00 00 cmp dword ptr ds:[0019313Ch],0 00000010 74 05 je 00000017 00000012 E8 14 44 36 73 call 7336442B 00000017 33 D2 xor edx,edx 00000019 89 55 F8 mov dword ptr [ebp-8],edx 0000001c 90 nop 15: ShowAddress(); 0000001d FF 15 04 34 19 00 call dword ptr ds:[00193404h] 00000023 90 nop 16: 17: SqlConnection connection = new SqlConnection(); 00000024 B9 E0 9C C2 00 mov ecx,0C29CE0h 00000029 E8 34 0D 0D 73 call 730D0D62 0000002e 89 45 F4 mov dword ptr [ebp-0Ch],eax 00000031 8B 4D F4 mov ecx,dword ptr [ebp-0Ch] 00000034 E8 1B AE 56 FF call FF56AE54 00000039 8B 45 F4 mov eax,dword ptr [ebp-0Ch] 0000003c 89 45 F8 mov dword ptr [ebp-8],eax 18: connection.CreateCommand(); 0000003f 8B 4D F8 mov ecx,dword ptr [ebp-8] 00000042 39 09 cmp dword ptr [ecx],ecx <b style='COLOR: blue'>00000044 E8 1B AE 56 FF call FF56AE64 </b> 00000049 90 nop 19: } </pre> <br /> call (E8) 명령어는 변위 주솟값을 오퍼랜드로 받기 때문에, (액면 그대로 해석하자면) "FF56AE64" 값은 -11096476으로 위의 call 명령어가 수행된 이후 메모리의 - 방향으로 11096476만큼 점프를 한 후 실행되는 구조가 됩니다.<br /> <br /> 여기서 잠깐, SqlConnection.CreateCommand의 함수 주소를 한번 구해볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > MethodBase func = typeof(SqlConnection).GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.Public); IntPtr pOld = func.MethodHandle.GetFunctionPointer(); </pre> <br /> 이렇게 해서 출력해 보니, Jitted된 FunctionPointer 값이 0x19ce54로 나옵니다. 그렇다면, Main 프로그램의 connection.CreateCommand 호출이 발생한 [call 명령어 다음 주소 + (-11096476) == 0x19ce54]가 나와야 합니다. 정말로 그럴까요?<br /> <br /> 이를 확인하려면 call 명령어의 정확한 Instruction Pointer 값을 알아내야 합니다. 왜냐하면 Visual Studio의 disassembly 창에서는 0부터 시작하는 기계어 코드를 순서대로 나열할뿐 절대값 주소를 출력하지 않기 때문입니다. <br /> <br /> 바로 이런 때 필요한 것이 ^^ sos.dll입니다.<br /> <br /> Visual Studio의 "Immediate Window"에서 sos를 로드하고, (그 전에, 반드시 Debug 메뉴의 "Enable unmanaged code debugging"을 체크하고!)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!load "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\SOS.dll"</b> extension c:\windows\microsoft.net\framework\v4.0.30319\sos.dll loaded </pre> <br /> 우선, Main 메서드가 컴파일 된 어셈블리 코드 및 SqlConnection.CreateCommand를 호출한 call 명령어의 위치를 알아야겠지요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Main</b> PDB symbol for clr.dll not loaded Module: 00192e9c Assembly: ConsoleApplication1.exe Token: 7891ac1506000001 MethodDesc: 001933f0 Name: ConsoleApplication1.Program.Main(System.String[]) <b style='COLOR: blue'>JITTED Code Address: 00c31ff0</b> </pre> <br /> 이어서 JITTED Code Address를 대상으로 디스어셈블을 하면 아래와 같은 결과를 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!u 00c31ff0</b> Normal JIT generated code ConsoleApplication1.Program.Main(System.String[]) Begin 00c31ff0, size 4f >>> 00C31FF0 55 push ebp 00C31FF1 8BEC mov ebp,esp 00C31FF3 83EC0C sub esp,0Ch 00C31FF6 894DFC mov dword ptr [ebp-4],ecx 00C31FF9 833D3C31190000 cmp dword ptr ds:[0019313Ch],0 00C32000 7405 je 00C32007 00C32002 E814443673 call 73F9641B (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 00C32007 33D2 xor edx,edx 00C32009 8955F8 mov dword ptr [ebp-8],edx 00C3200C 90 nop 00C3200D FF1504341900 call dword ptr ds:[00193404h] (ConsoleApplication1.Program.ShowAddress(), mdToken: 06000002) 00C32013 90 nop 00C32014 B9E09CC200 mov ecx,0C29CE0h (MT: System.Data.SqlClient.SqlConnection) 00C32019 E8340D0D73 call 73D02D52 (JitHelp: CORINFO_HELP_NEW_CROSSCONTEXT) 00C3201E 8945F4 mov dword ptr [ebp-0Ch],eax 00C32021 8B4DF4 mov ecx,dword ptr [ebp-0Ch] 00C32024 E81BAE56FF call 0019CE44 (System.Data.SqlClient.SqlConnection..ctor(), mdToken: 060024ec) 00C32029 8B45F4 mov eax,dword ptr [ebp-0Ch] 00C3202C 8945F8 mov dword ptr [ebp-8],eax 00C3202F 8B4DF8 mov ecx,dword ptr [ebp-8] 00C32032 3909 cmp dword ptr [ecx],ecx <b style='COLOR: blue'>00C32034 E81BAE56FF call 0019CE54 (System.Data.SqlClient.SqlConnection.CreateCommand(), mdToken: 060024c9)</b> 00C32039 90 nop 00C3203A 90 nop 00C3203B 8BE5 mov esp,ebp 00C3203D 5D pop ebp 00C3203E C3 ret </pre> <br /> 오호,,, 위의 "call 0x19ce54" 값은 MethodHandle.GetFunctionPointer의 값으로 반환받은 값과 동일하다는 것을 알 수 있습니다. 그렇다면, !u 명령어로 보여지는 어셈블리 코드 값의 call 명령어에 나오는 값은 변위값이라기 보다는 (서비스 차원에서) 계산되어진 최종 주솟값을 보여주는 것이라고 판단할 수 있습니다. (사실, 기계어 코드로 나온 E81BAE56FF에서 "0xff56ae1b" 값이 실제 변위값입니다.)<br /> <br /> 호기심 차원에서, 여기서 잠깐 실제 SqlConnection.CreateCommand의 기계어 주소를 확인해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand</b> Module: 001934dc Assembly: System.Data.dll Token: 6d37ad85060024c9 MethodDesc: 00c299a4 Name: System.Data.SqlClient.SqlConnection.CreateCommand() <b style='COLOR: blue'>JITTED Code Address: 076db240</b> </pre> <br /> 웬일인지, 0019CE54 != 076db240 다른 결과를 보여주고 있습니다. 그렇다면 0019CE54 위치의 코드를 마저 확인해 봐야할 필요가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > <b style='COLOR: blue'>!u 0019CE54</b> Unmanaged code 0019CE54 B8A499C200 mov eax,0C299A4h 0019CE59 90 nop 0019CE5A E88162B473 call 73CE30E0 <b style='COLOR: blue'>0019CE5F E9DCE35307 jmp 076DB240</b> 0019CE64 00B000EB7CB0 add byte ptr [eax+B07CEB00h],dh 0019CE6A 02EB add ch,bl 0019CE6C 78B0 js 0019CE1E 0019CE6E 05EB74B008 add eax,8B074EBh 0019CE73 EB70 jmp 0019CEE5 0019CE75 B00A mov al,0Ah </pre> <br /> 아하 정말 그렇군요. C/C++에서 마치 IAT(Import Address Table)을 이용해서 외부 주솟값을 참조하듯, .NET에서도 외부 DLL의 함수를 부를 때 위와 같이 일종의 stub 코드를 경유하는 것을 확인할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> sos와 GetFunctionPointer 관계를 알아보느라 잠시 본론에서 멀어졌는데요. ^^ 어쨌든 위와 같이 해서 구하고자 했던 "connection.CreateCommand 호출이 발생한 call 명령어 다음 주소" 값이 00C32039라는 것은 알아냈습니다.<br /> <br /> 다시 한번 disassembly 코드를 적어보고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > --- Disassembly 창 --- 00000044 E8 1B AE 56 FF call <b style='COLOR: blue'>FF56AE64</b> (unsigned 10진수: 4283870820, signed 10진수: -11096476) </pre> <br /> 이제, 예전에 하기로 했던 계산을 마치면 결과는 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > 00C32039 + FF56AE64 = 10019CE9D ==> 4byte 값만 취하면 ==> <b style='COLOR: blue'>0x19CE9D</b> != (예상값 0x19ce54) </pre> <br /> 음... 아쉽게도 0x19ce9d 값으로는 이론과 맞지 않는 결과가 나와버렸습니다. 그래도 어쩐지 희망이 보이는 것 같습니다. 왠지 SOS.dll의 call에서 보여준 절대 주솟값 0x19ce54와 어딘지 비슷해 보입니다. 그 차이는 정확히 0x19ce9d - 0x19ce54 = 0x49 값이 되고, 이는 Disassembly 창에서만 보여주던 "0부터 시작하는 독특한 코드 주솟값"의 위치와 일치합니다.<br /> <br /> <img alt='diff_disassembly_view_code_1.png' src='/SysWebRes/bbs/diff_disassembly_view_code_1.png' /><br /> <br /> 휴... 그렇군요. Visual Studio의 disassembly 창에서 보여주는 call 명령어의 옵셋값은 그 윈도우에서만 유효한, 즉 개발자 입장에서는 매우 도움이 안되는 값을 출력해 주고 있는 것입니다.<br /> <br /> 암튼... ^^ 여기까지 해서 MethodHandle.GetFunctionPointer와 Visual Studio의 Disassembly 창의 call 명령어, sos.dll의 !u 명령어로 본 어셈블리 코드값에 대한 관계를 알아보았습니다.<br /> <br /> (<a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?wid=1019&boardid=331301885'>첨부 파일은 제가 수행한 예제 코드</a>입니다.)<br /> <br /><br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2087
(왼쪽의 숫자를 입력해야 합니다.)