Debugging mod_perl with EPIC

This article describes how to debug mod_perl applications using EPIC and explains some of the current shortcomings. The functionality was tested with EPIC version 0.6.33, mod_perl version 2.0.4, and Apache::DB version 0.14. It is important to note that while the EPIC debugger frontend is fairly stable and debugging mod_perl applications in principle is not very different from debugging remote Perl scripts, at the time of writing there is little experience with EPIC and mod_perl in particular. Furthermore, the procedures described here will appear a little kludgy, as they overload the already existing remote debugging functionality to serve mod_perl debugging. You should expect to invest some time into troubleshooting before you arrive at a working configuration.

Set up Apache::DB without EPIC

The first step is to make sure that the interactive command-line debugger works as expected (without EPIC). You can find a more detailed description of Apache::DB in CPAN and in mod_perl documentation. In the following, I assume that you have an example Apache site configured as follows:

Alias /modperl/ /home/jpl/modperl/
    
PerlModule ModPerl::Registry
<IfDefine PERLDB>
    PerlRequire /home/jpl/modperl/db.pl
</IfDefine>
PerlPostConfigRequire /home/jpl/modperl/startup.pl

<Location /modperl/>
    SetHandler perl-script
    PerlHandler ModPerl::Registry
    <IfDefine PERLDB>
        PerlFixupHandler Apache::DB
    </IfDefine>
    Options +ExecCGI
    PerlSendHeader On
    allow from 127.0.0.1
    deny from all
</Location>

The file /home/jpl/modperl/db.pl referenced in the above configuration contains:

use APR::Pool (); 
use Apache::DB (); 
Apache::DB->init();

The file /home/jpl/modperl/startup.pl contains whatever is needed to initialize the environment for your mod_perl scripts. For example, we should define @INC there:

use lib qw(/home/jpl/modperl/lib);
1;

Apache::DB requires that Apache is started with the -X (single-process) option. I use apache2ctl -k start -X -DPERLDB, which leaves the Apache process running in the foreground and enables the debugging-related directives by defining the PERLDB token. Whenever a script /modperl/*.pl is loaded in the browser, the terminal running apache2ctl gets an interactive debugger prompt. So far, this is all more or less standard mod_perl configuration, no EPIC involved.

Create a "Perl Remote" debug configuration in EPIC

In Eclipse, open the dialog Run/Debug Configurations... to create a launch configuration of type "Perl Remote" with the following settings:

ProjectThe Perl project containing your scripts and modules.
File to executeIt doesn't matter - choose any script from the project.
Local Host IPIt doesn't matter - enter 127.0.0.1.
Target Host Project Installation PathEnter the file system path on the remote machine under which your Perl project is deployed (eg. /home/jpl/modperl). If you are running Apache on the same machine as Eclipse, enter the path displayed in Project Properties as "Location". If you notice that breakpoints are ignored, this is a key setting to tweak, more on that later.
PortThis is the port on which EPIC is going to listen for a connection from the debugger running on the target Apache host. Enter a port number that is not firewalled (in case of doubt, test connectivity with netcat or the like!). Default is 5000.
Capture OutputIt has to be unchecked, as you don't want EPIC to attempt hijacking of STDOUT and STDERR of your scripts!
Create Debug PackageIt should be checked on the first invocation (see below). It may be unchecked later on.
Debug Package File PathEnter the path to a new file that EPIC will create locally when the debugging session is started, for example: /tmp/epicdb.zip. This ZIP file will contain a custom version of perl5db.pl, which must be used instead of the Apache/perl5db.pl in order to support setting breakpoints (it refers to EPIC's epic_breakpoints.pm module, also included in the ZIP file). More on that in the next section.

Run the debug configuration. You should see a Remote Perl Script item in the Debug view (switch to the Debug perspective if necessary). Also, on disk, the ZIP archive mentioned above should now be available. Terminate the debugger session now, as we are not done with adjusting the configuration yet.

Install the EPIC helper modules on the target host

Copy the ZIP archive created in the previous step to the Apache host, create a new directory there and unpack the ZIP archive into it. You will notice that all project files are contained in it. However, you should only leave *epic*.pm and perl5db.pl in the target directory - remove all other files and subdirectories.

Redirect Apache::DB to contact EPIC

Edit your Apache configuration like so:

Alias /modperl/ /home/jpl/modperl/

PerlModule ModPerl::Registry
<IfDefine PERLDB>
    PerlSetEnv PERLDB_OPTS "RemotePort=127.0.0.1:5000 DumpReused ReadLine=0 PrintRet=0"
    PerlSetEnv PERL5DB "BEGIN { $DB::CreateTTY=0; require '/path/to/EPIC/provided/perl5db.pl'; }"
    PerlRequire /home/jpl/modperl/db.pl
</IfDefine>
PerlPostConfigRequire /home/jpl/modperl/startup.pl

<Location /modperl/>
    SetHandler perl-script
    PerlHandler ModPerl::Registry
    <IfDefine PERLDB>
        PerlFixupHandler Apache::DB
    </IfDefine>
    Options +ExecCGI
    PerlSendHeader On
    allow from 127.0.0.1
    deny from all
</Location>

/path/to/EPIC/provided/perl5db.pl should be adjusted to point to where you unpacked the ZIP archive.

RemotePort should be adjusted to include the IP address and port where Eclipse/EPIC are listening for debugger connections.

Edit db.pl to include EPIC helper modules

Add the directory in which perl5db.pl and epic_breakpoints.pm are located to @INC. I suggest that you edit the file db.pl, which was mentioned above:

use lib qw(/path/to/EPIC/provided);
use APR::Pool ();
use Apache::DB ();
Apache::DB->init();

Test debugging without breakpoints

In EPIC Preferences, enable the "suspend at first line" option. Launch the Remote Perl debug configuration created earlier. On the target host, restart Apache in single-process mode. Load one of your scripts in the browser. You should see in EPIC that the debugger suspends in one of the handler modules (eg. Apache::Registry). From here on, you could step through the code to eventually enter your scripts and modules. Resume execution, try reloading the script or loading another script. EPIC should suspend again. If this doesn't work, there is no sense of going further. For troubleshooting, you can enable Perl debugger console in EPIC Preferences.

The proper way to terminate the debugging session is stop the Apache process. EPIC should detect it and terminate the session automatically. If you attempt terminating the session from EPIC, you may get into trouble and may have to kill the Apache process with kill -9 later. In case of problems, make sure that you have no runaway Apache processes. The relative order of starting the EPIC debugging session and the Apache process doesn't matter. Obviously, before you run a script to be debugged by making a request in the browser, both the EPIC debugging session and the Apache process must be running.

Test breakpoints in your own modules

Do not set breakpoints in scripts just yet. Set a breakpoint in one of your own modules and check whether EPIC properly suspends on it. If the breakpoint is ignored, check the file workspace/metadata/.log for warnings like "Could not map local path ... to a remote path". If they appear for the file in which you set breakpoints, you should try adjusting "Target Host Project Installation Path" in the Perl Remote launch configuration. Watch out for possible path variations due to symlinks. Essentially, the path entered in the launch configuration should correspond to the path used by Perl internally to refer to files on the remote host. You can see those internal paths if you enable debugger console in EPIC preferences and step through your code. For example, I had trouble with breakpoints being ignored due to the Perl debugger referring to files as /home/jpl/modperl/hello.pl, while the location displayed in file properties in EPIC was /mnt/data/home/jpl/modperl/hello.pl. The project's include path configured in EPIC and the actual include path on the remote host are also used in mappings. As a general rule, the less the remote host differs from the development machine on which EPIC is running, the better.

Test breakpoints in scripts

Breakpoints in scripts don't work out-of-the-box with mod_perl and EPIC. The reason is that EPIC gets no opportunity to actually set breakpoints in scripts, which are loaded by mod_perl in a custom way and converted dynamically into packages. Fortunately, there is a little trick which allows suspending in scripts. Edit the target script to include $DB::single = 1; immediately before the line on which you want to suspend. Also don't forget to set an EPIC breakpoint as usual, like so:

$DB::single = 1;
print "suspend before print\n"; # set EPIC breakpoint on this line!

The debugger should now suspend on the correct line.

No comments:

Post a Comment