사용자 도구

사이트 도구


kb:debugengine

Debug Engine

WinDbg에서 사용하는 모듈. dbghlp 모듈에서 제공하는 기능과 겹치는 부분도 있고 아닌 부분도 있고…

Debugging tools for windows 설치하면, 설치 디렉토리에 debugger.chm 헬프 파일이 있다. 문제는 이게 다라는 것. 헬프 내용도 별로고, 웹에도 별 내용 없고… 슬프다.

사실 넥슨의 김이선 님이 NDC 2012에서 발표하신 덤프 파일을 통한 사후 디버깅 실용 테크닉을 보고 삘받아 좀 파봤다.

기본 초기화

CoInitialize();
DebugCreate(__uuidof(IDebugClient4), (void**)(&client));
client->SetOutputCallbacks(outputCallback);
client->SetEventCallbacks(eventCallback);
client->QueryInterface(__uuidof(IDebugControl), (void**)(&control));
client->QueryInterface(__uuidof(IDebugSystemObjects), (void**)(&systemObjects));
client->QueryInterface(__uuidof(IDebugDataSpaces), (void**)(&dataSpaces));
client->QueryInterface(__uuidof(IDebugSymbols3), (void**)(&symbols));

덤프 파일 열기

symbols->SetSymbolPathWide();
CreateFile(fileName, ...);
client->OpenDumpFileWide(fileName, (ULONG64)hFile);
control->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE); // 요거 중요
symbols->SetScopeFromStoredEvent();

실행 중인 프로세스에 붙어서 스레드별 콜스택 가져오기

샘플링 기반 프로파일러를 구현할 때 사용해봄직하다.

symbols->SetSymbolOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
symbols->SetSymbolPathWide();
client->AttachProcess(0, processId, 0);
//control->GetDebuggeeType(); //debugeeTypeClass == DEBUG_CLASS_USER_WINDOWS && debugeeTypeQualfier == DEBUG_USER_WINDOWS_PROCESS
client->FlushCallbacks();
...
client->WaitForEvent(delay);
systemObjects->GetTotalNumberThread();
for (ULONG i=0; i < numberThreads; ++i)
{
    systemObjects->SetCurrentThreadId(i);
    ULONG threadSystemId = systemObjects->GetCurrentThreadSystemId();
 
    DEBUG_STACK_FRAME frameArray[512];
 
    control->GetStackTrace(frameArray, ARRAYSIZE(frameArray), &frameCount);
 
    for (ULONG j=0; j<frameCount; ++j)
    {
        ULONG offset = frameArray[i].InstructonOffset;
        symbols->GetNameByOffsetWide(offset, ...);
        symbols->GetFunctionEntryByOffset(offset, 0, ...);
        symbols->getLineByOffsetWide(offset, );
    }
}

메모리 검색

1. 기본

1.1. 그냥 메모리 검색

dataSpaces->SearchVirtual2(offset, 0xFFFFFFFFFFFFFFFF, DEBUG_VSEARCH_WRITABLE_ONLY, pattern, patternSize, 1, &offset);

1.2. 심볼 주소 찾기

symbols->StartSymbolMatchWide();
while (symbols->GetNextSymbolMatchWide())
    ....
symbols->EndSymbolMatch();

1.3. 클래스/구조체 크기 알아내기 및 멤버 변수 옵셋 알아내기

GetTypeIdWide(module, className, &classId); // 모듈과 클래스 이름으로 심볼 ID 알아낸다.
GetTypeSize(module, classId, &classSize); // 심볼 ID로 클래스 크기를 알아낸다.
GetFieldOffsetWide(module, classId, memberName, &memberOffset); // 멤버 변수의 옵셋을 알아낸다.

2. 응용

2.1. vftable 가지는 클래스 인스턴스 찾기

같은 종류의 클래스는 같은 vftable을 가진다. 즉 다음과 같다.

1. 클래스 이름을 통해 vftable에 대한 심볼을 구성한다.

TCHAR vftable[256];
_sntprintf_s(vftable, ARRAYSIZE(vftable), _TRUNCATE, _T("%s!%s::`vftable'"), moduleName, className);

2. 이 심볼에 대한 주소를 알아낸다. 다중 상속 받은 클래스는 심볼 주소가 여러개 나온다.

ULONG64 handle = 0;
if (symbols->StartSymbolMatchWide(vftable, &handle) == S_OK)
{
    TCHAR symbolName[1024];
    ULONG64 symbolOffset = 0;
    while (symbols->GetNextSymbolMatchWide(handle, symbolName, ARRAYSIZE(symbolName), NULL, &symbolOffset) == S_OK
    {
        ...
    }
}

3. 힙 내에서 이 주소를 가르키고 있는 메모리들을 찾으면, 같은 타입의 객체들을 찾는 거라고 볼 수 있다.

ULONG64 offset = 0;
while (dataSpaces->SearchVirtual2(
    offset, 0xFFFFFFFFFFFFFFFF, DEBUG_VSEARCH_WRITABLE_ONLY, 
    &symbolOffset, sizeof(symbolOffset, sizeof(void*), &offset) == S_OK)
{
    ... // 찾았당~
}

당연히 실제 객체보다 좀 더 많이 나올 수 있다. 멤버 변수 값을 비교해 필터링하는 것이 좋다.

링크

다운로드

kb/debugengine.txt · 마지막으로 수정됨: 2014/11/12 10:43 저자 excel96