GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana cara mendapatkan jejak tumpukan untuk C++ menggunakan gcc dengan informasi nomor baris?

Jadi, Anda menginginkan fungsi mandiri yang mencetak pelacakan tumpukan dengan semua fitur yang dimiliki gdb stack trace dan tidak menghentikan aplikasi Anda. Jawabannya adalah mengotomatiskan peluncuran gdb dalam mode non-interaktif untuk melakukan tugas yang Anda inginkan saja.

Ini dilakukan dengan mengeksekusi gdb dalam proses anak, menggunakan fork(), dan membuat skripnya untuk menampilkan jejak-tumpukan sementara aplikasi Anda menunggu hingga selesai. Ini dapat dilakukan tanpa menggunakan core-dump dan tanpa membatalkan aplikasi. Saya belajar bagaimana melakukan ini dari melihat pertanyaan ini:Bagaimana lebih baik memanggil gdb dari program untuk mencetak stacktrace-nya?

Contoh yang diposting dengan pertanyaan itu tidak bekerja untuk saya persis seperti yang tertulis, jadi inilah versi "tetap" saya (saya menjalankan ini di Ubuntu 9.04).

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/prctl.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
    int child_pid = fork();
    if (!child_pid) {
        dup2(2,1); // redirect output to stderr - edit: unnecessary?
        execl("/usr/bin/gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

Seperti yang ditunjukkan pada pertanyaan yang direferensikan, gdb menyediakan opsi tambahan yang dapat Anda gunakan. Misalnya, menggunakan "bt full" alih-alih "bt" menghasilkan laporan yang lebih detail (variabel lokal disertakan dalam output). Halaman manual untuk gdb agak ringan, tetapi dokumentasi lengkap tersedia di sini.

Karena ini didasarkan pada gdb, hasilnya mencakup nama yang dibongkar , nomor baris , argumen fungsi , dan bahkan variabel lokal opsional . Selain itu, gdb peka terhadap thread, jadi Anda seharusnya dapat mengekstrak beberapa metadata khusus thread.

Berikut adalah contoh jenis pelacakan tumpukan yang saya lihat dengan metode ini.

0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0  0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1  0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2  0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3  0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4  0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5  0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70

Catatan:Saya menemukan ini tidak kompatibel dengan penggunaan valgrind (mungkin karena Valgrind menggunakan mesin virtual). Itu juga tidak berfungsi saat Anda menjalankan program di dalam sesi gdb (tidak dapat menerapkan contoh kedua "ptrace" ke suatu proses).


Belum lama ini saya menjawab pertanyaan serupa. Anda harus melihat kode sumber yang tersedia pada metode #4, yang juga mencetak nomor baris dan nama file.

  • Metode #4:

Perbaikan kecil yang saya lakukan pada metode #3 untuk mencetak nomor baris. Ini juga dapat disalin untuk bekerja pada metode #2.

Pada dasarnya, ini menggunakan addr2line untuk mengubah alamat menjadi nama file dan nomor baris.

Kode sumber di bawah mencetak nomor baris untuk semua fungsi lokal. Jika fungsi dari pustaka lain dipanggil, Anda mungkin melihat beberapa ??:0 bukan nama file.

#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;

  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *)ctx.eip;
  messages = backtrace_symbols(trace, trace_size);
  /* skip first stack frame (points here) */
  printf("[bt] Execution path:\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s\n", i, messages[i]);

    /* find first occurence of '(' or ' ' in message[i] and assume
     * everything before that is the file name. (Don't go beyond 0 though
     * (string terminator)*/
    size_t p = 0;
    while(messages[i][p] != '(' && messages[i][p] != ' '
            && messages[i][p] != 0)
        ++p;

    char syscom[256];
    sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
        //last parameter is the file name of the symbol
    system(syscom);
  }

  exit(0);
}


int func_a(int a, char b) {

  char *p = (char *)0xdeadbeef;

  a = a + b;
  *p = 10;  /* CRASH here!! */

  return 2*a;
}


int func_b() {

  int res, a = 5;

  res = 5 + func_a(a, 't');

  return res;
}


int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_handler = (void *)bt_sighandler;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());
}

Kode ini harus dikompilasi sebagai:gcc sighandler.c -o sighandler -rdynamic

Keluaran program:

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0

Linux
  1. Cara Menghindari Serangan Stack Smashing dengan GCC

  2. Cara mendapatkan total penggunaan cpu di Linux menggunakan C++

  3. Bagaimana cara menghitung jumlah tab di setiap baris menggunakan skrip shell?

  1. Bagaimana cara memeriksa versi kompiler gcc C++ saya untuk Eclipse saya?

  2. Bash - Cara mencetak string multi baris (dengan '\n') menggunakan printf

  3. Bagaimana cara mengisi file dengan FF menggunakan dd?

  1. Bagaimana Cara Mendapatkan Hitungan File Dalam Direktori Menggunakan Baris Perintah?

  2. Bagaimana Cara Membuat A For Loop Dengan Jumlah Iterasi yang Dapat Diubah?

  3. Linux – Bagaimana Agar Grindeq (Plugin lateks Untuk Word) Bekerja Dengan Word Dalam Anggur?