พิมพ์ call stack ใน C หรือ C ++


120

มีวิธีใดในการถ่ายโอนข้อมูลการโทรในกระบวนการทำงานใน C หรือ C ++ ทุกครั้งที่เรียกใช้ฟังก์ชันบางอย่าง สิ่งที่ฉันคิดไว้มีดังนี้:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

สถานที่print_stack_traceทำงานคล้ายกับcallerใน Perl

หรือสิ่งนี้:

int main (void)
{
    // will print out debug info every time foo() is called
    register_stack_trace_function(foo); 

    // etc...
}

ที่register_stack_trace_functionวางเบรกพอยต์ภายในบางประเภทที่จะทำให้การติดตามสแต็กถูกพิมพ์เมื่อใดก็ตามที่fooถูกเรียก

สิ่งนี้มีอยู่ในไลบรารี C มาตรฐานหรือไม่?

ฉันทำงานบน Linux โดยใช้ GCC


พื้นหลัง

ฉันมีการทดสอบที่ทำงานแตกต่างกันไปตามสวิตช์บรรทัดคำสั่งบางอย่างที่ไม่ควรส่งผลต่อพฤติกรรมนี้ รหัสของฉันมีตัวสร้างตัวเลขสุ่มหลอกที่ฉันคิดว่าถูกเรียกแตกต่างกันไปตามสวิตช์เหล่านี้ ฉันต้องการเรียกใช้การทดสอบกับสวิตช์แต่ละชุดและดูว่าตัวสร้างตัวเลขสุ่มถูกเรียกแตกต่างกันหรือไม่สำหรับแต่ละอัน


1
@ อาร์เมนคุณคุ้นเคยกับสิ่งเหล่านี้หรือไม่?
Nathan Fellman

1
@ นาธาน: หากดีบักเกอร์ของคุณคือ gdb มันสามารถจัดการกับกรณีนั้นได้ ฉันไม่สามารถบอกคุณเกี่ยวกับคนอื่น ๆ ได้ แต่ฉันถือว่า gdb ไม่ได้มีฟังก์ชันนี้เพียงอย่างเดียว นอกเหนือ:ฉันเพิ่งดูความคิดเห็นก่อนหน้านี้ :: gag :: s/easier/either/มันเกิดขึ้นได้อย่างไร?
dmckee --- อดีตผู้ดูแลลูกแมว

2
@dmckee: อันที่จริงมันควรจะเป็นs/either/easierนะ สิ่งที่ฉันต้องทำกับ gdb คือเขียนสคริปต์ที่แบ่งฟังก์ชันนั้นและพิมพ์การติดตามสแต็กออกมา ตอนนี้ฉันคิดเกี่ยวกับเรื่องนี้อาจถึงเวลาที่ฉันต้องเรียนรู้เกี่ยวกับสคริปต์ gdb
Nathan Fellman

1
Gah! ไปนอนบ้าง. เร็ว ๆ นี้ ...
dmckee --- อดีตผู้ดูแลลูกแมว

คำตอบ:


79

สำหรับโซลูชันลินุกซ์เท่านั้นคุณสามารถใช้backtrace (3)ที่เพียงส่งคืนอาร์เรย์ของvoid *(อันที่จริงแล้วแต่ละจุดเหล่านี้ไปยังที่อยู่ที่ส่งคืนจากสแต็กเฟรมที่เกี่ยวข้อง) การแปลเหล่านี้เพื่อบางสิ่งบางอย่างในการใช้มีbacktrace_symbols (3)

ให้ความสนใจกับส่วนบันทึกย่อใน backtrace (3) :

ชื่อสัญลักษณ์อาจไม่พร้อมใช้งานหากไม่มีการใช้ตัวเลือกตัวเชื่อมพิเศษ สำหรับระบบที่ใช้ GNU linker จำเป็นต้องใช้อ็อพชัน -rdynamic linker โปรดทราบว่าชื่อของฟังก์ชัน "คงที่" จะไม่ถูกเปิดเผยและจะไม่สามารถใช้ได้ใน backtrace


10
FWIW ฟังก์ชันนี้ยังมีอยู่ใน Mac OS X: developer.apple.com/library/mac/#documentation/Darwin/Reference/…
EmeryBerger

9
Windows มีCaptureStackBackTrace
bobobobo


บน Linux ด้วย glibcซึ่งน่าเสียดายที่backtrace_symbolsฟังก์ชันไม่ได้ระบุชื่อฟังก์ชันชื่อไฟล์ต้นฉบับและหมายเลขบรรทัด
Maxim Egorushkin

นอกจากการใช้ -rdynamicแล้วตรวจสอบด้วยว่าระบบบิลด์ของคุณไม่ได้เพิ่ม-fvisibility=hiddenตัวเลือก! (เพราะมันจะทิ้งผลของมันอย่างสมบูรณ์-rdynamic)
Dima Litvinov

38

เพิ่ม stacktrace

เอกสารที่: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

นี่เป็นตัวเลือกที่สะดวกที่สุดที่ฉันเคยเห็นมาเพราะมัน:

  • สามารถพิมพ์หมายเลขบรรทัดได้จริง

    มันก็ทำให้การโทรไปaddr2lineแต่ที่น่าเกลียดและอาจจะช้าถ้าคุณมีการร่องรอยมากเกินไป

  • demangles โดยค่าเริ่มต้น

  • Boost เป็นส่วนหัวเท่านั้นดังนั้นจึงไม่จำเป็นต้องแก้ไขระบบบิลด์ของคุณ

boost_stacktrace.cpp

#include <iostream>

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

void my_func_2(void) {
    std::cout << boost::stacktrace::stacktrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 28
        my_func_1(2.0); // line 29
    }
}

น่าเสียดายที่ดูเหมือนว่าจะเป็นส่วนเพิ่มเติมที่ใหม่กว่าและไม่มีแพ็คเกจlibboost-stacktrace-devใน Ubuntu 16.04 เพียง 18.04:

sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
  -Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out

เราต้องเพิ่ม-ldlในตอนท้ายไม่เช่นนั้นการคอมไพล์จะล้มเหลว

เอาท์พุท:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
 2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
 2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

ผลลัพธ์และมีคำอธิบายเพิ่มเติมในส่วน "glibc backtrace" ด้านล่างซึ่งคล้ายคลึงกัน

สังเกตวิธีการmy_func_1(int)และmy_func_1(float) , ซึ่งมี mangled เนื่องจากเกินพิกัดฟังก์ชั่นถูก demangled อย่างสำหรับเรา

สังเกตว่าประการแรก intโทรถูกปิดทีละบรรทัด (28 แทนที่จะเป็น 27 และการโทรครั้งที่สองถูกปิดโดยสองบรรทัด (27 แทนที่จะเป็น 29) แนะนำในความคิดเห็นว่านี่เป็นเพราะที่อยู่คำสั่งต่อไปนี้กำลังพิจารณาอยู่ ทำให้ 27 กลายเป็น 28 และ 29 กระโดดออกจากวงและกลายเป็น 27

จากนั้นเราจะสังเกตว่าด้วย -O3ผลลัพธ์จะถูกตัดขาดอย่างสมบูรณ์:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# main at /home/ciro/test/boost_stacktrace.cpp:31
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

โดยทั่วไปแล้วการย้อนกลับจะถูกตัดทอนอย่างไม่สามารถแก้ไขได้โดยการเพิ่มประสิทธิภาพ การเพิ่มประสิทธิภาพการโทรหางเป็นตัวอย่างที่น่าสังเกตว่า:การเพิ่มประสิทธิภาพการโทรหางคืออะไร?

Benchmark ทำงานบน -O3 :

time  ./boost_stacktrace.out 1000 >/dev/null

เอาท์พุท:

real    0m43.573s
user    0m30.799s
sys     0m13.665s

ดังที่คาดไว้เราจะเห็นว่าวิธีนี้มีแนวโน้มที่จะโทรไปภายนอกได้ช้ามาก addr2lineและจะทำได้ก็ต่อเมื่อมีการโทรออกในจำนวน จำกัด

การพิมพ์ backtrace แต่ละครั้งดูเหมือนจะใช้เวลาหลายร้อยมิลลิวินาทีดังนั้นโปรดเตือนว่าหากเกิดการย้อนกลับบ่อยครั้งประสิทธิภาพของโปรแกรมจะได้รับผลกระทบอย่างมาก

ทดสอบบน Ubuntu 19.10, GCC 9.2.1 เพิ่ม 1.67.0

glibc backtrace

เอกสารที่: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

main.c

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    puts("");
    free(strings);
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 33 */
    my_func_2(); /* line 34 */
    return 0;
}

รวบรวม:

gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
  -Wall -Wextra -pedantic-errors main.c

-rdynamic เป็นตัวเลือกที่สำคัญที่จำเป็น

วิ่ง:

./main.out

ขาออก:

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

ดังนั้นเราจึงเห็นได้ทันทีว่าการเพิ่มประสิทธิภาพแบบอินไลน์เกิดขึ้นและฟังก์ชันบางอย่างสูญหายไปจากการติดตาม

หากเราพยายามหาที่อยู่:

addr2line -e main.out 0x4008f9 0x4008fe

เราได้รับ:

/home/ciro/main.c:21
/home/ciro/main.c:36

ซึ่งปิดอย่างสมบูรณ์

หากเราทำเช่นเดียวกัน-O0แทน./main.outให้ระบุการติดตามแบบเต็มที่ถูกต้อง:

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

แล้ว:

addr2line -e main.out 0x400a74 0x400a79

ให้:

/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35

ดังนั้นเส้นจึงหลุดออกไปเพียงเส้นเดียว TODO ทำไม? แต่อาจยังใช้งานได้

สรุป: การย้อนกลับสามารถแสดงได้อย่างสมบูรณ์แบบด้วย-O0. ด้วยการปรับให้เหมาะสม backtrace ดั้งเดิมจะได้รับการแก้ไขโดยพื้นฐานในโค้ดที่คอมไพล์

ฉันไม่พบวิธีง่ายๆในการแยกสัญลักษณ์ C ++ โดยอัตโนมัติด้วยวิธีนี้นี่คือแฮ็กบางส่วน:

ทดสอบบน Ubuntu 16.04, GCC 6.4.0, libc 2.23

glibc backtrace_symbols_fd

ตัวช่วยนี้สะดวกกว่าเล็กน้อยbacktrace_symbolsและให้ผลลัพธ์ที่เหมือนกันโดยทั่วไป:

/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
    puts("");
}

ทดสอบบน Ubuntu 16.04, GCC 6.4.0, libc 2.23

glibc backtraceกับ C ++ demangling hack 1: -export-dynamic+dladdr

ดัดแปลงมาจาก: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

นี่คือ "แฮ็ก" เนื่องจากต้องเปลี่ยน ELF ด้วย-export-dynamic.

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

รวบรวมและเรียกใช้:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

เอาท์พุท:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

ทดสอบบน Ubuntu 18.04

glibc backtraceด้วย C ++ demangling hack 2: แยกวิเคราะห์เอาต์พุต backtrace

แสดงที่: https://panthema.net/2008/0901-stacktrace-demangled/

นี่เป็นการแฮ็กเนื่องจากต้องมีการแยกวิเคราะห์

สิ่งที่ต้องทำนำไปรวบรวมและแสดงที่นี่

libunwind

TODO สิ่งนี้มีข้อได้เปรียบเหนือ glibc backtrace หรือไม่? ผลลัพธ์ที่คล้ายกันมากยังต้องการการแก้ไขคำสั่ง build แต่ไม่ใช่ส่วนหนึ่งของ glibc ดังนั้นจึงต้องมีการติดตั้งแพ็คเกจเพิ่มเติม

โค้ดดัดแปลงมาจาก: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* This must be on top. */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

รวบรวมและเรียกใช้:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

ทั้งสอง#define _XOPEN_SOURCE 700จะต้องอยู่ด้านบนหรือเราจะต้องใช้-std=gnu99:

วิ่ง:

./main.out

เอาท์พุท:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

และ:

addr2line -e main.out 0x4007db 0x4007e2

ให้:

/home/ciro/main.c:34
/home/ciro/main.c:49

ด้วย-O0:

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

และ:

addr2line -e main.out 0x4009f3 0x4009f8

ให้:

/home/ciro/main.c:47
/home/ciro/main.c:48

ทดสอบบน Ubuntu 16.04, GCC 6.4.0, libunwind 1.1

libunwind ด้วยชื่อ C ++ demangling

โค้ดดัดแปลงมาจาก: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

รวบรวมและเรียกใช้:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

เอาท์พุท:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

จากนั้นเราจะพบบรรทัดmy_func_2และmy_func_1(int)ด้วย:

addr2line -e unwind.out 0x400c80 0x400cb7

ซึ่งจะช่วยให้:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

สิ่งที่ต้องทำ: ทำไมเส้นออกทีละเส้น?

ทดสอบบน Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1

ระบบอัตโนมัติ GDB

นอกจากนี้เรายังสามารถทำได้ด้วย GDB โดยไม่ต้องคอมไพล์ใหม่โดยใช้: จะดำเนินการเฉพาะอย่างไรเมื่อมีการกดเบรกพอยต์ใน GDB

แม้ว่าคุณจะพิมพ์ backtrace เป็นจำนวนมาก แต่ก็น่าจะเร็วน้อยกว่าตัวเลือกอื่น ๆ แต่บางทีเราสามารถเข้าถึงความเร็วดั้งเดิมcompile codeได้ แต่ฉันขี้เกียจที่จะทดสอบตอนนี้: จะเรียกแอสเซมบลีใน gdb ได้อย่างไร?

main.cpp

void my_func_2(void) {}

void my_func_1(double f) {
    my_func_2();
}

void my_func_1(int i) {
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

main.gdb

start
break my_func_2
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

รวบรวมและเรียกใช้:

g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out

เอาท์พุท:

Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.

Temporary breakpoint 1, main () at main.cpp:12
12          my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0  my_func_2 () at main.cpp:1
#1  0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2  0x0000555555555162 in main () at main.cpp:12

#0  my_func_2 () at main.cpp:1
#1  0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2  0x000055555555516f in main () at main.cpp:13

[Inferior 1 (process 14193) exited normally]

สิ่งที่ต้องทำฉันต้องการทำสิ่งนี้โดยใช้เพียงแค่-exจากบรรทัดคำสั่งเพื่อไม่ต้องสร้างmain.gdbแต่ฉันไม่commandsสามารถทำงานที่นั่นได้

ทดสอบใน Ubuntu 19.04, GDB 8.2

เคอร์เนลลินุกซ์

จะพิมพ์เธรดสแต็กติดตามปัจจุบันภายในเคอร์เนล Linux ได้อย่างไร

libdwfl

แต่เดิมมีการกล่าวถึงที่: https://stackoverflow.com/a/60713161/895245และอาจเป็นวิธีที่ดีที่สุด แต่ฉันต้องเปรียบเทียบอีกเล็กน้อย แต่โปรดเพิ่มคะแนนคำตอบนั้น

สิ่งที่ต้องทำ: ฉันพยายามย่อโค้ดในคำตอบนั้นซึ่งใช้งานได้เป็นฟังก์ชันเดียว แต่มันผิดพลาดโปรดแจ้งให้เราทราบหากมีใครสามารถหาสาเหตุได้

dwfl.cpp

#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>

#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid

// /programming/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
    int status = -4;
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

std::string debug_info(Dwfl* dwfl, void* ip) {
    std::string function;
    int line = -1;
    char const* file;
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    function = name ? demangle(name) : "<unknown>";
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << ip << ' ' << function;
    if (file)
        ss << " at " << file << ':' << line;
    ss << std::endl;
    return ss.str();
}

std::string stacktrace() {
    // Initialize Dwfl.
    Dwfl* dwfl = nullptr;
    {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);
        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    // Loop over stack frames.
    std::stringstream ss;
    {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
        for (int i = 0; i < stack_size; ++i) {
            ss << i << ": ";

            // Works.
            ss << debug_info(dwfl, stack[i]);

#if 0
            // TODO intended to do the same as above, but segfaults,
            // so possibly UB In above function that does not blow up by chance?
            void *ip = stack[i];
            std::string function;
            int line = -1;
            char const* file;
            uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
            Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
            char const* name = dwfl_module_addrname(module, ip2);
            function = name ? demangle(name) : "<unknown>";
            // TODO if I comment out this line it does not blow up anymore.
            if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
              Dwarf_Addr addr;
              file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
            }
            ss << ip << ' ' << function;
            if (file)
                ss << " at " << file << ':' << line;
            ss << std::endl;
#endif
        }
    }
    dwfl_end(dwfl);
    return ss.str();
}

void my_func_2() {
    std::cout << stacktrace() << std::endl;
    std::cout.flush();
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);
        my_func_1(2.0);
    }
}

รวบรวมและเรียกใช้:

sudo apt install libdw-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
./dwfl.out

เอาท์พุท:

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d7d my_func_1(int) at /home/ciro/test/dwfl.cpp:112
3: 0x402de0 main at /home/ciro/test/dwfl.cpp:123
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d66 my_func_1(double) at /home/ciro/test/dwfl.cpp:107
3: 0x402df1 main at /home/ciro/test/dwfl.cpp:121
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

การทดสอบเกณฑ์มาตรฐาน:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

เอาท์พุท:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

ดังนั้นเราจึงเห็นว่าวิธีนี้เร็วกว่า stacktrace ของ Boost 10 เท่าและอาจใช้ได้กับกรณีการใช้งานที่มากขึ้น

ทดสอบใน Ubuntu 19.10 amd64, libdw-dev 0.176-1.1

ดูสิ่งนี้ด้วย


1
"TODO: lines off by one" ทั้งหมดเป็นเพราะหมายเลขบรรทัดถูกนำมาจากจุดเริ่มต้นของนิพจน์ถัดไป
SS Anne


6

มีวิธีใดในการถ่ายโอนข้อมูลการโทรในกระบวนการทำงานใน C หรือ C ++ ทุกครั้งที่เรียกใช้ฟังก์ชันบางอย่าง

คุณสามารถใช้ฟังก์ชันมาโครแทนคำสั่ง return ในฟังก์ชันเฉพาะได้

ตัวอย่างเช่นแทนที่จะใช้ return

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

คุณสามารถใช้ฟังก์ชันมาโคร

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

เมื่อใดก็ตามที่เกิดข้อผิดพลาดในฟังก์ชันคุณจะเห็น call stack แบบ Java ดังที่แสดงด้านล่าง

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

ซอร์สโค้ดแบบเต็มมีอยู่ที่นี่

c-callstack ที่ https://github.com/Nanolat


6

อีกคำตอบสำหรับกระทู้เก่า

เมื่อฉันต้องการทำสิ่งนี้ฉันมักจะใช้system()และpstack

ดังนั้นสิ่งนี้:

#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>

void f()
{
    pid_t myPid = getpid();
    std::string pstackCommand = "pstack ";
    std::stringstream ss;
    ss << myPid;
    pstackCommand += ss.str();
    system(pstackCommand.c_str());
}

void g()
{
   f();
}


void h()
{
   g();
}

int main()
{
   h();
}

เอาต์พุตนี้

#0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2  0x0000000000400c3c in f() ()
#3  0x0000000000400cc5 in g() ()
#4  0x0000000000400cd1 in h() ()
#5  0x0000000000400cdd in main ()

สิ่งนี้ควรใช้กับ Linux, FreeBSD และ Solaris ผมไม่คิดว่ามี MacOS pstack หรือเทียบเท่าที่เรียบง่าย แต่นี้ด้ายดูเหมือนว่าจะมีทางเลือก

หากคุณกำลังใช้Cงานคุณจะต้องใช้Cฟังก์ชันสตริง

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void f()
{
    pid_t myPid = getpid();
    /*
      length of command 7 for 'pstack ', 7 for the PID, 1 for nul
    */
    char pstackCommand[7+7+1];
    sprintf(pstackCommand, "pstack %d", (int)myPid);
    system(pstackCommand);
}

ฉันใช้ 7 สำหรับจำนวนหลักสูงสุดใน PID ตามโพสต์นี้


จุดดีเนื่องจากหัวเรื่องขอ C ไม่จำเป็นต้องปรับตัวเนื่องจาก std :: string เป็น C ++ เท่านั้น ฉันจะอัปเดตคำตอบของฉันด้วยเวอร์ชัน C
Paul Floyd

6

เฉพาะ Linux TLDR:

  1. backtraceในglibcสร้าง stacktraces ที่ถูกต้องก็ต่อเมื่อ-lunwindมีการเชื่อมโยง (คุณลักษณะเฉพาะแพลตฟอร์มที่ไม่มีเอกสาร)
  2. การส่งออกชื่อฟังก์ชั่น , แฟ้มแหล่งที่มาและหมายเลขบรรทัดใช้#include <elfutils/libdwfl.h>(ห้องสมุดนี้เป็นเอกสารเฉพาะในไฟล์ส่วนหัวของมัน) backtrace_symbolsและให้backtrace_symbolsd_fdข้อมูลน้อยที่สุด

เมื่อวันที่ทันสมัย Linux ของคุณจะได้รับที่อยู่ stacktrace backtraceโดยใช้ฟังก์ชั่น วิธีที่ไม่มีเอกสารในการbacktraceสร้างที่อยู่ที่แม่นยำยิ่งขึ้นบนแพลตฟอร์มยอดนิยมคือการเชื่อมโยงกับ-lunwind( libunwind-devบน Ubuntu 18.04) (ดูตัวอย่างผลลัพธ์ด้านล่าง) backtraceใช้ฟังก์ชัน_Unwind_Backtraceและโดยค่าเริ่มต้นมาจากlibgcc_s.so.1และการใช้งานนั้นพกพาได้มากที่สุด เมื่อ-lunwindมีการเชื่อมโยงจะให้เวอร์ชันที่แม่นยำกว่า_Unwind_Backtraceแต่ไลบรารีนี้พกพาได้น้อยกว่า (ดูสถาปัตยกรรมที่รองรับในlibunwind/src )

น่าเสียดายที่เพื่อนร่วมทาง backtrace_symbolsdและbacktrace_symbols_fdฟังก์ชันไม่สามารถแก้ไขที่อยู่ stacktrace เป็นชื่อฟังก์ชันที่มีชื่อไฟล์ต้นฉบับและหมายเลขบรรทัดได้เป็นเวลาหนึ่งทศวรรษแล้ว (ดูตัวอย่างผลลัพธ์ด้านล่าง)

แต่มีวิธีการที่จะแก้ปัญหาที่อยู่สัญลักษณ์อื่นและก่อให้เกิดประโยชน์มากที่สุดร่องรอยที่มีชื่อฟังก์ชั่น , แฟ้มแหล่งที่มาและหมายเลขบรรทัด วิธีนี้คือการ#include <elfutils/libdwfl.h>เชื่อมโยงกับ-ldw( libdw-devบน Ubuntu 18.04)

ตัวอย่างการทำงาน C ++ ( test.cc):

#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>

#include <boost/core/demangle.hpp>

#include <execinfo.h>
#include <elfutils/libdwfl.h>

struct DebugInfoSession {
    Dwfl_Callbacks callbacks = {};
    char* debuginfo_path = nullptr;
    Dwfl* dwfl = nullptr;

    DebugInfoSession() {
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;

        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);

        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    ~DebugInfoSession() {
        dwfl_end(dwfl);
    }

    DebugInfoSession(DebugInfoSession const&) = delete;
    DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};

struct DebugInfo {
    void* ip;
    std::string function;
    char const* file;
    int line;

    DebugInfo(DebugInfoSession const& dis, void* ip)
        : ip(ip)
        , file()
        , line(-1)
    {
        // Get function name.
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? boost::core::demangle(name) : "<unknown>";

        // Get source filename and line number.
        if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
    }
};

std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
    s << di.ip << ' ' << di.function;
    if(di.file)
        s << " at " << di.file << ':' << di.line;
    return s;
}

void terminate_with_stacktrace() {
    void* stack[512];
    int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);

    // Print the exception info, if any.
    if(auto ex = std::current_exception()) {
        try {
            std::rethrow_exception(ex);
        }
        catch(std::exception& e) {
            std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
        }
        catch(...) {
            std::cerr << "Fatal unknown exception.\n";
        }
    }

    DebugInfoSession dis;
    std::cerr << "Stacktrace of " << stack_size << " frames:\n";
    for(int i = 0; i < stack_size; ++i) {
        std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
    }
    std::cerr.flush();

    std::_Exit(EXIT_FAILURE);
}

int main() {
    std::set_terminate(terminate_with_stacktrace);
    throw std::runtime_error("test exception");
}

คอมไพล์บน Ubuntu 18.04.4 LTS พร้อม gcc-8.3:

g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind

ขาออก:

Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start

เมื่อไม่มีการ-lunwindเชื่อมโยงระบบจะสร้าง stacktrace ที่แม่นยำน้อยกว่า:

0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start

สำหรับการเปรียบเทียบbacktrace_symbols_fdผลลัพธ์สำหรับ stacktrace เดียวกันนั้นให้ข้อมูลน้อยที่สุด:

/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]

ในรุ่นการผลิต (เช่นเดียวกับรุ่นภาษา C) คุณอาจต้องการที่จะให้รหัสนี้ที่แข็งแกร่งเป็นพิเศษโดยการแทนที่boost::core::demangle,std::stringและstd::coutด้วยโทรศัพท์พื้นฐานของพวกเขา

คุณยังสามารถแทนที่__cxa_throwเพื่อจับภาพ stacktrace เมื่อมีการโยนข้อยกเว้นและพิมพ์เมื่อข้อยกเว้นถูกจับได้ เมื่อถึงเวลาที่จะเข้าสู่catchบล็อกสแต็คที่ได้รับการคลายดังนั้นมันสายเกินไปที่จะโทรbacktraceและนี่คือเหตุผลที่สแต็คจะต้องถูกจับซึ่งดำเนินการโดยฟังก์ชั่นthrow __cxa_throwโปรดทราบว่าในโปรแกรมแบบมัลติเธรด__cxa_throwสามารถเรียกพร้อมกันโดยหลายหัวข้อเพื่อที่ว่าถ้ามันจับ stacktrace thread_localลงในอาร์เรย์ทั่วโลกที่จะต้อง


1
คำตอบที่ดี! ค้นคว้ามาอย่างดีเช่นกัน
SS Anne

@SSAnne ใจดีมากขอบคุณ ว่า-lunwindปัญหาได้รับการค้นพบในขณะที่การโพสต์นี้ผมเคยใช้libunwindโดยตรงจะได้รับ stacktrace และกำลังจะไปโพสต์ไว้ แต่backtraceไม่ได้สำหรับฉันเมื่อ-lunwindมีการเชื่อมโยง
Maxim Egorushkin

1
@SSAnne อาจจะเป็นเพราะผู้เขียนต้นฉบับของห้องสมุดเดวิด Mosbergerกำลังจดจ่ออยู่กับ IA-64 ในตอนแรก แต่แล้วห้องสมุดมีมากขึ้นฉุดnongnu.org/libunwind/people.html gccไม่เปิดเผย API ใช่ไหม
Maxim Egorushkin

3

คุณสามารถใช้ฟังก์ชันนี้ได้ด้วยตนเอง:

ใช้โกลบอลสแต็ก (สตริง) และเมื่อเริ่มต้นของแต่ละฟังก์ชันให้ดันชื่อฟังก์ชันและค่าอื่น ๆ (เช่นพารามิเตอร์) ลงบนสแต็กนี้ เมื่อออกจากฟังก์ชันจะปรากฏขึ้นอีกครั้ง

เขียนฟังก์ชันที่จะพิมพ์เนื้อหาสแต็กเมื่อถูกเรียกใช้และใช้ฟังก์ชันนี้ในฟังก์ชันที่คุณต้องการดู callstack

อาจฟังดูเหมือนงานเยอะ แต่มีประโยชน์มากทีเดียว


2
ฉันจะไม่ทำอย่างนั้น แต่ฉันจะสร้าง Wrapper ซึ่งใช้ API เฉพาะแพลตฟอร์มที่เป็นพื้นฐาน (ดูด้านล่าง) ปริมาณงานก็คงเท่าเดิม แต่การลงทุนควรจะหมดเร็วขึ้น
Paul Michalik

3
@ paul: คำตอบของคุณหมายถึง windows เมื่อ OP ระบุ linux อย่างชัดเจน ... แต่อาจมีประโยชน์สำหรับ windows-guys ที่ปรากฏที่นี่
slashmais

ใช่ฉันมองข้ามไปว่า .. อืมมันเป็นประโยคสุดท้ายของคำถามดังนั้นบางทีผู้โพสต์ควรปรับเปลี่ยนคำขอของเขาให้พูดถึงแพลตฟอร์มเป้าหมายของเขาในตำแหน่งที่โดดเด่นกว่านี้
Paul Michalik

1
นี่เป็นความคิดที่ดียกเว้น codebase ของฉันมีไฟล์ไม่กี่สิบไฟล์ที่มีไฟล์ไม่กี่ร้อย (ถ้าไม่ใช่สองสามพัน) ดังนั้นจึงเป็นไปไม่ได้
Nathan Fellman

อาจจะไม่ใช่ถ้าคุณแฮ็คสคริปต์ sed / perl เพื่อเพิ่มหลังจากการประกาศฟังก์ชันแต่ละครั้งcall_registror MY_SUPERSECRETNAME(__FUNCTION__);ซึ่งผลักอาร์กิวเมนต์ในตัวสร้างและปรากฏในตัวทำลายฟังก์ชัน FUNCTIONจะแสดงชื่อของฟังก์ชันปัจจุบันเสมอ
บิน

2

แน่นอนคำถามต่อไปคือจะเพียงพอหรือไม่?

ข้อเสียเปรียบหลักของสแต็กเทรซคือเหตุใดคุณจึงมีฟังก์ชันที่แม่นยำถูกเรียกว่าคุณไม่มีสิ่งอื่นใดเช่นค่าของอาร์กิวเมนต์ซึ่งมีประโยชน์มากสำหรับการดีบัก

หากคุณสามารถเข้าถึง gcc และ gdb ได้ฉันขอแนะนำให้ใช้assertเพื่อตรวจสอบเงื่อนไขเฉพาะและสร้างการถ่ายโอนข้อมูลหน่วยความจำหากไม่เป็นไปตามนั้น แน่นอนว่านี่หมายความว่ากระบวนการจะหยุดลง แต่คุณจะมีรายงานฉบับเต็มแทนที่จะเป็นเพียงสแต็กแทร็ก

หากคุณต้องการวิธีที่ไม่เป็นการรบกวนคุณสามารถใช้การบันทึกได้ตลอดเวลา มีสิ่งอำนวยความสะดวกในการบันทึกที่มีประสิทธิภาพมากเช่นPantheiosเป็นต้น ซึ่งจะช่วยให้คุณเห็นภาพที่ถูกต้องมากขึ้นอีกครั้งว่ากำลังเกิดอะไรขึ้น


1
แน่นอนว่ามันอาจไม่เพียงพอ แต่ถ้าฉันเห็นว่าฟังก์ชันถูกเรียกใช้ด้วยการกำหนดค่าเดียวไม่ใช่กับอีกฟังก์ชันนั่นก็เป็นจุดเริ่มต้นที่ดีทีเดียว
Nathan Fellman

2

คุณสามารถใช้Poppyสำหรับสิ่งนี้ โดยปกติจะใช้เพื่อรวบรวมการติดตามสแต็กในระหว่างการหยุดทำงาน แต่ยังสามารถส่งออกสำหรับโปรแกรมที่กำลังทำงานอยู่ได้เช่นกัน

ต่อไปนี้เป็นส่วนที่ดี: สามารถส่งออกค่าพารามิเตอร์จริงสำหรับแต่ละฟังก์ชันบนสแต็กและแม้แต่ตัวแปรโลคัลตัวนับลูป ฯลฯ


2

ฉันรู้ว่ากระทู้นี้เก่า แต่ฉันคิดว่ามันน่าจะเป็นประโยชน์สำหรับคนอื่น ๆ หากคุณใช้ gcc คุณสามารถใช้คุณสมบัติของเครื่องมือ (ตัวเลือก -finstrument-functions) เพื่อบันทึกการเรียกใช้ฟังก์ชันใด ๆ (การเข้าและออก) ดูข้อมูลเพิ่มเติมได้ที่http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

ดังนั้นคุณสามารถเช่น push และ pop ทุกสายลงในกองและเมื่อคุณต้องการพิมพ์คุณเพียงแค่ดูสิ่งที่คุณมีในกองของคุณ

ฉันได้ทดสอบแล้วมันใช้งานได้ดีและมีประโยชน์มาก

อัปเดต: คุณยังสามารถค้นหาข้อมูลเกี่ยวกับตัวเลือกการคอมไพล์ -finstrument-functions ในเอกสาร GCC เกี่ยวกับตัวเลือก Instrumentation: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html


คุณควรเชื่อมโยงไปยังเอกสาร GCC ในกรณีที่บทความหยุดทำงาน
HolyBlackCat

ขอบคุณคุณพูดถูก ฉันจึงได้เพิ่ม UPDATE ในโพสต์ของฉันพร้อมลิงก์ไปยัง gcc doc
François

2

คุณสามารถใช้ไลบรารี Boost เพื่อพิมพ์ callstack ปัจจุบัน

#include <boost/stacktrace.hpp>

// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

ชายที่นี่: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html


ฉันได้รับข้อผิดพลาดcannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dllใน Win10
zwcloud

0

คุณสามารถใช้ GNU profiler มันแสดงกราฟโทรด้วย! คำสั่งคือgprofและคุณต้องคอมไพล์โค้ดของคุณด้วยตัวเลือกบางอย่าง


-6

มีวิธีใดในการถ่ายโอนข้อมูลการโทรในกระบวนการทำงานใน C หรือ C ++ ทุกครั้งที่เรียกใช้ฟังก์ชันบางอย่าง

ไม่มีแม้ว่าอาจมีโซลูชันที่ขึ้นอยู่กับแพลตฟอร์ม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.