참고: https://theolizer.com/cpp-school2/cpp-school2-28/  https://theolizer.com/cpp-school2/cpp-school2-29/  https://theolizer.com/cpp-school2/cpp-school2-31/

#include <iostream>
#include <boost/stacktrace.hpp>
void bar()
{
   
std::cout << "bar()\n";
   
std::cout << boost::stacktrace::stacktrace();
}

void foo()
{
   bar();
}

int main()
{
   foo();
}

위 코드를 실행하면 boost::stacktrace::stacktrace() 에 의해 아래의 정보들을 얻을 수 있다.

어드레스

의미

0x00007FF6CC2710F8

bar() 함수에서 호출하고 있는 boost::stacktrace::stacktrace() 에서의 반환 처 어드레스

0x00007FF6CC271139

foo() 함수에서 호출하고 있는 bar()에서의 반환 처 어드레스

0x00007FF6CC271159

main() 함수에서 호출하고 있는 foo()에서의 반환 처 어드레스

스택 트레이스가 도움이 될 때

뭔가 문제가 발생했을 때 문제의 원인이 되는 부근의 스택 트레이스가 있으면 디버깅이 굉장히 쉬워진다.

결함 발생 부분에 이르는 호출 스택과 적절한 로그를 함께 읽으면 문제가 발생한 상황을 상당한 많이 추측 할 수 있다. 바로 버그의 원인도 찾는 경우도 의외로 많다. 또한 문제를 재현 할 수 없는 경우 또는 로그를 만들어서 기다릴 수 밖에 없지만, 이 때 더욱 정밀하게 로그를 남길 수 있는 디버깅 장치이다.

그런데 현대 OS에서 비정상적으로 종료되면 스택 추적을 취할 수 있는 옵션이 있으므로 이것에 의존 할 수 있다. 그런데 이 방법은 준비할게 많아서 좀 불편했다.

그래서 이상 발생시 프로그램 측에서 자동으로 스택 추적을 로그로 남겨두면 사후에 이 로그를 보내거나 자동으로 업로드 하는 기능을 넣으면 결함 분석을 매우 순조롭게 진행할 수 있다.

C++의 경우, 처리 계 마다 스택 트레이스를 하는 방법은 달라서 까다롭다. 또 필요한 프로그램의 코드 양도 적지 않기 때문에 실제 스택 트레이스를 출력하는 프로그램을 만들려면 시간이 걸린다.  C#처럼 아주 간단한 조사와 아주 간단한 코드로 스택 트레이스를 받을 수 있다면 좋겠지만, C++에서는 없다.

그러나 boost 라이브러리의 StackTrace를 사용하면 위의 문제를 해결할 수 있다.

사용법

header-only로도 사용할 수 있다. 단 라이브러리 링크로 사용하면 프로그램 빌드 시간을 줄일 수 있다. 링크해서 사용할 때는 아래의 #define을 참고해서 사용해야 한다.

https://www.boost.org/doc/libs/1_67_0/doc/html/stacktrace/configuration_and_build.html

아래 파일을 include 한다

#include <boost/stacktrace.hpp>

아래 함수를 호출하면 이것으로 끝이다.

boost::stacktrace::stacktrace();

릴리즈 모드에서도 디버깅 심볼을 출력하도록 설정

스택 트레이스는 현재까지 호출된 함수의 반환 연락처 주소의 목록이다. 단순한 주소이기 때문에 이것만으로는 의미를 알 수 없다.

그런데 코드의 주소를 소스 파일 이름과 줄 번호, 함수 이름에 전개하는 구조가 디버그 모드에 있다. 빌드 시 디버깅 심볼을 출력 해두고, 디버깅 할 때 심볼 테이블을 사용하여 소스 파일 이름 등으로 자동 변환하여 쉽게 디버깅 한다. 이 방법을 사용하면 반환 주소를 소스 상의 위치에 매핑 할 수 있다.

그러나 이 디버깅 심볼은 기본적으로 릴리스 빌드 할 때 출력 되지 않는다.

스택 트레이스는 출시한 프로그램이 개발자가 아닌 사람에 의해서 동작 할 때 발생하는 문제를 디버깅하는 데 유효한 것이므로, 릴리즈 모드에서 빌드한 때에도 사용하고 싶다.

그래서 릴리즈 모드로 빌드했을 때에도 디버깅 심볼을 출력하도록 지정한다.

옵션 자체는 구현에 따라 달라진다. Visual C++와 gcc에 대해 설명한다. (clang과 MinGW는 gcc와 동일하다.)

if(MSVC)

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc")

    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")

    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")

else()

    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11")

    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g")

endif()

디버그 모드에서 출력했을 때

릴리즈 모드에서 출력했을 때

덤프 파일 만들기

아래 함수를 실행하면 덤프 파일이 만들어진다

boost::stacktrace::safe_dump_to("./backtrace.dump");

backtrace.dump 파일이 생성된다.

Boost 라이브러리 1.67 버전에서는 Windows에서는 덤프 파일이 발생하지 않는다. 이 때 당시 Windows에서는 이 기능이 제대로 동작하지 않아서 막아 놓았다고 한다.

최신 버전에서는 Windows에서 동작할 수도 있으므로 확인이 필요하다.

덤프 파일 읽어서 출력하기

int main()
{
   
std::ifstream ifs("./backtrace.dump", std::ios_base::binary);
   
if (ifs)
   {
       boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(ifs);
       
std::cout << "Previous run crashed:\n" << st << std::endl;

       
// cleaning up
       ifs.close();
       remove(
"./backtrace.dump");
   }

   ::signal(SIGSEGV, &my_signal_handler);
   ::signal(SIGABRT, &my_signal_handler);

   sub(5);
}