programing

Win32 - C 코드에서 역추적

bestcode 2022. 8. 9. 21:43
반응형

Win32 - C 코드에서 역추적

현재 Windows에서 C코드(C++ 없음)에서 역추적 정보를 얻을 수 있는 방법을 찾고 있습니다.

레퍼런스 카운팅 메모리 관리 기능을 갖춘 크로스 플랫폼 C 라이브러리를 구축하고 있습니다.또한 메모리 오류에 대한 정보를 제공하는 내장 메모리 디버거(XEOS C Foundation 라이브러리)도 있습니다.

장애가 발생하면 장애에 대한 정보와 관련된 메모리 레코드를 제공하는 디버거가 실행됩니다.

여기에 이미지 설명 입력

또는 (Mac OS X)를 검색할 수 .execinfo.hbacktrace메모리 장애에 대한 추가 정보를 표시할 수 있습니다.

Windows에서도 같은 것을 찾고 있습니다.

스택 오버플로에서 C?에서 스택 트레이스를 잡는 방법을 봤어요.서드파티제 라이브러리를 사용하고 싶지 않기 때문에CaptureStackBackTrace ★★★★★★★★★★★★★★★★★」StackWalk기능은 좋아 보입니다.

다만, Microsoft 의 메뉴얼에서도, 사용법을 알 수 없는 것이 문제입니다.

저는 POSIX 호환 시스템에서 작업하기 때문에 Windows 프로그래밍에 익숙하지 않습니다.

이러한 기능에 대한 설명과 예시는 무엇입니까?

편집

는 지금 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★CaptureStackBackTraceDbgHelp.lib'그냥'은...

지금까지 제가 시도한 것은 다음과 같습니다.

unsigned int   i;
void         * stack[ 100 ];
unsigned short frames;
SYMBOL_INFO    symbol;
HANDLE         process;

process = GetCurrentProcess();

SymInitialize( process, NULL, TRUE );

frames = CaptureStackBackTrace( 0, 100, stack, NULL );

for( i = 0; i < frames; i++ )
{
    SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, &symbol );

    printf( "%s\n", symbol.Name );
}

난 그냥 잡동사니야. 제가 하다 말고 걸 요.SymFromAddr.

네, 알겠습니다. : )

문제는 심볼_INFO 구조에 있었습니다.힙에 할당하고 기호 이름을 위한 공간을 예약한 후 올바르게 초기화해야 합니다.

최종 코드는 다음과 같습니다.

void printStack( void );
void printStack( void )
{
     unsigned int   i;
     void         * stack[ 100 ];
     unsigned short frames;
     SYMBOL_INFO  * symbol;
     HANDLE         process;

     process = GetCurrentProcess();

     SymInitialize( process, NULL, TRUE );

     frames               = CaptureStackBackTrace( 0, 100, stack, NULL );
     symbol               = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
     symbol->MaxNameLen   = 255;
     symbol->SizeOfStruct = sizeof( SYMBOL_INFO );

     for( i = 0; i < frames; i++ )
     {
         SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );

         printf( "%i: %s - 0x%0X\n", frames - i - 1, symbol->Name, symbol->Address );
     }

     free( symbol );
}

출력:

6: printStack - 0xD2430
5: wmain - 0xD28F0
4: __tmainCRTStartup - 0xE5010
3: wmainCRTStartup - 0xE4FF0
2: BaseThreadInitThunk - 0x75BE3665
1: RtlInitializeExceptionChain - 0x770F9D0F
0: RtlInitializeExceptionChain - 0x770F9D0F

C++ Builder 앱에서 스택을 읽을 때 사용하는 초저파이 대안입니다.이 코드는 크래시가 발생하여 스택이 cs 배열에 들어갈 때 프로세스 자체 내에서 실행됩니다.

    int cslev = 0;
    void* cs[300];
    void* it = <ebp at time of crash>;
    void* rm[2];
    while(it && cslev<300)
    {
            /* Could just memcpy instead of ReadProcessMemory, but who knows if 
               the stack's valid? If  it's invalid, memcpy could cause an AV, which is
               pretty much exactly what we don't want
            */
            err=ReadProcessMemory(GetCurrentProcess(),it,(LPVOID)rm,sizeof(rm),NULL);
            if(!err)
                    break;
            it=rm[0];
            cs[cslev++]=(void*)rm[1];
    }

갱신하다

스택을 입수하면 이름을 번역합니다.은, 「 」와의 상호 에 의해서 ..mapC++Builder입니다.포맷은 다소 다르지만 다른 컴파일러의 맵 파일에서도 같은 작업을 수행할 수 있습니다.C++Builder를 사용하다이 역시 매우 저파이이며 MS의 표준 작업 방식은 아닐 수 있지만, 제 상황에서는 작동합니다.다음 코드는 최종 사용자에게 전달되지 않습니다.

char linbuf[300];
char *pars;
unsigned long coff,lngth,csect;
unsigned long thisa,sect;
char *fns[300];
unsigned int maxs[300];
FILE *map;

map = fopen(mapname, "r");
if (!map)
{
    ...Add error handling for missing map...
}

do
{
    fgets(linbuf,300,map);
} while (!strstr(linbuf,"CODE"));
csect=strtoul(linbuf,&pars,16); /* Find out code segment number */
pars++; /* Skip colon */
coff=strtoul(pars,&pars,16); /* Find out code offset */
lngth=strtoul(pars,NULL,16); /* Find out code length */
do
{
    fgets(linbuf,300,map);
} while (!strstr(linbuf,"Publics by Name"));

for(lop=0;lop!=cslev;lop++)
{
    fns[lop] = NULL;
    maxs[lop] = 0;
}
do
{
    fgets(linbuf,300,map);
    sect=strtoul(linbuf,&pars,16);
    if(sect!=csect)
        continue;
    pars++;
    thisa=strtoul(pars,&pars,16);
    for(lop=0;lop!=cslev;lop++)
    {
        if(cs[lop]<coff || cs[lop]>coff+lngth)
            continue;
        if(thisa<cs[lop]-coff && thisa>maxs[lop])
        {
            maxs[lop]=thisa;
            while(*pars==' ')
                pars++;
            fns[lop] = fnsbuf+(100*lop);
            fnlen = strlen(pars);
            if (fnlen>100)
                fnlen = 100;
            strncpy(fns[lop], pars, 99);
            fns[lop][fnlen-1]='\0';
        }
    }
} while (!feof(map));
fclose(map);

이 코드를 실행한 후fnsarray 에는 .map 파일의 best-filename 함수가 포함되어 있습니다.

제 상황에서는 PHP 스크립트에 제출되는 첫 번째 코드 조각에 의해 생성된 콜 스택을 가지고 있습니다.PHP 조각을 사용하여 위의 C 코드와 동등한 작업을 수행합니다.이 첫 번째 비트는 맵파일을 해석합니다(이것도 C++Builder 맵에서 동작하지만 다른 맵파일 형식에 쉽게 적응할 수 있습니다).

            $file = fopen($mapdir.$app."-".$appversion.".map","r");
            if (!$file)
                    ... Error handling for missing map ...
            do
            {
                    $mapline = fgets($file);
            } while (!strstr($mapline,"CODE"));
            $tokens = split("[[:space:]\:]", $mapline);
            $codeseg = $tokens[1];
            $codestart = intval($tokens[2],16);
            $codelen = intval($tokens[3],16);
            do
            {
                    $mapline = fgets($file);
            } while (!strstr($mapline,"Publics by Value"));
            fgets($file); // Blank
            $addrnum = 0;
            $lastaddr = 0;
            while (1)
            {
                    if (feof($file))
                            break;
                    $mapline = fgets($file);
                    $tokens = split("[[:space:]\:]", $mapline);
                    $thisseg = $tokens[1];
                    if ($thisseg!=$codeseg)
                            break;
                    $addrs[$addrnum] = intval($tokens[2],16);
                    if ($addrs[$addrnum]==$lastaddr)
                            continue;
                    $lastaddr = $addrs[$addrnum];
                    $funcs[$addrnum] = trim(substr($mapline, 16));
                    $addrnum++;
            }
            fclose($file);

다음으로 이 비트는 주소를 변환합니다(in).$rowaddr(및 기능 뒤의 오프셋)을 특정 함수로 변환합니다.

                    $thisaddr = intval($rowaddr,16);
                    $thisaddr -= $codestart;
                    if ($thisaddr>=0 && $thisaddr<=$codelen)
                    {
                            for ($lop=0; $lop!=$addrnum; $lop++)
                                    if ($thisaddr<$addrs[$lop])
                                            break;
                    }
                    else
                            $lop = $addrnum;
                    if ($lop!=$addrnum)
                    {
                            $lop--;
                            $lines[$ix] = substr($line,0,13).$rowaddr." : ".$funcs[$lop]." (+".sprintf("%04X",$thisaddr-$addrs[$lop]).")";
                            $stack .= $rowaddr;
                    }
                    else
                    {
                            $lines[$ix] = substr($line,0,13).$rowaddr." : external";
                    }

@Jon Bright:스택이 유효한지 아닌지를 누가 알았느냐고 말합니다.「」: 스택주소를 알고 있기 때문에, 특정 방법이 있습니다.물론 현재 스레드에 트레이스가 필요한 경우:

    NT_TIB*     pTEB = GetTEB();
    UINT_PTR    ebp = GetEBPForStackTrace();
    HANDLE      hCurProc = ::GetCurrentProcess();

    while (
        ((ebp & 3) == 0) &&
        ebp + 2*sizeof(VOID*) < (UINT_PTR)pTEB->StackBase &&
        ebp >= (UINT_PTR)pTEB->StackLimit &&
        nAddresses < nTraceBuffers)
        {
        pTraces[nAddresses++]._EIP = ((UINT_PTR*)ebp)[1];
        ebp = ((UINT_PTR*)ebp)[0];
        }

GetTEB()는 NTDLL의 NtCurrentTeb()입니다.DLL - 현재 MSDN에 기재되어 있는 것은 Windows 7 이후뿐만이 아닙니다.MS는 문서를 정리합니다.그것은 오랫동안 그곳에 있었다.스레드 환경 블록(TEB)을 사용하면 스택의 하한과 상한을 알 수 있으므로 Read Process Memory()가 필요하지 않습니다.이게 가장 빠른 방법인 것 같아요.

MS 컴파일러 GetEB 사용PForStackTrace()는 다음과 같습니다.

inline __declspec(naked) UINT_PTR GetEBPForStackTrace()
{
    __asm
        {
        mov eax, ebp
        ret
        }
}

현재 스레드의 EBP를 쉽게 취득할 수 있습니다(단, 현재 스레드의 경우 유효한 EBP를 이 루프에 전달할 수 있습니다).

제한:이것은, Windows 의 x86 에 유효합니다.

언급URL : https://stackoverflow.com/questions/5693192/win32-backtrace-from-c-code

반응형