성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Visual Studio에서 Mono용 Profiler 개발</h1> <p> Microsoft .NET용 프로파일러는 문서화가 잘 되어 있는 반면, Mono용은 쉽게 찾을 수 없어서 정리를 해봤습니다. (미리 말해두자면, Microsoft .NET용 프로파일러 제작 방법과는 많이 다릅니다.)<br /> <br /> 우선, Mono 소스 코드를 먼저 다운로드 받아서 빌드해야 합니다. 이에 대해서는 저번 글에 썼으니 참고해서 빌드해 둡니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Visual Studio 2013에서 Mono 컴파일하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1796'>http://www.sysnet.pe.kr/2/0/1796</a> </pre> <br /> 그다음, 프로파일러용 Win32 DLL Visual C++ 프로젝트를 만들고, 프로젝트 속성에서 헤더 및 라이브러리 폴더를 지정해야 하는데요. 각각 다음과 같은 경로로 포함하면 됩니다. (컴파일된 모노가 E:\mono 폴더라고 가정)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ===== Include 추가 경로 ===== e:\mono\mono\metadata e:\mono\mono\eglib\src ===== Library 추가 경로 ===== e:\mono\msvc\Win32\lib </pre> <br /> (사실, 모노를 빌드하는 이유는 단 하나입니다. lib 폴더의 eglib.lib 파일을 쉽게 얻기 위해 빌드한 것입니다.)<br /> <br /> 소스 코드는 이렇게 시작할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ================================ PerfProfiler.h #ifdef PERFPROFILER_EXPORTS #define PERFPROFILER_API __declspec(dllexport) #else #define PERFPROFILER_API __declspec(dllimport) #endif extern "C" { PERFPROFILER_API void mono_profiler_startup(const char *args); }; </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ================================ PerfProfiler.cpp #include "stdafx.h" #include <mono/metadata/profiler.h> #include <mono/metadata/debug-helpers.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <eglib\src\glib.h> #pragma comment(lib, "eglib.lib") #include "PerfProfiler.h" <span style='color: blue; font-weight: bold'>PERFPROFILER_API void mono_profiler_startup(const char *args)</span> { } </pre> <br /> mono_profiler_startup 함수에서 해야 할 대표적인 작업은 2가지입니다.<br /> <br /> <ol> <li>프로파일링 시, 콜백 함수에 전달될 임의의 문맥 정보 생성</li> <li>Mono 측에 어떤 범주의 프로파일링을 원하는지를 알린다.</li> </ol> <br /> 1단계 먼저 살펴보면, 문맥 정보는 포인터를 통해 콜백 함수에 전달되므로 원하는 구조체를 정의해서 포인터로 넘겨주면 됩니다. 관례인지는 모르겠지만 그 구조체의 이름을 _MonoProfiler, MonoProfiler로 따르는 것 같습니다. 따라서, 저도 다음과 같이 정의해 봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct _MonoProfiler { size_t _jitCount = 0; }; typedef _MonoProfiler MonoProfiler; </pre> <br /> 이 구조체를 동적 할당한 후 mono_profiler_install 메서드에 넘겨주면 이후의 CALLBACK 함수에서 이 포인터를 넘겨줍니다. 물론, 관심있는 callback 함수의 등록 및 이벤트 등록도 함께 해주는 것은 그나마 Microsoft .NET Profiler 만드는 요령과 닮았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static HMODULE hMonoModule = nullptr; typedef void(*mono_profiler_installFunc) (MonoProfiler *prof, MonoProfileFunc shutdown_callback); typedef void(*mono_profiler_install_jit_compileFunc) (MonoProfileMethodFunc enterCallback, MonoProfileMethodResult leaveCallback); typedef void(*mono_profiler_set_eventsFunc) (MonoProfileFlags events); static mono_profiler_installFunc g_mono_profiler_install; static mono_profiler_install_jit_compileFunc g_mono_profiler_install_jit_compile; static mono_profiler_set_eventsFunc g_mono_profiler_set_events; void mono_profiler_shutdown_callback(MonoProfiler *prof) { } void mono_profiler_jit_compile_enter(MonoProfiler *prof, MonoMethod *method) { } void mono_profiler_jit_compile_leave(MonoProfiler *prof, MonoMethod *method, int result) { } PERFPROFILER_API void mono_profiler_startup(const char *args) { <span style='color: blue; font-weight: bold'>MonoProfiler *prof = g_new0(MonoProfiler, 1);</span> (void) args; <span style='color: blue; font-weight: bold'>hMonoModule = LoadLibrary(L"libmonosgen-2.0.dll");</span> // 또는 // hMonoModule = LoadLibrary(L"mono.dll"); if (hMonoModule == nullptr) { return; } else { <span style='color: blue; font-weight: bold'>g_mono_profiler_install = (mono_profiler_installFunc)GetProcAddress(hMonoModule, "mono_profiler_install");</span> g_mono_profiler_install_jit_compile = (mono_profiler_install_jit_compileFunc)GetProcAddress(hMonoModule, "mono_profiler_install_jit_compile"); g_mono_profiler_set_events = (mono_profiler_set_eventsFunc)GetProcAddress(hMonoModule, "mono_profiler_set_events"); g_mono_profiler_install(prof, mono_profiler_shutdown_callback); g_mono_profiler_install_jit_compile(mono_profiler_jit_compile_enter, mono_profiler_jit_compile_leave); g_mono_profiler_set_events(MONO_PROFILE_JIT_COMPILATION); } } </pre> <br /> 위의 소스 코드를 찬찬히 뜯어 보겠습니다. ^^ 우선, g_new0과 같은 다소 생소한 동적 할당 함수를 사용하고 있는데요. 알고 보니 이것은 Glib라는 별도의 라이브러리였습니다. (eglib.lib가 필요한 이유입니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Glib 라이브러리 ; <a target='tab' href='http://edgar.tistory.com/25'>http://edgar.tistory.com/25</a> </pre> <br /> Mono에서 크로스 플랫폼을 위해 광범위하게 이 라이브러리를 사용하고 있으므로 우리가 만드는 프로파일러도 기왕이면 이것을 따르는 것이 좋을 듯 합니다.<br /> <br /> 그다음 LoadLibrary를 통해 libmonosgen-2.0.dll을 로드하는 작업입니다. 모노 3.2.3 미만의 버전에서는 "mono.dll"을 로드해야 하고, 그 이후부터는 GC 구현체에 따라 "Boehm", "SGen" 유형으로 나뉘므로 각각 libmonoboehm-2.0.dll, libmonosgen-2.0.dll을 상황에 따라 선택해야 합니다.<br /> <br /> 그런데, 딱히 이걸 Profiler 입장에서 선택할 수 있는 기준이 없습니다. 따라서, LoadLibary보다는 이미 메모리에 올라와 있는 Mono 런타임 모듈을 구하기 위해 GetModuleHandle API를 사용하는 것이 (제가 생각하기에는) 더 나은 방법입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > hMonoModule = GetModuleHandle(L"libmonosgen-2.0.dll"); if (hMonoModule == nullptr) { hMonoModule = GetModuleHandle(L"libmonoboehm-2.0.dll"); } if (hMonoModule == nullptr) { hMonoModule = GetModuleHandle(L"mono.dll"); } </pre> <br /> 이후 mono_profiler_install API를 동적 로드해서 모노 측에 관심있는 프로파일링 동작을 등록하면 됩니다. 위의 예제에서는 JIT 컴파일 전/후에 모노로 하여금 콜백 함수를 부르도록 등록하고 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이렇게 만들고 빌드하면 DLL 파일이 생성되겠지요? 저는 프로젝트를 PerfProfiler.vcxproj로 만들었기 때문에 출력 DLL의 이름은 PerfProfiler.dll로 됩니다. 문제는, Mono는 profiler를 위해 특별한 명명 규칙이 있기 때문에 이를 따라야 합니다. 기본적으로 "mono-profiler-"라는 prefix를 가지고 있기 때문에 제가 만든 DLL도 그것에 맞게 "mono-profiler-perf.dll"로 출력되도록 이름을 바꿨습니다.<br /> <br /> 바뀐 이름으로 프로파일링을 적용할 때는 접두사를 뺀 부분의 문자열만 전달해야 합니다. 예를 들어, 제가 만든 DLL의 이름이 "mono-profiler-perf.dll"이므로 mono에서 다음과 같은 옵션으로 실행해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > mono <span style='color: blue; font-weight: bold'>--profile=perf.dll</span> [대상EXE파일] </pre> <br /> 방법을 알았으니, 실제 테스트를 위해 모노 런타임을 준비해야 하는데요. 아쉽게도 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/1796'>Visual Studio 2013에서 Mono 컴파일하는 방법</a>" 글에서 빌드한 것은 단지 프로파일러를 컴파일하기 위해 Import Library가 필요해서 한 것일 뿐, 이때 빌드된 mono.exe는 써 먹을 수 없습니다. 왜냐하면 그걸 사용하려면 클래스 라이브러리(mscorlib.dll)까지 모두 빌드해야 하는데, 이 과정이 좀 복잡합니다.<br /> <br /> 이 때문에 제 경우에 모노 런타임을 다운로드 받았는데요. (version 3.2.3)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Mono for Windows is available as a Windows Installer file ; <a target='tab' href='http://www.mono-project.com/download/#download-win'>http://www.mono-project.com/download/#download-win</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;' > E:\mono\mre\bin><span style='color: blue; font-weight: bold'>mono --profile=perf.dll TestConsole.exe</span> Hello World! </pre> <br /> 자... 여기까지 실습했으면, 이제 Visual Studio의 "F5 디버깅"이 되도록 구성할 수 있습니다. Visual C++의 속성창에서 "Debugging" 범주의 값을 다음과 같이 맞춰 주고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Command: E:\mono\mre\bin\mono.exe Command Arguments: --profile=perf.dll "E:\mono\mre\bin\TestConsole.exe" Working Directory: E:\mono\mre\bin </pre> <br /> "General"의 "Output Directory" 값을 "E:\mono\mre\bin"로 해줘서 빌드된 DLL이 해당 폴더에 생성되도록 해주면, 이제부터 "F5"를 눌러 실행할 때마다 다음과 같이 BreakPoint를 사용할 수 있는 디버그 모드로 진입하게 됩니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='mono_profiler_1.png' src='/SysWebRes/bbs/mono_profiler_1.png' /><br /> <br /> 오~~~~ 멋지군요. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=900&boardid=331301885'>첨부한 파일은 이 글의 프로파일러 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> (여기부터는 시행 착오를 겪은 것입니다.)<br /> <br /> 잘못 빌드한 경우 다음과 같은 오류가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > E:\mono\mre\bin><span style='color: blue; font-weight: bold'>mono --profile=perf.dll TestConsole.exe</span> The 'perf.dll' profiler wasn't found in the main executable nor could it be loaded from 'mono-profiler-perf.dll'. Hello World! </pre> <br /> 제가 겪은 바로는 mono-2.0.dll과 같은 특정 DLL에 의존해서 빌드하면 안 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Unity3d 제품에 포함된 모노 런타임에서도 잘 실행이 됩니다. 단지, Unity3d의 경우 이전 버전의 모노를 사용하기 때문에 프로파일러 내부에서 로드하게 되는 런타임 DLL은 mono.dll입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > E:\Program Files (x86)\Unity\Editor\Data\Mono\bin>mono --profile=perf.dll TestConsole.exe Hello World! </pre> <br /> <hr style='width: 50%' /><br /> <br /> 리눅스 환경을 지원하겠다면 철저하게 초기부터 cross-platform을 염두에 두어야 합니다. 가령 GetModuleHandle부터 Win32 API이기 때문에 사용이 불가능하다는 점!<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6259
(왼쪽의 숫자를 입력해야 합니다.)