C veya C ++ 'da çağrı yığınını yazdır


120

Belirli bir işlev her çağrıldığında çağrı yığınını çalışan bir işlemde C veya C ++ 'da dökmenin bir yolu var mı? Aklımdaki şey şuna benzer:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

Nerede print_stack_tracebenzer şekilde çalışır callerPerl.

Veya bunun gibi bir şey:

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

    // etc...
}

burada register_stack_trace_functionbir yığın izleme neden olur iç kesme çeşit basılacak koyar her foodenir.

Bazı standart C kütüphanelerinde buna benzer bir şey var mı?

GCC kullanarak Linux üzerinde çalışıyorum.


Arka fon

Bu davranışı etkilememesi gereken bazı komut satırı anahtarlarına göre farklı davranan bir test çalıştırmam var. Kodumda, bu anahtarlara bağlı olarak farklı şekilde çağrıldığını varsaydığım sözde rastgele bir sayı üreteci var. Testi her anahtar setiyle çalıştırabilmek ve rastgele sayı üreticisinin her biri için farklı şekilde çağrılıp çağrılmadığını görmek istiyorum.


1
@Armen, bunlardan herhangi birine aşina mısın?
Nathan Fellman

1
@Nathan: Hata ayıklayıcınız gdb ise bu durumu halledebilir . Size başkalarından bahsedemem, ancak gdb'nin bu işlevselliğe sahip olması konusunda yalnız olmadığını varsayıyorum. Kenara: Daha önceki yorumuma baktım . :: gag :: bu s/easier/either/nasıl oldu?
dmckee --- eski moderatör yavru kedi

2
@dmckee: Aslında öyle olmalı s/either/easier. Gdb ile yapmam gereken şey, o işlevi kesen ve yığın izini yazdıran ve sonra devam eden bir betik yazmaktır. Şimdi düşünüyorum da, belki de gdb komut dosyası oluşturma hakkında bilgi edinme zamanım gelmiştir.
Nathan Fellman

1
Gah! Biraz uyumaya gidiyorum. Şimdi çok yakında ...
dmckee --- eski moderatör yavru kedi

Yanıtlar:


79

Yalnızca linux çözümü için, basitçe bir dizi döndüren (aslında bu noktalardan her biri karşılık gelen yığın çerçevesinden dönüş adresine ) geri izlemeyi (3) kullanabilirsiniz void *. Bunları işe yarar bir şeye çevirmek için backtrace_symbols (3) vardır .

Geri izleme (3) ' deki notlar bölümüne dikkat edin :

Sembol adları, özel bağlayıcı seçenekleri kullanılmadan kullanılamayabilir. GNU bağlayıcısını kullanan sistemler için -rdynamic bağlayıcı seçeneğini kullanmak gerekir. "Statik" işlevlerin adlarının açığa çıkmadığını ve geri izlemede kullanılamayacağını unutmayın.


10
FWIW, bu işlevsellik Mac OS X'te de mevcuttur: developer.apple.com/library/mac/#documentation/Darwin/Reference/…
EmeryBerger



Linux'ta glibc, ne yazık ki, backtrace_symbolsişlevler işlev adı, kaynak dosya adı ve satır numarası sağlamaz.
Maxim Egorushkin

Kullanmanın yanı sıra -rdynamic, derleme sisteminizin -fvisibility=hiddenseçenek eklemediğini de kontrol edin ! (etkisini tamamen ortadan kaldıracağı için -rdynamic)
Dima Litvinov

38

Yığın izlemeyi artırın

Belgelendirilmiştir: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Bu şimdiye kadar gördüğüm en uygun seçenek çünkü:

  • aslında satır numaralarını yazdırabilir.

    Sadece çağrı yapar addr2lineancak çirkin ve sizin çok fazla iz alıyor yavaş olabilir.

  • varsayılan olarak demangles

  • Yükseltme yalnızca başlıktır, bu nedenle büyük olasılıkla derleme sisteminizi değiştirmenize gerek yoktur

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
    }
}

Ne yazık ki, daha yeni bir ekleme gibi görünüyor ve paket libboost-stacktrace-devUbuntu 16.04'te değil, sadece 18.04'te mevcut:

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

-ldlSonuna eklemeliyiz yoksa derleme başarısız olur.

Çıktı:

 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

Çıktı ve aşağıda benzer olan "glibc geri izleme" bölümünde daha ayrıntılı açıklanmıştır.

Not nasıl my_func_1(int)ve my_func_1(float), bağlı fonksiyon aşırı yüklenmesine mangled edilir , güzel bizim için demangled bulundu.

İlk intaramaların bir hat kapalı olduğuna dikkat edin (27 yerine 28 ve ikincisinde iki hat kapalı (29 yerine 27) Yorumlarda bunun aşağıdaki talimat adresinin dikkate alınmasından kaynaklandığı ileri sürüldü. 27'yi 28'e, 29'u döngüden atlayarak 27'ye dönüştürür.

Ardından -O3, çıktının tamamen parçalandığını gözlemliyoruz :

 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

Geri izlemeler genel olarak iyileştirmelerle onarılamayacak şekilde bozulur. Kuyruk arama optimizasyonu bunun dikkate değer bir örneğidir: Kuyruk arama optimizasyonu nedir?

Karşılaştırma çalışması -O3:

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

Çıktı:

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

Beklendiği gibi, bu yöntemin dış aramalara son derece yavaş olduğunu addr2lineve yalnızca sınırlı sayıda arama yapılıyorsa uygulanabilir olacağını görüyoruz .

Her geri izleme baskısı yüzlerce milisaniye sürüyor gibi görünmektedir, bu nedenle bir geri izleme çok sık gerçekleşirse, program performansının önemli ölçüde düşeceği konusunda uyarılmalıdır.

Ubuntu 19.10, GCC 9.2.1, boost 1.67.0 üzerinde test edilmiştir.

glibc backtrace

Belgelendirilmiştir: 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;
}

Derleme:

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

-rdynamic anahtar gerekli seçenektir.

Çalıştırmak:

./main.out

Çıktılar:

./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]

Böylece hemen bir satır içi optimizasyonun gerçekleştiğini ve bazı işlevlerin izlemeden kaybolduğunu görüyoruz.

Adresleri almaya çalışırsak:

addr2line -e main.out 0x4008f9 0x4008fe

elde ederiz:

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

ki bu tamamen kapalıdır.

-O0Bunun yerine aynı şeyi yaparsak ./main.out, doğru tam izini verir:

./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]

ve sonra:

addr2line -e main.out 0x400a74 0x400a79

verir:

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

öyleyse satırlar sadece bir farkla, TODO neden? Ancak bu yine de kullanılabilir.

Sonuç: Geriye dönük izler ancak muhtemelen mükemmel şekilde gösterilebilir -O0. Optimizasyonlarla, orijinal geri izleme derlenen kodda temelde değiştirilir.

Bununla C ++ sembollerini otomatik olarak ayırmanın basit bir yolunu bulamadım, ancak işte bazı hileler:

Ubuntu 16.04, GCC 6.4.0, libc 2.23 üzerinde test edilmiştir.

glibc backtrace_symbols_fd

Bu yardımcı, biraz daha kullanışlıdır backtrace_symbolsve temelde aynı çıktıyı üretir:

/* 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 üzerinde test edilmiştir.

glibc backtraceile C ++ demangling hack 1: -export-dynamic+dladdr

Https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3 adresinden uyarlanmıştır

Bu bir "hack" tir çünkü ELF'yi ile değiştirmeyi gerektirir -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);
}

Derleyin ve çalıştırın:

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 

çıktı:

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'te test edildi.

glibc backtraceile C ++ demangling hack 2: parse backtrace output

Şu adreste gösteriliyor: https://panthema.net/2008/0901-stacktrace-demangled/

Bu bir hack'tir çünkü ayrıştırma gerektirir.

TODO onu derlemek ve burada göstermek için alın.

libunwind

TODO'nun glibc geri izlemeye göre herhangi bir avantajı var mı? Çok benzer çıktı, ayrıca build komutunun değiştirilmesini gerektirir, ancak glibc'nin bir parçası değildir, bu nedenle fazladan bir paket kurulumu gerektirir.

Kod uyarlanmıştır: 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;
}

Derleyin ve çalıştırın:

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

Ya #define _XOPEN_SOURCE 700üstte olmalı ya da kullanmalıyız -std=gnu99:

Çalıştırmak:

./main.out

Çıktı:

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

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

ve:

addr2line -e main.out 0x4007db 0x4007e2

verir:

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

İle -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)

ve:

addr2line -e main.out 0x4009f3 0x4009f8

verir:

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

Ubuntu 16.04, GCC 6.4.0, libunwind 1.1'de test edilmiştir.

C ++ adı ayrıştırmalı libunwind

Kod uyarlanmıştır: 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);
}

Derleyin ve çalıştırın:

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

Çıktı:

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)

ve sonra biz satırları bulabilirsiniz my_func_2ve my_func_1(int)şununla:

addr2line -e unwind.out 0x400c80 0x400cb7

hangi verir:

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

TODO: Hatlar neden birer birer kapalı?

Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1 üzerinde test edilmiştir.

GDB otomasyonu

Bunu GDB ile yeniden derlemeden de şunu kullanarak yapabiliriz: GDB'de belirli bir kesme noktası vurulduğunda belirli bir eylem nasıl yapılır?

Geriye dönük izlemeyi çok fazla yazdıracak olsanız da, bu muhtemelen diğer seçeneklerden daha az hızlı olacaktır, ancak belki ile yerel hızlara ulaşabiliriz compile code, ancak şimdi test etmek için tembelim: gdb'de montaj nasıl çağrılır?

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

Derleyin ve çalıştırın:

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

Çıktı:

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]

TODO Oluşturmak -exzorunda kalmamak için bunu sadece komut satırından yapmak istedim main.gdbama commandsorada çalışmayı başaramadım .

Ubuntu 19.04, GDB 8.2'de test edilmiştir.

Linux çekirdeği

Linux çekirdeği içindeki mevcut iş parçacığı yığın izi nasıl yazdırılır?

libdwfl

Bu başlangıçta şu adreste belirtilmişti: https://stackoverflow.com/a/60713161/895245 ve bu en iyi yöntem olabilir, ancak biraz daha kıyaslamalıyım, ancak lütfen bu yanıta oy verin.

TODO: Bu cevaptaki kodu tek bir işleve indirgemeye çalıştım, ancak bu segfaulting, kimse nedenini bulabilirse bana bildirin.

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);
    }
}

Derleyin ve çalıştırın:

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

Çıktı:

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

Karşılaştırma çalışması:

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

Çıktı:

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

Bu nedenle, bu yöntemin Boost'un yığın izlemesinden 10 kat daha hızlı olduğunu ve bu nedenle daha fazla kullanım durumu için geçerli olabileceğini görüyoruz.

Ubuntu 19.10 amd64, libdw-dev 0.176-1.1'de test edilmiştir.

Ayrıca bakınız


1
Tüm "YAPILACAKLAR: satırlar tek tek" satır numarasının bir sonraki ifadenin başından alınmasıdır.
SS Anne


6

Belirli bir işlev her çağrıldığında çağrı yığınını çalışan bir işlemde C veya C ++ 'da dökmenin bir yolu var mı?

Belirli işlevde return ifadesi yerine bir makro işlevi kullanabilirsiniz.

Örneğin, return kullanmak yerine,

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

    ... do something ...

    return 0
}

Bir makro işlevi kullanabilirsiniz.

#include "c-callstack.h"

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

    ... do something ...

    NL_RETURN(0);
}

Bir işlevde bir hata meydana geldiğinde, aşağıda gösterildiği gibi Java tarzı çağrı yığınını göreceksiniz.

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)

Tam kaynak kodu burada mevcuttur.

c-callstack https://github.com/Nanolat adresinde


6

Eski bir konuya başka bir cevap.

Bunu yapmam gerektiğinde, genellikle sadece kullanıyorum system()vepstack

Yani bunun gibi bir şey:

#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();
}

Bu çıktılar

#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 ()

Bu, Linux, FreeBSD ve Solaris üzerinde çalışmalıdır. MacOS'un pstack veya basit bir eşdeğeri olduğunu sanmıyorum, ancak bu iş parçacığının bir alternatifi var gibi görünüyor .

Eğer kullanıyorsanız C, o zaman Cdize işlevlerini kullanmanız gerekecektir .

#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);
}

Ben dayalı PID basamak azami sayısı için 7 kullandım bu yazı .


İyi nokta, özne C'yi istediğinden, hayır uyarlamaya ihtiyaç duyacaktır, çünkü std :: string sadece C ++ 'dır. Cevabımı bir C versiyonu ile güncelleyeceğim.
Paul Floyd

6

Linux'a özgü, TLDR:

  1. backtracein glibc, yalnızca -lunwindbağlantılı olduğunda doğru yığın izleri üretir (belgelenmemiş platforma özgü özellik).
  2. Çıktı için işlev adını , kaynak dosyası ve satır numarası kullanımı #include <elfutils/libdwfl.h>(bu kütüphane sadece başlık dosyasında belgelenmiştir). backtrace_symbolsve backtrace_symbolsd_fden az bilgilendiricidir.

Modern Linux'ta, işlevi kullanarak yığın izleme adreslerini alabilirsiniz backtrace. backtracePopüler platformlarda daha doğru adresler üretmenin belgelenmemiş yolu , -lunwind( libunwind-devUbuntu 18.04'te) ile bağlantı kurmaktır (aşağıdaki örnek çıktıya bakın). backtraceişlevi kullanır _Unwind_Backtraceve varsayılan olarak ikincisi gelir libgcc_s.so.1ve bu uygulama en taşınabilirdir. Bağlandığında -lunwinddaha doğru bir sürüm sağlar, _Unwind_Backtraceancak bu kitaplık daha az taşınabilirdir (desteklenen mimarilere bakın libunwind/src).

Ne yazık ki, tamamlayıcı backtrace_symbolsdve backtrace_symbols_fdişlevler yığın izleme adreslerini kaynak dosya adı ve satır numarası ile işlev adlarına muhtemelen on yıldan beri çözümleyememiştir (aşağıdaki örnek çıktıya bakın).

Bununla birlikte, adresleri sembollere çözümlemek için başka bir yöntem vardır ve işlev adı , kaynak dosyası ve satır numarası ile en kullanışlı izleri üretir . Yöntem, ( Ubuntu 18.04'te) #include <elfutils/libdwfl.h>ile bağlantı kurmaktır.-ldwlibdw-dev

Çalışma C ++ örneği ( 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'de gcc-8.3 ile derlendi:

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

Çıktılar:

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

Hayır -lunwindbağlı olmadığında , daha az doğru bir yığın izleme üretir:

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

Karşılaştırma backtrace_symbols_fdiçin, aynı yığın izleme için çıktı en az bilgilendiricidir:

/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]

Bir üretim versiyonunda (yanı sıra C dili sürümü) size değiştirerek bu kod ekstra sağlam yapmak olabilir boost::core::demangle, std::stringve std::coutbunların altında yatan çağrılar geldi.

Ayrıca __cxa_throw, bir istisna atıldığında yığın izini yakalamak için geçersiz kılabilirsiniz ve istisna yakalandığında bunu yazdırabilirsiniz. catchBloğa girdiğinde yığın çözülmüştür, bu nedenle aramak için çok geçtir backtraceve bu nedenle throwişlev tarafından uygulanan yığının yakalanması gerekir __cxa_throw. Çok iş parçacıklı bir programda __cxa_throwaynı anda birden çok iş parçacığı tarafından çağrılabileceğine dikkat edin, böylece yığın izini yakalarsa olması gereken global bir diziye thread_local.


1
Güzel cevap! Ayrıca iyi araştırılmış.
SS Anne

@SSAnne Çok naziksiniz, teşekkür ederim. Bu -lunwindsorun bu yazıyı yaparken keşfedildi, daha önce libunwinddoğrudan yığın izini elde etmek için kullanıyordum ve onu gönderecektim, ancak bağlantı backtracekurulduğunda benim için yapıyor -lunwind.
Maxim Egorushkin

1
@SSAnne Belki de kütüphanenin orijinal yazarı David Mosberger başlangıçta IA-64'e odaklanmıştı, ancak daha sonra kütüphane daha fazla ilgi gördü nongnu.org/libunwind/people.html . gccAPI'yi ifşa etmiyor, doğru mu?
Maxim Egorushkin

3

İşlevselliği kendiniz uygulayabilirsiniz:

Global (dizgi) bir yığın kullanın ve her işlevin başlangıcında işlev adını ve diğer değerleri (örn. Parametreler) bu yığına itin; fonksiyonun çıkışında tekrar açılır.

Çağrıldığında yığın içeriğini yazdıracak bir işlev yazın ve bunu çağrı yığınını görmek istediğiniz işlevde kullanın.

Bu kulağa çok iş gibi gelebilir ama oldukça kullanışlıdır.


2
Bunu yapmazdım. Bunun yerine, temel platforma özgü API'leri kullanan bir sarmalayıcı oluşturardım (aşağıya bakın). İş miktarı muhtemelen aynı olacaktır, ancak yatırım daha hızlı ödenmelidir.
Paul Michalik

3
@paul: OP'nin linux'u açıkça belirttiği durumlarda cevabınız pencerelere atıfta bulunur ... ancak burada görünen Windows kullanıcıları için yararlı olabilir.
slashmais

Doğru, bunu göz ardı ettim ... Hm, bu sorunun son cümlesi, bu yüzden belki de poster talebini daha belirgin bir yerde hedef platformundan bahsetmek için değiştirmeli.
Paul Michalik

1
Kod tabanımın birkaç yüz (birkaç bin değilse bile) dosya içeren birkaç düzine dosya içermesi dışında bu iyi bir fikir olabilir, bu yüzden bu mümkün değildir.
Nathan Fellman

call_registror MY_SUPERSECRETNAME(__FUNCTION__);yapıcısında argümanı iten ve yıkıcı FUNCTION içindeki açılır her işlev bildiriminden sonra eklemek için bir sed / perl betiğini hack'lediğinizde olmayabilir .
flownt

2

Tabii ki bir sonraki soru şu: bu yeterli olacak mı?

Yığın izlerinin ana dezavantajı, neden tam olarak çağrılan fonksiyona sahip olmanızdır, hata ayıklama için çok yararlı olan argümanlarının değeri gibi başka hiçbir şeye sahip olmamanızdır.

Gcc ve gdb'ye erişiminiz varsa, assertbelirli bir koşulu kontrol etmek için kullanmanızı ve karşılanmazsa bir bellek dökümü oluşturmanızı öneririm . Elbette bu, sürecin duracağı anlamına gelir, ancak yalnızca yığın izleme yerine tam teşekküllü bir raporunuz olacaktır.

Daha az rahatsız edici bir yol istiyorsanız, günlük kaydını her zaman kullanabilirsiniz. Örneğin Pantheios gibi çok verimli ağaç kesme tesisleri var . Bu da size neler olup bittiğine dair çok daha doğru bir görüntü verebilir.


1
Elbette yeterli olmayabilir, ancak işlevin bir yapılandırma ile yerine diğeriyle değil çağrıldığını görebilirsem, o zaman başlamak için oldukça iyi bir yer.
Nathan Fellman

2

Bunun için Poppy'yi kullanabilirsiniz . Normalde bir çökme sırasında yığın izini toplamak için kullanılır, ancak aynı zamanda çalışan bir program için de çıktı alabilir.

Şimdi işte iyi kısım: Yığın üzerindeki her bir fonksiyon için gerçek parametre değerlerini ve hatta yerel değişkenleri, döngü sayaçlarını vb.


2

Bu konunun eski olduğunu biliyorum, ancak diğer insanlar için faydalı olabileceğini düşünüyorum. Gcc kullanıyorsanız, herhangi bir işlev çağrısını (giriş ve çıkış) kaydetmek için enstrüman özelliklerini (-finstrument-functions seçeneği) kullanabilirsiniz. Daha fazla bilgi için buna bir göz atın: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

Böylece, örneğin her aramayı bir yığına itip atabilirsiniz ve bunu yazdırmak istediğinizde, yığınızda ne olduğuna bakarsınız.

Test ettim, mükemmel çalışıyor ve çok kullanışlı

GÜNCELLEME: GCC belgesinde -finstrument-functions derleme seçeneği hakkında bilgi bulabilirsiniz ve Enstrümantasyon seçenekleri: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html


Makalenin azalması durumunda GCC belgelerine de bağlantı vermelisiniz.
HolyBlackCat

Teşekkür ederim haklısın Bu nedenle, gönderime gcc belgesine bağlantı içeren bir GÜNCELLEME ekledim
François

2

Mevcut çağrı yığınını yazdırmak için Boost kitaplıklarını kullanabilirsiniz.

#include <boost/stacktrace.hpp>

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

Buradaki adam: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html


cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dllWin10'da bir hata aldım.
zwcloud

0

GNU profil oluşturucusunu kullanabilirsiniz. Çağrı grafiğini de gösterir! komuttur gprofve kodunuzu bir seçenekle derlemeniz gerekir.


-6

Belirli bir işlev her çağrıldığında çağrı yığınını çalışan bir işlemde C veya C ++ 'da dökmenin bir yolu var mı?

Platforma bağlı çözümler mevcut olsa da, yoktur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.