如何在C++中修复回溯行号错误

4

我想在捕获异常的程序中跟踪一些信息,但是遇到了问题。

我使用了下面的函数:

extern "C" void log_backtrace()
{   
    // Dump the callstack
    int callstack[128];
    int  frames = backtrace((void**) callstack, 128);
    char** strs = backtrace_symbols((void**) callstack, frames);

    for (int i = 1; i < frames; ++i)
    {
        char functionSymbol[64*1024];
        char moduleName    [64*1024];
        int  offset        = 0;
        sscanf(strs[i], "%*d %s %*s %s %*s %d", &moduleName, &functionSymbol, &offset);
        int addr = callstack[i];
        int   validCppName;
        char* functionName = abi::__cxa_demangle(functionSymbol, NULL, 0,
                                             &validCppName);
        if (validCppName == 0)
            printf(   "\t%8.8x — %s + %d\t\t(%s)\n", addr, functionName, offset, moduleName);
        else
            printf(   "\t%8.8x — %s + %d\t\t(%s)\n", addr, functionSymbol, offset, moduleName);
        if (functionName)
            free(functionName);
    }
    free(strs);
}

输出结果如下:

20:48:44 [ERROR]tcp_client::connect() failed. error:Connection refused
00000001 — latte::Log::out_error(std::string const&) + 151      (valhalla)
001a6637 — latte::tcp_client::connect(boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> const&) + 307       (valhalla)
00000001 — valhalla::hall::start() + 388        (valhalla)
00204803 — main + 388       (valhalla)
00000001 — start + 52       (valhalla)
00143ae4 — 0x0 + 1      (???)

所有信息(命名空间,类名和方法名)都是正确的。但唯一的问题是行号不正确。

如何在回溯中修复行号?


我在Mac OSX 10.7中使用了GCC / LLVM-GCC,谢谢 :) - Jason Cheng
你的 callstack 变量类型错误,因此在 64 位系统上无法工作。请使用 void* 替代 int - SoapBox
我正在使用OSX 10.13.6,使用g++编译。当我使用-g进行编译时,我会得到调试符号,并且在~/Library/Logs/DiagnosticReports/中的核心转储中显示的堆栈跟踪现在显示行号。 - solstice333
3个回答

12

这些不是行号,而是相对于函数开头的偏移量。有一个名为addr2line的工具随 binutils 一起提供,可以在给定调试符号的情况下将地址转换为行号。你可以从程序内部调用它(使用pipe()+fork()+exec()),或查看其所使用的库。

在我的 Linux 系统上,addr2line 内部使用libbfd来执行该操作。但据我所见,它的文档并不是特别详细,但通过 addr2line 源代码的示例还是相对容易理解的。


1
我曾经遇到过同样的问题,找了很久也没找到解决方法。你在帖子中标记了OSX,所以我假设你想在这个平台上运行它?不过,在OSX版本10.8/10.9中并没有addr2line。我使用来自各种来源的代码片段编写了下面的代码,并生成了类似于这样的回溯(其中包括前两个帧,它们是异常处理程序,如果你愿意可以跳过它们):
Caught SIGBUG: Bus error (bad memory access)
 0 MyGame                                     0x342878   ExceptionHandler::PrintStackTrace() (in MyGame) (MacOSXEngine.cpp:89)
 1 MyGame                                     0x342c4e   ExceptionHandler::Handler(int, __siginfo*, void*) (in MyGame) (MacOSXEngine.cpp:232)
 2 libsystem_platform.dylib                 0x92beedeb   0x92beedeb
 3 ???                                      0xffffffff   0 + 4294967295
 4 MyGame                                     0x48ae93   GlfwGraphicsSystem::Initialise(PiEngine::EmulationMode::T, PiGraphics::Orientation::T) (in MyGame) (GlfwGraphicsSystem.cpp:29)
 5 MyGame                                     0x343f1f   MacOSXEngine::Initialise() (in MyGame) (MacOSXEngine.cpp:581)
 6 MyGame                                     0x342f8f   main (in MyGame) (MacOSXEngine.cpp:304)
 7 MyGame                                       0x3445   start (in MyGame) + 53

它仅为执行模块内的帧生成函数+文件+行号,该代码仅适用于OSX,但可以改编为在其他平台上运行。可能会有未覆盖的边角情况,但希望这是一个很好的起点。代码如下:

namespace ExceptionHandler
{
    char m_ExeFilename[ PATH_MAX ];

    // Execute cmd store stdout into buf (up to bufSize).
    int Execute( const char * cmd, char * buf, size_t bufSize )
    {
        char filename[ 512 ];
        sprintf( filename, "%d.tmp", rand( ) );

        if ( FILE * file = fopen( filename, "w" ) )
        {
            if ( FILE * ptr = popen( cmd, "r" ) ) 
            {
                while ( fgets( buf, bufSize, ptr ) != NULL )
                {
                    fprintf( file, "%s", buf );
                }
                pclose( ptr );
            }
            fclose( file );

            unlink( filename );

            return 0;   
        }

        return -1;
    }

    // Resolve symbol name and source location given the path to the executable and an address
    int Addr2Line(char const * const program_name, void const * const addr, char * buff, size_t buffSize )
    {
        char addr2line_cmd[512] = {0};
        sprintf( addr2line_cmd, "atos -d -o %.256s %p", program_name, addr ); 
        return Execute( addr2line_cmd, buff, buffSize );
    }

    // Check if file exists.
    bool FileExists( const char * filename )
    {
        if ( FILE * fh = fopen( filename, "r" ) )
        {
            fclose( fh );
            return true;
        }

        return false;
    }

    // Print stack trace.
    void PrintStackTrace( )
    {
        int trace_size = 0;
        char ** messages = ( char ** )NULL;

        static const size_t kMaxStackFrames = 64;
        static void * stack_traces[ kMaxStackFrames ];
        trace_size = backtrace( stack_traces, kMaxStackFrames );
        messages = backtrace_symbols( stack_traces, trace_size );

        for ( int i = 0; i < trace_size; ++i )
        {
            int stackLevel;
            char filename[ 512 ];
            uintptr_t address;
            char symbol[ 512 ];
            uintptr_t symbolOffset;
            uintptr_t functionOffset;
            bool symbolOffsetValid = false;
            bool somethingValid = true;

            if ( sscanf( messages[ i ], "%d%*[ \t]%s%*[ \t]%" SCNxPTR "%*[ \t]%" SCNxPTR "%*[ \t]+%*[ \t]%" SCNuPTR, &stackLevel, filename, &address, &symbolOffset, &functionOffset ) == 5 )
            {
                symbolOffsetValid = true;
            }
            else if ( sscanf( messages[ i ], "%d%*[ \t]%s%*[ \t]%" SCNxPTR "%*[ \t]%s%*[ \t]+%*[ \t]%" SCNuPTR, &stackLevel, filename, &address, symbol, &functionOffset ) == 5 )
            {
            }
            else
            {
                somethingValid = false;
            }

            const size_t BUFF_SIZE = 4096;
            char buff[ BUFF_SIZE ] = { '\0' };

            if ( somethingValid )
            {
                if ( symbolOffsetValid && symbolOffset == 0 )
                {
                    fprintf( stderr, "%3d %-32s   %#16" PRIxPTR "   %#" PRIxPTR " + %" PRIuPTR "\n", stackLevel, filename, address, symbolOffset, functionOffset );
                }
                else if ( FileExists( m_ExeFilename ) && Addr2Line( m_ExeFilename, stack_traces[ i ], buff, BUFF_SIZE) == 0 )
                {
                    fprintf( stderr, "%3d %-32s   %#16" PRIxPTR "   %s", stackLevel, filename, address, buff );
                }
                else
                {
                    fprintf( stderr, "%3d %-32s   %#16" PRIxPTR "   %#" PRIxPTR " + %" PRIuPTR "\n", stackLevel, filename, address, symbolOffset, functionOffset );
                }
            }
            else
            {
                fprintf( stderr, "%s\n", messages[ i ] );
            }
        }
        if (messages) 
        { 
            free( messages ); 
        } 
    }

    void Handler( int sig, siginfo_t * siginfo, void * context )
    {
        switch(sig)
        {
            case SIGSEGV:
                fputs("Caught SIGSEGV: Segmentation Fault\n", stderr);
                break;

            case SIGBUS:
                fputs("Caught SIGBUG: Bus error (bad memory access)\n", stderr);
                break;

            case SIGINT:
                fputs("Caught SIGINT: Interactive attention signal, (usually ctrl+c)\n", stderr);
                break;

            case SIGFPE:
                switch(siginfo->si_code)
                {
                    case FPE_INTDIV:
                        fputs("Caught SIGFPE: (integer divide by zero)\n", stderr);
                        break;
                    case FPE_INTOVF:
                        fputs("Caught SIGFPE: (integer overflow)\n", stderr);
                        break;
                    case FPE_FLTDIV:
                        fputs("Caught SIGFPE: (floating-point divide by zero)\n", stderr);
                        break;
                    case FPE_FLTOVF:
                        fputs("Caught SIGFPE: (floating-point overflow)\n", stderr);
                        break;
                    case FPE_FLTUND:
                        fputs("Caught SIGFPE: (floating-point underflow)\n", stderr);
                        break;
                    case FPE_FLTRES:
                        fputs("Caught SIGFPE: (floating-point inexact result)\n", stderr);
                        break;
                    case FPE_FLTINV:
                        fputs("Caught SIGFPE: (floating-point invalid operation)\n", stderr);
                        break;
                    case FPE_FLTSUB:
                        fputs("Caught SIGFPE: (subscript out of range)\n", stderr);
                        break;
                    default:
                        fputs("Caught SIGFPE: Arithmetic Exception\n", stderr);
                        break;
                }
                break;

            case SIGILL:
                switch(siginfo->si_code)
                {
                    case ILL_ILLOPC:
                        fputs("Caught SIGILL: (illegal opcode)\n", stderr);
                        break;
                    case ILL_ILLOPN:
                        fputs("Caught SIGILL: (illegal operand)\n", stderr);
                        break;
                    case ILL_ILLADR:
                        fputs("Caught SIGILL: (illegal addressing mode)\n", stderr);
                        break;
                    case ILL_ILLTRP:
                        fputs("Caught SIGILL: (illegal trap)\n", stderr);
                        break;
                    case ILL_PRVOPC:
                        fputs("Caught SIGILL: (privileged opcode)\n", stderr);
                        break;
                    case ILL_PRVREG:
                        fputs("Caught SIGILL: (privileged register)\n", stderr);
                        break;
                    case ILL_COPROC:
                        fputs("Caught SIGILL: (coprocessor error)\n", stderr);
                        break;
                    case ILL_BADSTK:
                        fputs("Caught SIGILL: (internal stack error)\n", stderr);
                        break;
                    default:
                        fputs("Caught SIGILL: Illegal Instruction\n", stderr);
                        break;
                }
                break;

            case SIGTERM:
                fputs("Caught SIGTERM: a termination request was sent to the program\n", stderr);
                break;
            case SIGABRT:
                fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr);
                break;
            default:
                break;
        }
        PrintStackTrace( );
        fflush( stderr );
        fflush( stdout );

        _exit( 1 );
    }

    bool Initialise( const char * argv )
    {
        char path[ PATH_MAX ];
        uint32_t size = sizeof( path );
        if ( _NSGetExecutablePath( path, &size ) == 0 )
        {
            if ( ! realpath( path, m_ExeFilename ) )
            {
                strcpy( m_ExeFilename, path );
            }
        }
        else
        {
            strcpy( m_ExeFilename, argv ? argv : "" );
        }

        struct sigaction sig_action = {};
        sig_action.sa_sigaction = Handler;
        sigemptyset(&sig_action.sa_mask);
        sig_action.sa_flags = SA_SIGINFO;

        int toCatch[ ] = {
            SIGSEGV,
            SIGBUS,
            SIGFPE,
            SIGINT,
            SIGILL,
            SIGTERM,
            SIGABRT
        };

        bool okay = true;
        for ( size_t toCatchIx = 0; toCatchIx < PiArraySize( toCatch ); ++toCatchIx )
        {
            okay &= sigaction( toCatch[ toCatchIx ], &sig_action, NULL ) == 0;
        }

        return okay;
    }
}

int main( int argc, char ** argv )
{
    argc = argc;
    argv = argv;

    ExceptionHandler::Initialise( argc > 0 ? argv[ 0 ] : NULL );

    // Do something

    return 0;
}

0

我意识到原问题已经十年过去了,但我有同样的问题,并找到了答案。我正在使用cmake在Mac上生成AppleClang的makefile。

如果您正在使用cmake构建可执行文件,则需要传递“-g”编译器选项,如下所示:

add_compile_options(-g)

然后,您需要像这样使用lldb运行可执行文件:

lldb

然后一旦进入lldb命令行,您需要使用“run”命令,然后发出回溯命令“bt”。然后您最终将获得崩溃的行号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接