- Overview
- System Requirements
- Safeguards and Limitations
- Signals
- FAQ
- It doesn't work; it just says "Attaching to process". What gives?
- On OSX it times out after saying "Unable to find Mach task port for process-id ___"
- I want to inject something that changes my running program's state. Can I?
- I want to inject code into multiple places inside a process. Can I?
- Why not just use the Perl debugger/GDB directly?
- Why use FIFOs, and not use perl debugger's RemotePort functionality?
- Additional Resources
inject.pl
is the legacy version of the gdb-inject-perl
tool; it is no longer maintained and has many limitations that the current version of the tool does not.
I recommend you use the current version, which is available at https://github.com/zbentley/gdb-inject-perl
gdb-inject-perl is a script that uses GDB to attach to a running Perl process, and execute code inside that process. It works by using the debugger to inject a Perl eval
call with a string of code supplied by the user (it defaults to code that prints out the Perl call stack). If everything goes as planned, the Perl process in question will run that code in the middle of whatever else it is doing.
- First, identify the PID of the Perl process that you want to debug. In the below examples, it's a backgrounded process created at the top.
- Ensure you are running as a user with permissions to attach to the PID in question (either the user that owns the process or root, usually).
# Run something in the background that has a particular call stack:
~> perl -e 'sub Foo {
my $stuff = shift; eval $stuff;
}
sub Bar {
Foo(@_);
};
eval {
Bar("while (1) { sleep 1; }");
};' &
[1] 1234
# Inject code into the backgrounded process
~> inject.pl --pid 1234
INJECT at (eval 1) line 1.
eval 'while (1) { sleep 1; }
;' called at -e line 1
main::Foo(undef) called at -e line 1
main::Bar('while (1) { sleep 1; }') called at -e line 1
eval {...} called at -e line 1
~> inject.pl --pid 1234 --code 'print STDERR qq{FOOO $$}; sleep 1;'
FOOO 1234 # printed from other process
~> inject.pl --pid <SOMEPID> --code 'print $fh STDERR qq{FOOO $$}; sleep 1;'
FOOO 6789 # printed from gdb-inject-perl
--pid PID
- Process ID of the Perl process to inject code into.
PID
can be any kind of Perl process: embedded, mod_perl, simple script, etc. This option is required.
- Process ID of the Perl process to inject code into.
--code CODE
: String of code that will be injected into the Perl process atPID
and run. This code will have access to a special file handle,$fh
, which connects it toinject.pl
. When$fh
is written to, the output will be returned byinject.pl
. IfCODE
is omitted, it defaults to printing the value of Carp::longmess to$fh
.CODE
should not perform complex alterations or change the state of the program being attached to.CODE
may not contain double quotation marks or Perl code that does not compile with strict and warnings. To bypass these restrictions, use--force
.--force
- Bypass sanity checks and restrictions on the content of
CODE
.
- Bypass sanity checks and restrictions on the content of
--[no]signals
- Enable or disable the option to send signals to the process at
PID
. If--signals
is enabled, onceinject.pl
has injected code into the process atPID
, the user will be prompted to send signals toPID
in order to interrupt any blocking system calls and forceCODE
to be run. See "Signals" for more info. Defaults to enabled. If Term::ReadKey is not installed on your system, disabling signals via--nosignals
bypasses thie requirement for that module.
- Enable or disable the option to send signals to the process at
--timeout SECONDS
- Number of seconds to wait until
PID
runsCODE
. If the timeout is exceeded (usually becausePID
is in the middle of a blocking system call),inject.pl
gives up. Defaults to 5.
- Number of seconds to wait until
--verbose
- Show all GDB output in addition to values captured from the process at
PID
.
- Show all GDB output in addition to values captured from the process at
--help
- Show help message.
--man
- Show manpage/perldoc.
This program only works on POSIX-like OSes on which GDB is installed. In practice, this includes most Linuxes, BSDs, and Solaris OSes out of the box. GDB can be installed on OSX and other operating systems as well.
- It works on scripts.
- It works on mod_perl processes.
- It works on other CGI Perls inside webservers.
- It works on (many/most) embedded Perls.
Just pass it the process ID of a Perl process and it will do its best to inject code.
It's incredibly dangerous.
The script works by injecting arbitrary function calls into the runtime of a complex, high-level programming language (Perl). Even if the code you inject doesn't modify anything, it might be injected in the wrong place, and corrupt internal interpreter state. If it does modify anything, the interpreter might not detect state changes correctly.
In short, it should not be used on a healthy process with important functionality that could be interrupted. "Interrupted", in this case, does not mean the same thing as a signal interrupt (Perl-safe or unsafe); it's possible to break/segfault/corrupt Perl in the midst of operations that would not normally be interruptible at all. gdb-inject-perl tries to mimic safe-signal delivery behavior, but does not do so perfectly.
gdb-inject-perl is recommended for use on processes that are already known to be deranged, and that are soon to be killed.
If a Perl process is stuck, broken, or otherwise malfunctioning, and you want more information than logs, /proc
, lsof
, strace
, or any of the other standard black-box debugging utilities can give you, you can use gdb-inject-perl to get more information.
- POSIX-ish OS.
- Modern Perl (5.6 or later, theoretically; 5.8.8 or later in practice).
- GDB installed.
- The following non-core CPAN modules:
- The following core CPAN modules (these are almost certainly installed with your Perl distribution):
There are a few basic safeguards used by gdb-inject-perl.
- Code that will not compile with
strict
andwarnings
will be rejected. You can use the--force
switch to run it anyway (at your own risk).- Warning: "Will it compile?" is checked using
perl -c
, which will runBEGIN
andEND
blocks. Such blocks will be executed during the pre-injection compilation check. Besides, if code you plan on injecting into an already-running Perl process hasBEGIN
orEND
blocks, it's probably a bad idea.
- Warning: "Will it compile?" is checked using
- Code containing literal double quotation marks, even backslash-escaped ones, will be rejected. You can use the
--force
switch to run it anyway (at your own risk).- This restriction is imposed because code must be supplied as a string argument into a GDB call. You can work around it by using the alternative quoting constructs in Perl, e.g.
$interpolated = qq{var: $var}; $not_interpolated = q{var: $var}
.
- This restriction is imposed because code must be supplied as a string argument into a GDB call. You can work around it by using the alternative quoting constructs in Perl, e.g.
- If
gdb
cannot be found on your system, the script will not start. Ifgdb
is installed in a nonstandard location, set theGDB
environment variable to its path before invoking the injector. For example:GDB=/path/to/gdb perl inject.pl [options]
.
Sometimes, code is injected into a target process and not run. This is often because the target process is in the middle of a blocking system call (e.g. sleep
). In those situations, it is often useful to interrupt that system call by sending the target process a signal. To facilitate this, when target processes do not run injected code within a small amount of time, inject.pl
prompts the user on the command line to send a signal (by name or number) to the target process, e.g.:
~> inject.pl --pid 1234
[inject.pl] Press a number key to send a signal to 1234. Press 'l' or 'L' to list signals.
int
[inject.pl] SIGINT sent to 1234
# Signals can also be entered by number:
[inject.pl] Press a number key to send a signal to 1234. Press 'l' or 'L' to list signals.
15
[inject.pl] SIGTERM sent to 1234
Signals can be entered by number or name, case-insensitive. Pressing "L" triggers a listing of signals, similar to the behavior of kill -l
.
Note: the behavior of a target process after it has been signalled is even more unknown than its behavior when running injected code without signals. While inject.pl
tries to run the injected code before a process shuts down, signalling a target process often results in its termination immediately after running CODE
. Also, since inject.pl
uses the target process's internal Perl signal handling check as the attach point for the injected code, it is not guaranteed that any internal (safe or unsafe) signal handlers already installed in the target process will run when it is signalled by inject.pl
.
Your process is probably in a blocking system call or uninterruptible state (doing something other than just running Perl code). You can send it a signal and it might wake up and run your injected code. See signals for more info. If you don't want to use signals, try strace
and friends.
You need to codesign the debugger.
Sure, but don't come crying to me when it segfaults your application.
Probably, but if you do, don't tell me how you pulled it off. It sounds like you need a real[1] debugger[2].
- You might not need it. gdb-inject-perl is intended for a much, much simpler use case than the Perl debugger (or the excellent Devel::Trepan): getting a little bit of context information out of a process that you might not know anything about.
- Simplicity is paramount: the person monitoring and/or killing a Perl process might not know how to use the Perl debugger; they might not know what Perl is. Consider the example of a support technician or administrator that finds a process that is hung and breaking an important service: with gdb-inject-perl, they can run a command, send its output to the developers that maintain the service, and kill it as the normally would: no Perl understanding required.
- Debug symbols/Perl debugger support might not exist in your environment (certain embedded Perls, or bizarre system Perls). Even in those cases, the "caller" stack is usable for context information about a Perl process, and gdb-inject-perl can get it for you.
Something else might be using it. gdb-inject-perl is meant to be usable with minimal interference with other code running in a Perl process, even other debuggers.