2013-08-07 3 views
1

C++에서 작업중인 일부 코드에이 오류 처리기를 쓰고 있습니다. 내가 명시 적으로 나에게 전달되지 않고 스택에있는 모든 것에 대한 참조를 만들 수 있기를 원합니다. 특히, 호출 스택의 함수 이름을 순서대로 인쇄하려고한다고 가정 해 봅시다. 이것은 JVM과 같은 관리 된 런타임 환경에서는 사소한 것으로, '단순한'컴파일 된 코드로는 그리 간단하지 않을 수 있습니다. 내가 할 수 있을까?C++에서 현재 스레드의 호출 스택을 얻으려면 어떻게해야합니까?

주 :

  • 내가 정보없이 최적화 디버깅을 내 코드를 컴파일 편의상 가정합니다.
  • 플랫폼 독립적 또는 다중 플랫폼 중 하나를 작성하고 싶습니다. 대부분 전자를 선호합니다.
  • 휠을 재발 명하려는 경우 해당 휠의 소스에 연결하면 보입니다.

업데이트 :이 작업을 수행하기 위해 거꾸로 구부릴 필요가 얼마나 많은

나는 날 거의 언급하지 않는다 또 다른 language에 대한 소나무하게 ...을 믿을 수 없다.

+0

어떤 것이 있으면 [''] (http://man7.org/linux/man-pages/man3/backtrace.3.html)을보십시오. –

+0

H2CO3에서 제안한 것과 같은 것을 사용하는 것 외에도 대부분 플랫폼에 따라 다르며 경우에 따라서는 불가능할 수도 있습니다. –

+0

디버깅 정보 및 최적화 정도에 크게 의존합니다. 많은 최적화는 인라인 및 꼬리 호출 최적화와 같이 일부 스택 프레임을 모두 존재하지 않게하는 경향이 있습니다. – SirDarius

답변

2

휴대용이 아니지만 C++에서 백 추적을 얻을 수있는 방법이 있습니다. 나는 윈도우에 대한 이야기,하지만 유닉스 계열 시스템에 주로 다음과 같은 기능으로 구성되어 역 추적의 API가 없습니다

  • int backtrace(void** array, int size);
  • char** backtrace_symbols(void* const* array, int size);
  • void backtrace_symbols_fd(void* const* array, int size, int fd);

당신이 찾을 수 있습니다 GNU 웹 사이트 here에 대한 최신 문서 및 예제. OS X 용 this manual page과 같은 다른 출처가 있습니다.

이 API를 사용하여 백 트레이스를 가져 오는 데는 몇 가지 문제가 있음을 기억하십시오. 첫째, 파일 이름이 없으며 줄 번호도 없습니다. 둘째, 프레임 포인터가 완전히 생략 된 경우 (x86_64 플랫폼 용 최신 GCC 컴파일러의 기본 동작)와 같은 특정 상황에서는 백 트레이스를 얻을 수 없습니다. 또는 바이너리에 디버그 기호가 전혀 없을 수도 있습니다. 일부 시스템에서는 바이너리를 컴파일 할 때 -rdynamic 플래그를 지정해야합니다 (다른 가능한 바람직하지 않은 영향이 있음).

+0

감사합니다. @ H2CO3에서 제안한 내용이기도합니다. 하지만 거기에 내가 필요로하는 모든 것을 가진 훌륭한 링크 된 목록을 얻는 코드를 줄 바꿈하지 않습니까? – einpoklum

+0

"바이너리에는 디버그 심볼이 전혀 없습니다"- 오히려 제거 된 것입니까? 나는 이것을 위해 디버그 심볼이 필요하지 않다고 생각한다. 단순히 오래된 (함수) 심볼로 충분하다. –

+0

@einpoklum No. 그러나이 배열은 동일한 배열을 제공합니다. –

1

표준 C++로는이 작업을 수행 할 수있는 기본 제공 방법이 없습니다. 스택 트레이서 유틸리티를 만드는 데 도움이되는 클래스 시스템을 만들 수 있지만 추적하려는 각 메서드에 특별한 매크로를 넣어야합니다.

나는 그것을 아래에 설명 된 전략을 사용하여 수행 (심지어 그것의 일부를 구현) 본 적이 :

  • 는 스택 프레임에 대한 정보를 저장하는 자신의 클래스를 정의합니다. 최소한 각 노드는 호출되는 함수의 이름을 포함해야하며, 파일 이름/행 번호 정보는 두 번째 가까이에 있어야합니다.
  • 스택 프레임 노드는 링크 된 목록에 저장되며, 존재하는 경우 다시 사용되거나 존재하지 않는 경우 생성됩니다.
  • 특수 개체를 인스턴스화하여 스택 프레임을 만들고 목록에 추가합니다. Object의 생성자는 프레임 노드를 목록에 추가합니다. 객체의 소멸자가 목록에서 노드를 삭제합니다.
  • 동일한 생성자/소멸자 쌍이 스레드 로컬 저장소에서 프레임 목록을 만들고 해당 목록을 삭제하는 역할을 담당합니다.
  • 특수 개체의 생성은 매크로에 의해 처리됩니다. 매크로는 특별한 전처리 기 토큰을 사용하여 함수 식별 및 위치 정보를 프레임 작성자 객체로 전달합니다.

    #include <iostream> 
    #include <list> 
    
    using namespace std; 
    
    struct stack_frame { 
        const char *funName; 
        const char *fileName; 
        int line; 
        stack_frame(const char* func, const char* file, int ln) 
        : funName(func), fileName(file), line(ln) {} 
    }; 
    
    thread_local list<stack_frame> *frames = 0; 
    
    struct entry_exit { 
        bool delFrames; 
        entry_exit(const char* func, const char* file, int ln) { 
         if (!frames) { 
          frames = new list<stack_frame>(); 
          delFrames = true; 
         } else { 
          delFrames = false; 
         } 
         frames->push_back(stack_frame(func, file, ln)); 
        } 
        ~entry_exit() { 
         frames ->pop_back(); 
         if (delFrames) { 
          delete frames; 
          frames = 0; 
         } 
        } 
    }; 
    
    void show_stack() { 
        for (list<stack_frame>::const_iterator i = frames->begin() ; i != frames->end() ; ++i) { 
         cerr << i->funName << " - " << i->fileName << " (" << i->line << ")" << endl; 
        } 
    } 
    
    #define FUNCTION_ENTRY entry_exit _entry_exit_(__func__, __FILE__, __LINE__); 
    
    void foo() { 
        FUNCTION_ENTRY; 
        show_stack(); 
    } 
    void bar() { 
        FUNCTION_ENTRY; 
        foo(); 
    } 
    void baz() { 
        FUNCTION_ENTRY; 
        bar(); 
    } 
    
    int main() { 
         baz(); 
         return 0; 
    } 
    

    위의 코드는 C++ (11)와 인쇄이 함께 컴파일 : 여기

이 방법의 다소 골격 개념 증명 구현

baz - prog.cpp (52) 
bar - prog.cpp (48) 
foo - prog.cpp (44) 

기능이 그 매크로는 스택에서 보이지 않을 것입니다. 성능이 중요한 기능에는 이러한 매크로가 없어야합니다.

여기는 demo on ideone입니다.

0

쉽지 않습니다. 정확한 해결책은 OS와 실행 환경에 따라 크게 달라집니다.

스택을 인쇄하는 것은 일반적으로 어렵지는 않지만 디버그 기호를 읽는 것을 의미하기 때문에 기호를 찾는 일은 매우 까다 롭습니다.

#ifdef DEBUG 
struct StackEntry 
{ 
    const char *file; 
    const char *func; 
    int   line; 
    StackEntry(const char *f, const char *fn, int ln) : file(f), func(fn), line(ln) {} 
}; 

std::stack<StackEntry> call_stack; 

class FuncEntry 
{ 
    public: 
    FuncEntry(const char *file, const char *func, int line) 
    { 
     StackEntry se(file, func, line); 
     call_stack.push_back(se); 
    } 
    ~FuncEntry() 
    { 
     call_stack.pop_back(); 
    } 

    void DumpStack() 
    { 
     for(sp : call_stack) 
     { 
      cout << sp->file << ":" << sp->line << ": " << sp->func << "\n"; 
     } 
    } 
}; 


#define FUNC() FuncEntry(__FILE__, __func__, __LINE__); 
#else 
#define FUNC() 
#endif 


void somefunction() 
{ 
    FUNC(); 
    ... more code here. 
} 

내가 과거에이 기술을 사용하고 있습니다 :

대안은 일부는 "여기서 나는"(아마도 "디버그 빌드 만"에 대한) 각 기능에 대한 형 코드가 침입 방법을 사용하고 추가하는 것입니다 ,하지만이 코드를 입력했는데 컴파일되지 않을 수도 있지만 충분히 명확하다고 생각합니다. 중요한 이점 중 하나는 "중요한 것"과 같은 모든 기능에이 기능을 넣지 않아도된다는 것입니다. [다른 디버깅 수준에 따라 서로 다른 유형의 FUNC 매크로를 활성화하거나 비활성화 할 수 있습니다.]

관련 문제