backtrace () / backtrace_symbols ()에서 함수 이름을 인쇄하는 방법은 무엇입니까?


90

Linux 전용 backtrace()이며 backtrace_symbols()프로그램의 호출 추적을 생성 할 수 있습니다. 그러나 내 프로그램의 이름이 아닌 함수 주소 만 인쇄합니다. 함수 이름도 인쇄하도록하려면 어떻게해야합니까? 나는 -g뿐만 아니라 -ggdb. 아래 테스트 사례는 다음과 같이 인쇄합니다.

    백 트레이스 ------------
    ./a.out () [0x8048616]
    ./a.out () [0x8048623]
    /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
    ./a.out () [0x8048421]
    ----------------------
    

또한 함수 이름을 표시하는 최초의 2 개 항목을 원하는 것 foomain

암호:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                size_t len = strlen(bt_syms[i]);
                full_write(STDERR_FILENO, bt_syms[i], len);
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}
void foo()
{
    print_backtrace();
}

int main()
{
    foo();
    return 0;
}


btw, function은와 backtrace_symbols_fd동일한 작업을 수행 backtrace_symbols()하지만 결과 문자열은 즉시 파일 설명자에 기록됩니다 fd.
nhnghia

답변:


63

기호는 동적 기호 테이블에서 가져옵니다. 에 대한 -rdynamic옵션 이 필요합니다. gcc이렇게하면 모든 기호가 테이블에 배치 되도록하는 플래그를 링커에 전달 합니다.

( GCC 매뉴얼Link Options 페이지 및 / 또는 glibc 매뉴얼Backtraces 페이지를 참조하십시오 .)


10
그러나 이것은 정적 기호에 대해서는 작동하지 않습니다. libunwind@Nemo가 언급 한 것은 정적 함수에서 작동합니다.
Nathan Kidd 2014 년

CMake 프로젝트에서 이것은 실수로 ADD_COMPILE_OPTIONS(-rdynamic). 대신, ADD_LINK_OPTIONS(-rdynamic)원하는 동작을 갖기 위해서는 동일하거나 동일해야합니다.
Stéphane

30

addr2line 명령 을 사용하여 실행 가능한 주소를 소스 코드 파일 이름 + 줄 번호에 매핑합니다. -f함수 이름도 가져 오는 옵션을 제공하십시오 .

또는 libunwind를 시도하십시오 .


3
addr2 행은 출력에 파일 이름과 행 번호를 포함하기 때문에 좋지만 로더가 재배치를 수행하자마자 실패합니다.
Erwan Legrand

... 그리고 ASLR로 재배치는 그 어느 때보 다 흔합니다.
Erwan Legrand

@ErwanLegrand : ASLR 이전에는 재배치가 예측 가능하고 addr2line공유 객체의 주소에 대해서도 안정적으로 작동 한다고 생각 했습니다 (?).하지만 현대 플랫폼에서는 원칙적으로이 작업을 수행하기 위해 재배치 가능한 객체의 실제로드 주소를 알아야합니다. .
Nemo

ASLR 이전에 얼마나 잘 작동했는지는 알 수 없습니다. 요즘에는 "일반"실행 파일 내부의 코드에만 적용되고 DSO 및 PIE (위치 독립적 실행 파일) 내부의 코드에 대해서는 실패합니다. 다행히 libunwind는이 모든 작업에 대해 작동하는 것으로 보입니다.
Erwan Legrand

나는이 토론에 대해 잊었다. 당시에 내가 몰랐던 Libbacktrace가 더 나은 옵션입니다. (내 새로운 대답을 참조하십시오.)
유안 르 그랑에게

11

Ian Lance Taylor의 뛰어난 Libbacktrace가이 문제를 해결합니다. 스택 해제를 처리하고 일반 ELF 기호와 DWARF 디버깅 기호를 모두 지원합니다.

Libbacktrace는 추악한 모든 기호를 내보낼 필요가 없으며 ASLR은이를 깨뜨리지 않습니다.

Libbacktrace는 원래 GCC 배포의 일부였습니다. 이제 독립 실행 형 버전은 Github에서 찾을 수 있습니다.

https://github.com/ianlancetaylor/libbacktrace


2

ret == -1이고 errno가 EINTER이면 상단의 답변에 버그가 있습니다. 다시 시도해야하지만 ret을 복사 된 것으로 계산하지 않습니다 (강하게 마음에 들지 않으면 계정을 만들지 않음).

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) {
                        if (errno != EINTR))
                                break;
                        //else
                        continue;
                }
                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

0

역 추적 향상

다음 두 가지를 모두 인쇄하므로 매우 편리합니다.

  • 얽 히지 않은 C ++ 함수 이름
  • 줄 번호

당신을 위해 자동으로.

사용 요약 :

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

std::cout << boost::stacktrace::stacktrace() << std::endl;

나는 그것에 대한 최소한의 실행 가능한 예제와 다른 많은 방법을 제공했습니다 : C 또는 C ++의 print call stack


질문에는 [c ++]가 아니라 [c] 태그가 지정되어 있습니다.
Yakov Galka
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.