5

I want to get a backtrace-like output as gdb does. But I want to do this via ptrace() directly. My platform is Linux, x86; and, later x86_64.

Now I want only to read return addresses from the stack, without conversion into symbol names.

So, for test program, compiled in -O0 mode by 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();
  }

I will start a my program and connect with ptrace to test program at very beginning. Then, I will do PTRACE_CONT and will wait for signal. When test program will do a self-kill; the signal will be delivered to my program. At this moment I want to read return addresses, they will be like (because kill function is active at the moment):

 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

How can I find return addresses of currently stopped test process with ptrace? There will be a loop over frames? When should I stop such loop?

PS: yes, this is also very like backtrace(3) libc function in idea, but I want to do this externally via ptrace.

osgx
  • 90,338
  • 53
  • 357
  • 513

2 Answers2

8

The example posted by osgx will work only with code that uses frame pointers. x86_64 code produced by GCC with optimizations doesn't. The kernel vdso code on x86 doesn't use frame pointers on at least some processors. GCC 4.6 (with optimization) doesn't use frame pointers in x86 mode either.

All of the above combine to make the "stack crawl via frame pointers" exceedingly unreliable.

You can use libunwind (which supports both local (in-process) and global (out-of-process via ptrace) unwinding).

Or you'll have to re-implement very large portion of libunwind.

Example of getting backtrace via ptrace using libunwind.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • can you provide link to libunwind external unwinding documents? – osgx Aug 31 '11 at 15:50
  • This? http://www.nongnu.org/libunwind/man/libunwind-ptrace(3).html - Is there some examples of getting backtrace with libunwind-ptrace? – osgx Aug 31 '11 at 15:53
  • I've added links to documentation. No: libunwind-ptrace is not what you want -- it's the part of libunwind that implements ptrace facilities. You can use that part to re-implement higher levels of libunwind, but there should be no reason for you to do so. – Employed Russian Aug 31 '11 at 17:02
  • If I'm reading the documentation correctly, to use the ptrace code, one should create an addr space using `_UPT_accessors`, and pass that to `unw_init_remote` – Hasturkun Aug 31 '11 at 17:20
  • Can't use libunwind for sparc "`Unknown ELF target`"; the test-ptrace fails on my x86 with "`too deeply nested---assuming bogus unwind`" – osgx Sep 07 '11 at 13:51
  • Your question was about about "linux(x86/x86_64)". Last I checked, SPARC was not an x86 variant. Unwinding on SPARC is actually trivial, and doesn't require anything nearly as complicated as libunwind. As for test-ptrace, it (and several others) are known to fail. Check libunwind mailing list for current known failures. – Employed Russian Sep 07 '11 at 21:30
0

May be, source of pstack(1) utility will help me: (online git from debian). Unfortunately this is x86 32-bit only

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 

Also, quick test says that is it not very reliable, but sometimes it can print something.

osgx
  • 90,338
  • 53
  • 357
  • 513