C에서 스택 추적을 어떻게 잡을 수 있습니까?


83

이 작업을 수행하는 표준 C 함수가 없다는 것을 알고 있습니다. Windows와 * nix에서 이에 대한 기술이 무엇인지 궁금합니다. (Windows XP는 지금이 작업을 수행하는 가장 중요한 OS입니다.)


답변:


81

glibc는 backtrace () 함수를 제공합니다.

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html


6
glibc FTW ... 다시. (이 아직 나는 그것이) (C 프로그래밍에 오는 그것으로가는 컴파일러 때의 glibc는 절대 금 표준이라고 생각하는 또 다른 이유입니다.)
트레버 보이드 스미스

4
하지만 더 기다려주세요! backtrace () 함수는 콜 스택 함수를 나타내는 void * 포인터의 배열 만 제공합니다. "별로 유용하지 않습니다. arg." 두려워하지 마세요! glibc는 모든 void * 주소 (calltack 함수 주소)를 사람이 읽을 수있는 문자열 기호로 변환하는 함수를 제공합니다. char ** backtrace_symbols (void *const *buffer, int size)
Trevor Boyd Smith

1
주의 사항 : C 함수에서만 작동한다고 생각합니다. @Trevor : ELF 테이블에서 주소로 syms를 찾고 있습니다.
Conrad Meyer

2
void backtrace_symbols_fd(void *const *buffer, int size, int fd)예를 들어 출력을 stdout / err에 직접 보낼 수도 있습니다.
wkz apr

2
backtrace_symbols()짜증나. 모든 심볼을 내 보내야하며 DWARF (디버깅) 심볼을 지원하지 않습니다. libbacktrace는 대부분의 경우 훨씬 더 나은 옵션입니다.
Erwan Legrand

29

backtrace () 및 backtrace_symbols ()가 있습니다.

man 페이지에서 :

#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
    printf("%s\n", strs[i]);
}
free(strs);
...

보다 편리한 / OOP 방식으로 이것을 사용하는 한 가지 방법은 예외 클래스 생성자에 backtrace_symbols ()의 결과를 저장하는 것입니다. 따라서 해당 유형의 예외를 throw 할 때마다 스택 추적이 있습니다. 그런 다음 인쇄 기능을 제공하십시오. 예를 들면 :

class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i < frames; ++i) {
            printf("%s\n", strs[i]);
        }
        free(strs);
    }
};

...

try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

따다!

참고 : 최적화 플래그를 활성화하면 결과 스택 추적이 부정확해질 수 있습니다. 이상적으로는 디버그 플래그를 켜고 최적화 플래그를 끈 상태에서이 기능을 사용하는 것이 좋습니다.


@shuckc는 주소를 기호 문자열로 변환하는 데만 사용되며 필요한 경우 다른 도구를 사용하여 외부에서 수행 할 수 있습니다.
Woodrow Barlow

22

Windows의 경우 StackWalk64 () API (32 비트 Windows에서도)를 확인하십시오. UNIX의 경우 OS의 기본 방식을 사용하거나 가능한 경우 glibc의 backtrace ()로 대체해야합니다.

그러나 네이티브 코드에서 Stacktrace를 사용하는 것은 거의 좋은 생각이 아닙니다. 가능하지 않기 때문이 아니라 일반적으로 잘못된 것을 달성하려고하기 때문입니다.

대부분의 경우 사람들은 예외가 잡히면 어설 션이 실패하거나 최악의 경우와 같은 예외적 인 상황에서 스택 추적을 시도하거나 치명적인 "예외"또는 세분화 위반.

마지막 문제를 고려할 때 대부분의 API는 명시 적으로 메모리를 할당하거나 내부적으로 할당해야합니다. 프로그램이 현재있을 수있는 취약한 상태에서 그렇게하면 상황이 더욱 악화 될 수 있습니다. 예를 들어, 충돌 보고서 (또는 코어 덤프)는 문제의 실제 원인을 반영하지 않지만 문제를 처리하려는 실패한 시도를 반영합니다.

대부분의 사람들이 스택 추적을 얻을 때이를 시도하는 것처럼 보이는 것처럼 치명적인 오류 처리를 시도하고 있다고 가정합니다. 그렇다면 디버거 (개발 중)에 의존하고 프로덕션에서 프로세스 코어 덤프 (또는 Windows의 미니 덤프)를 허용합니다. 적절한 기호 관리와 함께 사후에 원인이되는 명령어를 파악하는 데 문제가 없어야합니다.


2
신호 또는 예외 처리기에서 메모리 할당을 시도하는 것이 깨지기 쉬운 것에 대해 맞습니다. 한 가지 가능한 방법은 프로그램 시작시 고정 된 양의 "비상"공간을 할당하거나 정적 버퍼를 사용하는 것입니다.
j_random_hacker

또 다른 방법은 밖으로는 독립적으로 실행되는 코어 덤프 서비스, 만드는
Kobor42

5

unwind 라이브러리를 사용해야합니다 .

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

공유 라이브러리에서 호출하지 않으면 접근 방식도 잘 작동합니다.

addr2lineLinux 에서 명령을 사용 하여 해당 PC의 소스 기능 / 줄 번호를 가져올 수 있습니다 .


"소스 기능 / 행 번호"? 링크가 감소 된 코드 크기에 최적화되어 있다면 어떨까요? 그러나 이것은 유용한 프로젝트처럼 보인다고 말할 것입니다. 레지스터를 얻을 방법이 없다는 유감입니다. 나는 이것을 확실히 조사 할 것이다. 절대적으로 프로세서 독립적이라는 것을 알고 있습니까? C 컴파일러가있는 모든 작업에서 작동합니까?
Mawg는 분석 재개 모니카 말한다

1
이 주석은 유용한 addr2line 명령이 언급 되었기 때문에 그만한 가치가 있습니다!
Ogre Psalm33

addr2line은 ASLR을 사용하는 시스템에서 재배치 가능한 코드에 대해 실패합니다 (즉, 지난 10 년 동안 사람들이 사용해온 대부분).
Erwan Legrand

4

이를 수행하는 플랫폼 독립적 인 방법은 없습니다.

가장 가까운 방법은 최적화없이 코드를 실행하는 것입니다. 이렇게하면 (Visual C ++ 디버거 또는 GDB를 사용하여) 프로세스에 연결하고 사용 가능한 스택 추적을 얻을 수 있습니다.


현장에 내장 된 컴퓨터에서 충돌이 발생하면 도움이되지 않습니다. :(
Kevin

@Kevin : 임베디드 머신에서도 일반적으로 원격 디버거 스텁 또는 적어도 코어 덤프를 얻을 수있는 방법이 있습니다. 그것은 분야에,하지만 ... 배치있어 어쩌면 번
ephemient

Windows / linux / mac을 선택한 플랫폼에서 gcc-glibc를 사용하여 실행하면 backtrace () 및 backtrace_symbols ()가 세 플랫폼 모두에서 작동합니다. 그 진술을 감안할 때, 나는 "이것을 할 수있는 [휴대용] 방법이 없다"라는 말을 사용합니다.
Trevor Boyd Smith

4

Windows의 경우 CaptureStackBackTrace()도 옵션으로, 사용자 쪽에서 준비 코드가 덜 필요합니다 StackWalk64(). (또한 내가 가진 비슷한 시나리오의 CaptureStackBackTrace()경우 StackWalk64().) 보다 (더 안정적으로) 더 잘 작동 합니다.)


2

Solaris에는 Linux에도 복사 된 pstack 명령이 있습니다.


1
유용하지만 실제로 C는 아닙니다 (외부 유틸리티 임).
ephemient

1
또한 설명에서 (섹션 : 제한) ": pstack은 현재 Linux에서만 작동하며 32 비트 ELF 바이너리를 실행하는 x86 시스템에서만 작동합니다 (64 비트는 지원되지 않음)"
Ciro Costa

0

스택을 뒤로 걸어 가면됩니다. 그러나 실제로는 각 함수의 시작 부분에서 호출 스택에 식별자를 추가하고 끝에서 팝업 한 다음 내용을 인쇄하는 것이 더 쉽습니다. 약간의 PITA이지만 잘 작동하며 결국 시간을 절약 할 수 있습니다.


1
"스택을 뒤로 걷는 것"에 대해 좀 더 자세히 설명해 주시겠습니까?
Spidey

@Spidey 임베디드 시스템에서 때때로 이것이 당신이 가진 전부입니다-플랫폼이 WinXP이기 때문에 이것이 투표되지 않은 것 같습니다. 그러나 스택 워킹을 지원하는 libc가 없으면 기본적으로 스택을 "워킹"해야합니다. 현재 기본 포인터로 시작합니다 (x86에서는 RBP reg의 내용입니다). 그러면 1. 저장된 이전 RBP (스택이 계속 진행되는 방식)와 2. 함수가 무엇인지 알려주는 호출 / 분기 반환 주소 (호출 함수의 저장된 RIP reg)로 스택을 가리 킵니다. 그런 다음 기호 테이블에 대한 액세스 권한이 있으면 함수 주소를 조회 할 수 있습니다.
Ted Middleton

0

지난 몇 년 동안 Ian Lance Taylor의 libbacktrace를 사용해 왔습니다. 모든 심볼을 내 보내야하는 GNU C 라이브러리의 함수보다 훨씬 깨끗합니다. libunwind보다 역 추적 생성에 더 많은 유틸리티를 제공합니다. 마지막으로 .NET Framework와 같은 외부 도구를 필요로하는 접근 방식이 ASLR에 의해 패배하지 않습니다 addr2line.

Libbacktrace는 처음에는 GCC 배포의 일부 였지만 이제 작성자가 BSD 라이선스에 따라 독립 실행 형 라이브러리로 사용할 수 있습니다.

https://github.com/ianlancetaylor/libbacktrace

글을 쓰는 시점에서 libbacktrace에서 지원하지 않는 플랫폼에서 역 추적을 생성해야하는 경우가 아니면 다른 것을 사용하지 않을 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.