Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Unraveling Code with the Debugger
by Daniel Allen | Pages: 1, 2, 3

The next feature, which goes along with n, is s, for single-stepping into a function. s will replace the current running context with that context of the subroutine. After one s, you're within TWiki::initialize:

  DB<4> s
TWiki::initialize(/usr/share/perl5/TWiki.pm:333):
333:        my ( $thePathInfo, $theRemoteUser, $theTopic, 
                               $theUrl, $theQuery ) = @_;

The description line has changed, showing that you're in a new package, function, and file. Press Enter; note that s also repeats with Enter. Three more repeats will get you two levels deeper:

  DB<4>
TWiki::initialize(/usr/share/perl5/TWiki.pm:336):
336:            basicInitialize();
  DB<4>
TWiki::basicInitialize(/usr/share/perl5/TWiki.pm:490):
490:        setupLocale();
  DB<4>
TWiki::setupLocale(/usr/share/perl5/TWiki.pm:515):
515:        $siteCharset = 'ISO-8859-1';        
               # Default values if locale mis-configured
  DB<4>

Now you are deep inside the TWiki module. Imagine tracking this program execution by hand; it would be considerably more tedious. Instead, you have the sometimes challenging task of figuring out where the program has taken you. Fortunately, the debugger has tools for making this easier. Note that the indenting isn't any judge of the execution depth; to find that, use T for trace:

  DB<1> T
. = TWiki::setupLocale() called from file 
    `/usr/share/perl5/TWiki.pm' line 490
. = TWiki::basicInitialize() called from file 
    `/usr/share/perl5/TWiki.pm' line 336
@ = TWiki::initialize('', undef, undef, 'http://localhost/view', 
    ref(CGI)) called from file `view' line 45

The backtrace has the following format: the first character is the calling context; ., $, and @ signify void, scalar, and list contexts respectively. Next is function name, including the arguments passed, if any. Finally comes the calling filename and line number. These lines go from deepest to shallowest execution depth.

Perhaps you went too deep. Use r to return from a function.

  DB<8> r
void context return from TWiki::setupLocale
TWiki::basicInitialize(/usr/share/perl5/TWiki.pm:491):
491:        setupRegexes();

Here you can see that r shows the return value and its context. This can be quite useful, as the calling context can occasionally surprise even experienced programmers. With inspection, you can verify that this output makes sense compared to the trace above, because it's one line beyond the caller from the first line of that trace.

Use r again twice, to get back to the top. Now it's clear that initialize() returns a list to the caller:

  DB<1> r
list context return from TWiki::initialize:
0  'WebHome'
1  'Main'
2  '/cgi-bin/twiki'
3  'guest'
4  '/var/lib/twiki/data'
main::(view:49):        TWiki::UI::View::view( $webName, 
                            $topic, $userName, $query );

If you were tracking the initialization routine, you might remind yourself what the caller was doing with the command - to show the calling line again; but that's not necessary now, so continue!

Take a step back. You know you're looking for the function TWiki::getPublicWebList. You can take a shortcut to get there. The debugger command c can take arguments, either line numbers or subroutine names.

  DB<5> c TWiki::getPublicWebList
TWiki::getPublicWebList(/usr/share/perl5/TWiki.pm:2559):
2559:       if( ! @publicWebList ) {
  DB<6>

As you can see, you've switched modules again, into TWiki.pm. Take a look around:

  DB<3> l
2559==>     if( ! @publicWebList ) {
2560            # build public web list, e.g. exclude hidden 
                # webs, but include current web
2561:           my @list = &TWiki::Store::getAllWebs( "" );
2562:           my $item = "";
2563:           my $hidden = "";
2564:           foreach $item ( @list ) {
2565:               $hidden = 
  &TWiki::Prefs::getPreferencesValue( "NOSEARCHALL", $item );
2566                # exclude topics that are hidden or start 
                    # with . or _ unless current web
2567:               if( ( $item eq $TWiki::webName  ) || 
                        ( ( ! $hidden ) && ( $item =~ 
                                      /^[^\.\_]/ ) ) ) {
2568:                   push( @publicWebList, $item );

This looks promising. c 2562 and then x @list to see that there are five list items.

  DB<15> c 2562
TWiki::getPublicWebList(/usr/share/perl5/TWiki.pm:2562):
2562:           my $item = "";
  DB<16> x @list
0  'Main'
1  'Sandbox'
2  'TWiki'
3  'Trash'
4  '_default'
  DB<17> n

As you can see, the foreach() in line 2,564 will identify which of these are public. Getting warmer! You can n several times to see the program flow. (Remember, after the first, you can press Enter.) If you run other commands, Enter still keeps its shortcut of n or s. If you x $hidden after you've passed line 2,565 in each loop, you will see that they contain the empty string because NOSEARCHALL is unset.

The expression in the following line tests the truth of $hidden as per the comment.

Here is the solution. In my team's original problem, we changed NOSEARCHALL from Yes to No. The string No certainly is still true. It took me a few runs through the code, testing values each time, to find it.

When I changed NOSEARCHALL to "" from Yes in the config file, the hidden web immediately became unhidden. Problem solved. The entire process took no more than 45 minutes. Considering the number of lines of code in TWiki and its relative complexity, that isn't bad.

Results

Yes, the problem was that we overlooked something "obvious." True, I didn't actually debug the code; I debugged my configuration. No matter. This is the way of a lot of debugging; the problem isn't where you expect, and it's obvious in retrospect. I hope with this simple example, I've shown how the debugger can tell you things you may not notice by code inspection or reading documentation.

This article has barely scratched the surface of the debugger's capabilities; it can also do such things as automatically insert code that runs whenever you step forward, or automatically stop execution when an expression becomes true. You can insert lines of code into the program that exist only in the debugging instance. You can record your actions and play them back in other debugging sessions; or customize the debugger with your own preferences and aliases. There are also methods to run a debugger GUI on a remote server, and modules to harness debugging mod_perl and other programs that don't run on the command line. Please see the references for more information about these features.

References: Introductory Debugger Information Online

The Perl Debugger. Linux Journal. March 2005, 73-76

perldoc perldebtut

perldoc perldebug

Perl Debugger Quick Reference