Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Enabling or disabliing the debugger at runtime

by diotalevi (Canon)
on Jul 16, 2007 at 05:34 UTC ( [id://626777]=perlmeditation: print w/replies, xml ) Need Help??

I had a problem at work where two very small iterations of the debugger somehow triggered an "Out of memory" error. I think there was a little bit of memory corruption going on but that wasn't in the scope of what I was willing or able to tackle in that moment. So instead of solving the real problem, I learned how to load the debugger during runtime. I've no reason to think this method isn't applicable to removing the debugger either.

By the way, the punchline is that the module Enbugger is an implementation of the ideas in this post and with it you don't need to start perl with the -d flag or incur any debugging overhead. It does a bit more than it needs to but that's only because my understanding of how to do this evolved while I writing the module. Some future version (the current version is 0.03) will take this information into account. In fact, feel free to do this yourself if you wish. Email me at jjore at cpan.org to get PAUSE permissions if you're interested.

The only real trick appears to be to modify all "nextstate" opnodes to become "dbstate" opnodes. My first thought when approaching this problem was to hook the runops loop and change all OP_NEXTSTATE ops to OP_DBSTATE. This has two problems. Currently executing subroutines aren't affected because their runloop is already entered. I wasn't being discriminating about which nextstate ops to modify. I'd turned the debugger on itself. While this is interesting in theory, the debugger hasn't been tested while operating on itself and I'm not sure if this would break stuff.

Without debugging:

perl -MO=Terse -e 'print;exit' LISTOP (0x8133d68) leave [1] OP (0x8126628) enter COP (0x8133d28) nextstate LISTOP (0x8133e30) print OP (0x8133e68) pushmark UNOP (0x8133ce8) null [15] SVOP (0x8133f40) gvsv GV (0x811e120) *_ COP (0x8133d90) nextstate OP (0x8133c98) exit

With debugging:

perl -d -MO=Terse -e 'print;exit' Loading DB routines from perl5db.pl version 1.28 Editor support available. Enter h or `h h' for help, or `man perldebug' for more help. LISTOP (0x8133268) leave [1] OP (0x8126700) enter COP (0x81265f0) dbstate LISTOP (0x82db488) print OP (0x82db2d8) pushmark UNOP (0x82db468) null [15] SVOP (0x82db350) gvsv GV (0x811e120) *_ COP (0x81334b0) dbstate OP (0x8134a98) exit

In the above code, you can see an optree for the "main" part of a program including two "nextstate" operations. They occur roughly anywhere there could be a semi-colon in your perl program. If you compare to the debugged version, the same places that had nextstate operations now contain dbstate operations but were otherwise the same op class: COP.

op.c: Perl_newSTATEOP

if (PERLDB_LINE && CopLINE(PL_curcop) && PL_curstash != PL_debstash) { cop->op_type = OP_DBSTATE; cop->op_ppaddr = PL_ppaddr[ OP_DBSTATE ]; } else { cop->op_type = OP_NEXTSTATE; cop->op_ppaddr = PL_ppaddr[ OP_NEXTSTATE ]; }

The above snippet of C code show the only fundamental difference between DBSTATE and NEXTSTATE ops. The op_type field which is largely informational and then the function pointer from PL_ppaddr[...]. Switching between debugging and non-debugging is as simple as loading perl5db.pl and changing all the relevant opcodes. Simple thanks to B::Utils!

Enbugger.pm transforms all nextstate ops into breakpoints

B::Utils::walkallops_filtered( sub { local $@; return eval { return $_[0]->name eq 'nextstate' and $_[0]->stashpv ne 'DB'; }; }, \ &_enbug_cop, );

Enbugger.xs does an ickle bit of magic. The XS safety is lax here but I expect to get only B::COP objects. All B::* objects are just blessed scalars where the integer value is the pointer value. You can easily marshal them back into COP or SVs just by turning the ints into pointers.

void Enbugger_enbug_cop( o ) SV * o PROTOTYPE: $ PREINIT: COP* cop; CODE: cop = INT2PTR( COP*, SvIV( SvRV( o ) ) ); cop->op_type = OP_DBSTATE; cop->op_ppaddr = PL_ppaddr[ OP_DBSTATE ];

Removing the debugger at runtime is just the opposite operation to all the same COP nodes. Change all the nodes where ->name eq 'dbstate' and set cop->op_type = OP_NEXTSTATE; cop->op_ppaddr = PL_ppaddr[ OP_NEXTSTATE ].

Cake's done!

⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Replies are listed 'Best First'.
Re: Enabling or disabliing the debugger at runtime
by diotalevi (Canon) on Jul 16, 2007 at 05:38 UTC

    Here's two nifty recipies for using a debugger at rumtime:

    At any arbitrary moment. Perhaps you've got some process that's long running and you occasionally want to stop it, examine its contents and then tell it to continue. Presumably you'd do the moral equivalent of Enbugger->unimport to remove the debugger.

    $SIG{USR1} = \ &start_debugger; sub start_debugger { require Enbugger; Enbugger->import; }

    Start the debugger on exception.

    $SIG{__DIE__} = sub { require Enbugger; Enbugger->import };

    Please share your ideas. You're smart people, I like using other smart people's ideas!

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

        That requires that perl be started with -d so ops are compiled as potential breakpoints. The point of Enbugger is that you don't have to use -d at all.

        I've also updated the root post to make this a little clearer. The point is to not have to use the -d command line parameter at all.

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://626777]
Approved by skx
Front-paged by liverpole
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (3)
As of 2024-03-19 07:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found