성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법</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;' > AVX2 div (나누기)가 오류를 발생합니다. ; <a target='tab' href='http://social.msdn.microsoft.com/Forums/ko-KR/7b4ea39d-ba0b-4fe6-82a0-3664678f81d2/avx2-div-?forum=vsko'>http://social.msdn.microsoft.com/Forums/ko-KR/7b4ea39d-ba0b-4fe6-82a0-3664678f81d2/avx2-div-?forum=vsko</a> </pre> <br /> 다들 아시는 것처럼 inline assembly 구문은,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { <span style='color: blue; font-weight: bold'> _asm { mov rax, 1111111111 mov ebx, 100 div ebx }</span> return 0; } </pre> <br /> x64 Visual C++ 환경에서는 더 이상 지원하지 않고 있습니다.<br /> <br /> 그런데, 위의 코드에서는 rax 레지스터(eax 레지스터의 64비트 버전)를 사용하고 있는데 당연히 컴파일되지 않습니다. 우선, 매크로 어셈블리를 이야기하기 전에 위의 문제를 짚고 넘어가죠. ^^<br /> <br /> 위의 코드를 빌드하려면 rax를 eax로 바꾸고 x86 타겟으로 Visual C++로 컴파일하면 됩니다. 그런데, 나눗셈 결과를 확인하기 위해 다음과 같이 부가적인 코드를 더 추가한 후 실행해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int a = 0; _asm { mov eax, 1111111111 mov ebx, 100 div ebx mov a, eax } printf("%d\n", a); printf("%d\n", 1111111111 / 100); return 0; } </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;' > 54060784 11111111 </pre> <br /> 오호~~~ 2개의 결과가 같아야 하는데, 다르군요. 왜냐하면 div 어셈블리 명령어는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > The DIV/IDIV Instructions ; <a target='tab' href='http://www.tutorialspoint.com/assembly_programming/assembly_arithmetic_instructions.htm'>http://www.tutorialspoint.com/assembly_programming/assembly_arithmetic_instructions.htm</a> </pre> <br /> 위의 문서에서 볼 수 있는 것처럼, divisor (이 코드에서는 ebx)의 크기가 32비트이면 피젯수(dividend)가 64비트여야 하고 상위 32비트는 EDX 레지스터에, 하위 32비트는 EAX 레지스터에 담겨 있어야 합니다.<br /> <br /> 따라서, 위의 div 연산을 제대로 하려면 edx 레지스터 값을 제대로 초기화해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _asm { <span style='color: blue; font-weight: bold'>mov edx, 0</span> mov eax, 1111111111 mov ebx, 100 div ebx // ... [생략] ... } </pre> <br /> 이렇게 하고 출력해 보면 2개의 값 모두 "11111111"로 일치합니다.<br /> <br /> 기존에 54060784 결과값이 나왔던 것은 edx 레지스터에 임의의 값이 있었기 때문입니다. Visual C++의 경우 _tmain 함수에 전달하는 인자의 수를 보관하기 위한 임시 변수로 edx가 사용되는데, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #ifdef WPRFLAG __winitenv = envp; 009F73A2 mov ecx,dword ptr ds:[0A040F4h] 009F73A8 mov edx,dword ptr ds:[0A03B9Ch] 009F73AE mov dword ptr [ecx],edx mainret = wmain(argc, argv, envp); 009F73B0 mov eax,dword ptr ds:[00A03B9Ch] 009F73B5 push eax 009F73B6 mov ecx,dword ptr ds:[0A03B98h] 009F73BC push ecx <span style='color: blue; font-weight: bold'>009F73BD mov edx,dword ptr ds:[0A03B94h] 009F73C3 push edx </span> 009F73C4 call _wmain (09F1028h) 009F73C9 add esp,0Ch 009F73CC mov dword ptr ds:[00A03B8Ch],eax #else /* WPRFLAG */ __initenv = envp; mainret = main(argc, argv, envp); </pre> <br /> 보시는 바와 같이 edx에 argc 인자에 해당하는 1이 들어 있었기 때문에 결과는 다음과 같았던 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > __int64 num = (1111111111 | ((__int64)1 << 32)); printf("%d\n", num / 100); // 출력 결과: 54060784 </pre> <br /> 물론, 위의 예제 프로그램을 실행할 때 명령행에 인자를 하나 더 주면 edx값은 2가 되고 결과는 97010457로 바뀝니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 테스트를 x64에서 하고 싶으면 더 이상 C++ 소스 코드에서 할 수는 없고 별도로 매크로 어셈블러를 이용해서 연동해야 합니다. 이에 대해서는 다음의 글에서 자세하게 소개하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Using Assembly routines in Visual C++ ; <a target='tab' href='http://www.codeproject.com/Articles/11037/Using-Assembly-routines-in-Visual-C'>http://www.codeproject.com/Articles/11037/Using-Assembly-routines-in-Visual-C</a> </pre> <br /> 매크로 어셈블리 컴파일러는 Visual Studio를 설치했으면 다음의 경로에 이미 32/<a target='tab' href='https://docs.microsoft.com/en-us/cpp/assembler/masm/masm-for-x64-ml64-exe'>64비트</a>용이 있기 때문에 별도로 구하지 않아도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 32비트 컴파일러: "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\ml.exe" 64비트 컴파일러: "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64\ml64.exe" </pre> <br /> x64의 경우 4개 이하의 파라미터는 레지스터를 통해서 전달하므로 제수/피제수로 넘어올 파라미터를 각각 rdx, rcx로부터 받아서 처리하게 바꿔야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .code DivInt64 PROC mov rax, rcx ; dividend mov rbx, rdx ; divisor xor rdx, rdx div rbx ret DivInt64 ENDP END </pre> <br /> 이제 컴파일 해주면 DivInt64.obj 파일이 생기고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ml64.exe /c DivInt64.asm </pre> <br /> 생성된 DivInt64.obj 파일을 Visual Studio의 "솔루션 탐색기(Solution Explorer)"에 있는 Visual C++ 프로젝트에 (파일 탐색기로부터 끌어다 놓는 등의 방법을 이용해) 추가하고, 원래의 C++ 코드를 다음과 같이 바꿔줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" extern "C" int DivInt64(__int64 dividend, __int64 divider); int _tmain(int argc, _TCHAR* argv[]) { int a = 0; a = DivInt64(1111111111, 100); printf("%d\n", a); printf("%d\n", 1111111111 / 100); return 0; } </pre> <br /> 이번에도 동일하게 "11111111" 출력값을 볼 수 있습니다 대신 64비트로 레지스터 값이 바뀌었기 때문에 128비트 나눗셈까지 할 수 있습니다.<br /> <br /> 마지막으로 해당 소스 코드를 x86/x64에 따라 동일하게 컴파일 되도록 preprocessor를 이용해 주면 끝!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "stdafx.h" extern "C" int DivInt64(__int64 dividend, __int64 divider); int _tmain(int argc, _TCHAR* argv[]) { int a = 0; <span style='color: blue; font-weight: bold'>#if _M_AMD64</span> a = DivInt64(1111111111, 100); #else _asm { mov edx, 0 mov eax, 1111111111 mov ebx, 100 div ebx mov a, eax } #endif printf("%d\n", a); printf("%d\n", 1111111111 / 100); return 0; } </pre> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=885&boardid=331301885'>첨부 파일은 위의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6619
(왼쪽의 숫자를 입력해야 합니다.)