2011-02-14 6 views
6

Windows 실행 파일에서 호출 스택을 얻으려고 애를 썼습니다. 호출 스택을 얻기 위해 여러 가지 방법을 시도했습니다. 다음은 몇 가지 예입니다. 그들을 약간 수정하고 오류 처리를 제거하여 쉽게 이해할 수 있도록 컴파일하여 그대로 컴파일하지 않을 수 있습니다. 나는 당신이 요점을 얻은다고 생각합니다.릴리스 빌드에서 호출 스택을 가져 오는 중 문제가 발생했습니다.

간단한 방법 :

const int max_entries = 10; 
void *entries[max_entries]; 
return CaptureStackBackTrace(0, max_entries, entries, 0); 

낮은 수준 방법 :

const int max_entries = 10; 
void *entries[max_entries]; 

void **frame = 0; 
__asm { mov frame, ebp } 
unsigned int i = 0; 
while(frame && i < max_entries) { 
    entries[i++] = frame[1]; 
    frame = (void **)frame[0]; 
} 

호환되는 방법 :

void *entries[max_entries]; 
CONTEXT context; 
RtlCaptureContext(&context); 
STACKFRAME64 stack_frame; 
ZeroMemory(&stack_frame, sizeof(STACKFRAME64)); 
stack_frame.AddrPC.Offset = context.Eip; 
stack_frame.AddrPC.Mode  = AddrModeFlat; 
stack_frame.AddrFrame.Offset = context.Ebp; 
stack_frame.AddrFrame.Mode = AddrModeFlat; 
stack_frame.AddrStack.Offset = context.Esp; 
stack_frame.AddrStack.Mode = AddrModeFlat; 

unsigned int num_frames = 0; 
while (true) { 
    if (!StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), 
     GetCurrentThread(), &stack_frame, &context, NULL, 
     SymFunctionTableAccess64, SymGetModuleBase64, NULL)) 
     break; 

    if (stack_frame.AddrPC.Offset == 0) 
     break; 

    entries[num_frames++] = reinterpret_cast<void *>(stack_frame.AddrPC.Offset); 
} 

내 문제는, 그들이 최적화되지 않은 빌드에서 작동하지만, 에 대한 완전한 최적화가 아닙니다. 무슨 일이 일어나는가 나는 깨진 엔트리 하나를 얻은 다음 루프를 빠져 나가는 것이다. 디버그에서 전체 호출 스택을 얻었고 나중에 심볼을 찾을 때 모두 정확합니다.

디버거가 항상 수행 할 때 모든 빌드에서이 작업을 수행하는 것이 얼마나 어려울 지 이해할 수 없습니다. 나는 특별히 프레임 포인터가 코드 생성에서 생략되지 않는다고 말할 수있다. 먼저 디버그 용으로 빌드 한 다음 최적화를 none에서 전체 최적화로 변경하고 호출 스택 오류를 재현하기 위해 다시 작성합니다.

해결책에 대한 모든 힌트를 크게 주시면 감사하겠습니다.

/조나스

+0

프레임 포인터 최적화 기능을 켜고 컴파일하지 않습니까? –

+0

프레임 포인터가 코드 생성에서 생략되지 않는다. –

답변

2

"호환 가능한 방식"으로 작업하고 있습니다. 다음 코드를 사용하여 컨텍스트를 초기화합니다.

#define GET_CURRENT_CONTEXT(c, contextFlags) \ 
    do { \ 
     memset(&c, 0, sizeof(CONTEXT)); \ 
     c.ContextFlags = contextFlags; \ 
     __asm call x \ 
     __asm x: pop eax \ 
     __asm mov c.Eip, eax \ 
     __asm mov c.Ebp, ebp \ 
     __asm mov c.Esp, esp \ 
    } while(0); 

CONTEXT context; 
GET_CURRENT_CONTEXT(context, CONTEXT_FULL); 

그런 다음 이전과 같이 StackWalk64를 사용하여 스택을 계속 페치합니다. 내가 RtlCaptureContext로 보내기 전에 CONTEXT 구조를 취소하는 것을 잊었다 것으로 나타났습니다

void *entries[max_entries]; 
STACKFRAME64 stack_frame; 
ZeroMemory(&stack_frame, sizeof(STACKFRAME64)); 
stack_frame.AddrPC.Offset = context.Eip; 
stack_frame.AddrPC.Mode  = AddrModeFlat; 
stack_frame.AddrFrame.Offset = context.Ebp; 
stack_frame.AddrFrame.Mode = AddrModeFlat; 
stack_frame.AddrStack.Offset = context.Esp; 
stack_frame.AddrStack.Mode = AddrModeFlat; 

unsigned int num_frames = 0; 
while (true) { 
    if (!StackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), 
     GetCurrentThread(), &stack_frame, &context, NULL, 
     SymFunctionTableAccess64, SymGetModuleBase64, NULL)) 
     break; 

    if (stack_frame.AddrPC.Offset == 0) 
     break; 

    entries[num_frames++] = reinterpret_cast<void *>(stack_frame.AddrPC.Offset); 
} 

은 그래서 (내가 RtlCaptureContext 기능을 사용하는 것을 선호하기 때문에) 이런 식으로 작업을 수행하려고 노력했다.

CONTEXT context; 
memset(&context, 0, sizeof(CONTEXT)); 
context.ContextFlags = CONTEXT_FULL; 
RtlCaptureContext(&context); 

지금 RtlCaptureContext 충돌, 그래서 GET_CURRENT_CONTEXT 매크로를 사용하여 다시 갔다.

내가 RtlCaptureContext 내부 만 디버그 빌드 32 비트에서가 아닌 32 비트 릴리스에 산발적 인 충돌을보고 있어요 :

+0

안녕하세요 Jonas, 검색된 항목 []을 최적화 된 빌드의 실제 함수 이름에 매핑 할 수 있습니까? 나는 잘못된 결과를 얻는다. 어떤 모범이 있습니까? –

0

이 특정 버전을 명확하게 대답하지만, 그냥 "나도"보고하지 않습니다 빌드 또는 디버그 또는 릴리스 64 비트 빌드가 아닙니다. 2011 년 4 월 25 일에 다운로드 한 Windows 용 디버깅 도구의 dbghelp.dll fileVersion 6.12.2.633과 함께 VS2008 SP1을 사용하고 dbghelp.dll을 호출하기 전에 내 EXE와 동일한 디렉토리에 복사했습니다.

이것은 동일한 Windows XP 64 비트 SP2 컴퓨터에서 동일한 VS2008 SP1 컴파일러 릴리스를 사용하여 컴파일하는 것입니다 (32 비트 및 64 비트 기본 응용 프로그램 모두 컴파일, .NET 관리 코드는 혼합).

위의 키는 sporadic 자연입니다. 나는 그것이 부서지는 조건을 결정하지 않았다.

관련 문제