Как получить обратную трассировку (например, gdb), используя только ptrace (linux, x86/x86_64)

Я хочу получить вывод, похожий на backtrace, как это делает gdb. Но я хочу сделать это напрямую через ptrace(). Моя платформа — Linux, x86; а позже x86_64.

Теперь я хочу только читать адреса возврата из стека, без преобразования в имена символов.

Итак, для тестовой программы, скомпилированной в режиме -O0 автором gcc-4.5:

  int g() {
    kill(getpid(),SIGALRM);
  }
  int f() {
    int a;
    int b;
    a = g();
    b = a;
    return a+b;
  }
  int e() {
    int c;
    c = f();
  }
  main() {
    return e();
  }

Я запущу свою программу и свяжусь с ptrace для тестирования программы в самом начале. Затем я сделаю PTRACE_CONT и буду ждать сигнала. Когда тестовая программа выполнит самоуничтожение; сигнал будет доставлен в мою программу. В этот момент я хочу прочитать обратные адреса, они будут такими (потому что функция kill в данный момент активна):

 0x00_some_address_in_g
 0x00_some_address_in_f
 0x00_some_address_in_e
 0x00_some_address_in_main
 0x00_some_address_in__libc_start_main

Как я могу найти адреса возврата текущего остановленного тестового процесса с помощью ptrace? Будет ли петля по кадрам? Когда я должен остановить такой цикл?

PS: да, это тоже очень похоже на backtrace(3)libc function в идее, но я хочу сделать это извне через ptrace.


person osgx    schedule 31.08.2011    source источник


Ответы (2)


Пример, опубликованный osgx, будет работать только с кодом, использующим указатели фреймов. x86_64 код, созданный GCC с оптимизацией, не работает. Код ядра vdso на x86 не использует указатели кадров, по крайней мере, на некоторых процессорах. GCC 4.6 (с оптимизацией) также не использует указатели кадров в режиме x86.

Все вышеперечисленное в совокупности делает «сканирование стека с помощью указателей фреймов» чрезвычайно ненадежным.

Вы можете использовать libunwind (который поддерживает как local (внутрипроцессный ) и global (вне процесса через ptrace) раскручивание ).

Или вам придется повторно реализовать очень большую часть libunwind.

Пример получения обратной трассировки через ptrace с помощью libunwind.

person Employed Russian    schedule 31.08.2011
comment
Можете ли вы предоставить ссылку на внешние раскручивающиеся документы libunwind? - person osgx; 31.08.2011
comment
Этот? nongnu.org/libunwind/man/libunwind-ptrace(3). html — Есть ли примеры получения обратной трассировки с помощью libunwind-ptrace? - person osgx; 31.08.2011
comment
Я добавил ссылки на документацию. Нет: libunwind-ptrace — это не то, что вам нужно — это часть libunwind, реализующая возможности ptrace. Вы можете использовать эту часть для повторной реализации более высоких уровней libunwind, но у вас не должно быть причин для этого. - person Employed Russian; 31.08.2011
comment
Если я правильно читаю документацию, чтобы использовать код ptrace, нужно создать адресное пространство, используя _UPT_accessors, и передать его unw_init_remote - person Hasturkun; 31.08.2011
comment
Невозможно использовать libunwind для sparc Unknown ELF target; test-ptrace не работает на моем x86 с too deeply nested---assuming bogus unwind - person osgx; 07.09.2011
comment
Ваш вопрос был о Linux (x86/x86_64). Последнее, что я проверял, SPARC не был вариантом x86. Раскрутка на SPARC на самом деле тривиальна и не требует ничего такого сложного, как libunwind. Что касается test-ptrace, известно, что он (и некоторые другие) не работает. Проверьте список рассылки libunwind, чтобы узнать о текущих известных сбоях. - person Employed Russian; 08.09.2011

Возможно, мне поможет исходник утилиты pstack(1): (онлайн-git от debian). К сожалению, это только 32-разрядная версия x86.

http://anonscm.debian.org/gitweb/?p=collab-maint/pstack.git;a=blob;f=pstack.c;h=61beb8d10fa490492ab351115f261614d00adb6d;hb=HEAD#l547

 547 static int crawl(int pid)
 548 {
 549   unsigned long pc, fp, nextfp, nargs, i, arg;
 550   int error_occured = 0;
 551 
 552   errno = 0;
 553   fp = -1;
 554 
 555   pc = ptrace(PTRACE_PEEKUSER, pid, EIP * 4, 0);
 556   if (pc != -1 || !errno)
 557     fp = ptrace(PTRACE_PEEKUSER, pid, EBP * 4, 0);
 558 
 559   if ((pc != -1 && fp != -1) || !errno) {
 560     print_pc(pc);
 561     for ( ; !errno && fp; ) {
 562       nextfp = ptrace(PTRACE_PEEKDATA, pid, fp, 0);
 563       if (nextfp == (unsigned) -1 && errno) break;
 564 
 565       nargs = (nextfp - fp - 8) / 4;
 566       if (nargs > MAXARGS) nargs = MAXARGS;
 567       if (nargs > 0) {
 568         fputs(" (", stdout);
 569         for (i = 1; i <= nargs; i++) {
 570           arg = ptrace(PTRACE_PEEKDATA, pid, fp + 4 * (i + 1), 0);
 571           if (arg == (unsigned) -1 && errno) break;
 572           printf("%lx", arg);
 573           if (i < nargs) fputs(", ", stdout);
 574         }
 575         fputc(')', stdout);
 576         nargs = nextfp - fp - 8 - (4 * nargs);
 577         if (!errno && nargs > 0) printf(" + %lx\n", nargs);
 578         else fputc('\n', stdout);
 579       } else fputc('\n', stdout);
 580 
 581       if (errno || !nextfp) break;
 582       pc = ptrace(PTRACE_PEEKDATA, pid, fp + 4, 0);
 583       if (pc == (unsigned) -1 && errno) break;
 584       fp = nextfp;
 585       print_pc(pc);
 586     }
 587     if (fp) error_occured = 1;
 588   } else error_occured = 1;
 589 
 590   if (error_occured) perror("crawl");
 591   else errno = 0;
 592   return errno;
 593 }
 594 

Также быстрый тест говорит, что он не очень надежен, но иногда может что-то напечатать.

person osgx    schedule 31.08.2011