How can I attach a debugger to a running Perl process?
Asked Answered
M

4

35

I have a running Perl process that’s stuck, I’d like to poke inside with a debugger to see what’s wrong. I can’t restart the process. Can I attach the debugger to the running process? I know I can do gdb -p, but gdb does not help me. I’ve tried Enbugger, but failed:

$ perl -e 'while (1) {}'&
[1] 86836
$ gdb -p 86836
…
Attaching to process 86836.
Reading symbols for shared libraries . done
Reading symbols for shared libraries ............................. done
Reading symbols for shared libraries + done
0x000000010c1694c6 in Perl_pp_stub ()
(gdb) call (void*)Perl_eval_pv("require Enbugger;Enbugger->stop;",0)
perl(86836) malloc: *** error for object 0x3: pointer being realloc'd was not allocated
*** set a breakpoint in malloc_error_break to debug

Program received signal SIGABRT, Aborted.
0x00007fff8269d82a in __kill ()
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on"
Evaluation of the expression containing the function (Perl_eval_pv) will be abandoned.
(gdb) 

Am I doing it wrong? Are there other options?


P.S. If you think you could benefit from a debugger attached to a running process yourself, you can insert a debugger back door triggered by SIGUSR1:

use Enbugger::OnError 'USR1';

Then you can simply kill -USR1 pid and your process will jump into the debugger.

Monticule answered 12/1, 2012 at 14:29 Comment(0)
A
12

First, please use a DEBUGGING perl, if you want to inspect it with gdb.

Please define "stuck". Busy or non-busy waiting (high or low CPU), eating memory or not? With while 1 it is busy waiting. I usually get busy waiting (endless cycles) on HV corruption in Perl_hfree_next_entry() since 5.15. Non-busy waiting is usually waiting on a blocking IO read.

I get the correct:

`0x00007fba15ab35c1 in Perl_runops_debug () at dump.c:2266`
`2266       } while ((PL_op = PL_op->op_ppaddr(aTHX)));`

and can inspect everything, much more than with a simple perl debugger. With a non-threaded perl you have to type less.

`(gdb) p Perl_op_dump(PL_op)`

and so on.

If you have to do with perl: Inside the pp_stub function it is not a good idea to enter the Enbugger runloop, you should be in the main runloop in dump.c. Set a breakpoint to the shown line.

"error for object 0x3" on eval sound like internal corruption in the context, so you should look at the cx and stack pointers. Probably because you started it in a bad context.

Abbreviation answered 16/1, 2012 at 23:24 Comment(0)
C
7

I've never used gdb, but maybe you could get something useful out of strace?

strace -f -s512 -p <PID>
Chindwin answered 13/1, 2012 at 15:34 Comment(0)
I
6

http://metacpan.org/pod/App::Stacktrace “perl-stacktrace prints Perl stack traces of Perl threads for a given Perl process. For each Perl frame, the full file name and line number are printed.”

Interdiction answered 30/5, 2014 at 22:41 Comment(0)
D
1

Setup

  1. Install Enbugger from CPAN:
    cpan install Enbugger
    
  2. Add this to your .gdbinit:
    define perl_eval
        call (void*)Perl_eval_pv((void*)Perl_get_context(), $arg0, 0)
    end
    
    define perl_stop
        perl_eval "Enbugger->stop"
        continue
    end
    
    define perl_init
        python import os
        python gdb.execute("set $tty=\"" + os.ttyname(0) + "\"")
        call open($tty, 0)
        set $tty_in=$
        call open($tty, 1)
        set $tty_out=$
        call (int) 'dup@plt'(0)
        set $old_stdin=$
        call (int) 'dup@plt'(1)
        set $old_stdout=$
        call (int) 'dup@plt'(2)
        set $old_stderr=$
        call (int) 'dup2@plt'($tty_in, 0)
        call (int) 'dup2@plt'($tty_out, 1)
        call (int) 'dup2@plt'($tty_out, 2)
        eval "perl_eval \"$ENV{PERLDB_OPTS}='TTY=%s'\"", $tty
        perl_eval "require Enbugger"
    end
    
    define attach_perl
        attach $arg0
        perl_init
        perl_stop
    end
    
    Basically perl_init sets up terminal for perl debugger. If stdin/stdout/stderr was redirected by the debugged process you cannot interact perl debugger without the above setup. You can recover old redirections from the backup descriptors $old_stdin/$old_stdout/$old_stderr if needed.

Attach to perl process

  1. Attach to your perl program by PID from GDB:

    (gdb) attach_perl 640368
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    0x00007fd0b1313984 in __GI___select (nfds=0, readfds=0x0, writefds=0x0, exceptfds=0x0, timeout=0x7fff19c97240) at ../sysdeps/unix/sysv/linux/select.c:69
    69      ../sysdeps/unix/sysv/linux/select.c: No such file or directory.
    $1 = 7
    $2 = 8
    $3 = 9
    $4 = 10
    $5 = 11
    $6 = 0
    $7 = 1
    $8 = 2
    $9 = (void *) 0x562cae6a7810
    $10 = (void *) 0x562cad300ee0
    
    Loading DB routines from perl5db.pl version 1.60
    Editor support available.
    
    Enter h or 'h h' for help, or 'man perldebug' for more help.
    
    $11 = (void *) 0x562cad6322d0
    My::SafeProcess::(lib/My/SafeProcess.pm:543):
    543:        $millis++ if $millis < 1000;
      DB<1>
    
  2. Sometimes you will not get prompt in p.3, so you press Ctrl+C and run perl_stop:

    Enter h or 'h h' for help, or 'man perldebug' for more help.
    
    $11 = (void *) 0x560577ff45a0
    ^C
    Program received signal SIGINT, Interrupt.
    0x00007fd8f9ee25a7 in __GI___wait4 (pid=626998, stat_loc=0x7fffbbeedf1c, options=0, usage=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:30
    30      in ../sysdeps/unix/sysv/linux/wait4.c
    (gdb) perl_stop
    main::((eval 20)[/home/midenok/src/mariadb/10.4/build/opt/mysql-test/mysql-test-run:5]:1):
    1:      Enbugger->stop
      DB<1>
    

    Any time you can get from GDB to perl debugger by perl_stop as long as Enbugger is loaded.

  3. All you need to know is probably the stack trace:

      DB<1> T
    @ = DB::DB called from file '(eval 39)[/usr/lib/x86_64-linux-gnu/perl-base/IO/Select.pm:130]' line 1
    $ = eval 'Enbugger->stop' called from file '/usr/lib/x86_64-linux-gnu/perl-base/IO/Select.pm' line 130
    @ = IO::Select::can_read(ref(IO::Select), 1) called from file '/home/midenok/src/mariadb/10.4/src/mysql-test/mysql-test-run.pl' line 594
    @ = main::run_test_server(ref(IO::Socket::INET), ref(ARRAY), ref(HASH)) called from file '/home/midenok/src/mariadb/10.4/src/mysql-test/mysql-test-run.pl' line 485
    . = main::main() called from file '/home/midenok/src/mariadb/10.4/src/mysql-test/mysql-test-run.pl' line 343
    

    Here the latest execution point is Select.pm line 130, don't get surprised by injected eval 'Enbugger->stop', but sometimes you will see just a normal stack trace.

Detach from perl process

  1. Run "continue" in perl debugger:
      DB<1> c
    [Detaching after fork from child process 641324]
    [Detaching after fork from child process 641326]
    [Detaching after fork from child process 641328]
    [Detaching after fork from child process 641329]
    
  2. Press Ctrl+C and then Ctrl+D:
    ^C
    Program received signal SIGINT, Interrupt.
    0x00007fd0b1313984 in __GI___select (nfds=0, readfds=0x0, writefds=0x0, exceptfds=0x0, timeout=0x7fff19c97240) at ../sysdeps/unix/sysv/linux/select.c:69
    69      in ../sysdeps/unix/sysv/linux/select.c
    (gdb) quit
    Detaching from program: /usr/bin/perl, process 640368
    [Inferior 1 (process 640368) detached]
    
Delmadelmar answered 1/8, 2023 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.