You stated you don't want to introduce signal handling code but a signal handler could actually be the least intrusive approach.
Once per process, like in the code that runs in each freshly forked child, run something like:
use Carp ();
$SIG{'QUIT'} = \&Carp::confess;
It would be unusual for other code employing signals to use this particular signal for other purposes.
Then, next time you find a looping process, kill it with SIGQUIT.
A bash commandline to kill the process with PID 12345 could be:
kill -QUIT 12345
The misbehaving instance of your program will die after dumping the stack trace on STDERR.