File Coverage

lib/Devel/Trepan/SigHandler.pm
Criterion Covered Total %
statement 117 168 69.6
branch 51 90 56.6
condition 12 21 57.1
subroutine 19 25 76.0
pod 0 2 0.0
total 199 306 65.0


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2             # Copyright (C) 2011, 2014-2015 Rocky Bernstein <rocky@gnu.org>
3             #
4             # This program is free software: you can redistribute it and/or modify
5             # it under the terms of the GNU General Public License as published by
6             # the Free Software Foundation, either version 3 of the License, or
7             # (at your option) any later version.
8             #
9             # This program is distributed in the hope that it will be useful,
10             # but WITHOUT ANY WARRANTY; without even the implied warranty of
11             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12             # GNU General Public License for more details.
13             #
14             # You should have received a copy of the GNU General Public License
15             # along with this program. If not, see <http://www.gnu.org/licenses/>.
16              
17             #TODO:
18             # - Doublecheck handle_pass and other routines.
19             # - can remove signal handler altogether when
20             # ignore=True, print=False, pass=True
21             #
22             #
23 3     3   23282 use rlib '../..';
  3         8  
  3         19  
24              
25             # Manages Signal Handling information for the debugger
26             package Devel::Trepan::SigMgr;
27 3     3   1075 use Devel::Trepan::Util;
  3         7  
  3         351  
28 3     3   20 use Exporter;
  3         7  
  3         88  
29 3     3   16 use vars qw(@EXPORT %signo @signame);
  3         6  
  3         198  
30             @EXPORT = qw( lookup_signum lookup_signame %signo @signame);
31             @ISA = qw(Exporter);
32              
33 3     3   17 use warnings; use strict;
  3     3   5  
  3         57  
  3         14  
  3         7  
  3         87  
34              
35             our %signo;
36             our @signame;
37              
38 3     3   13 use Config;
  3         6  
  3         6336  
39              
40             my $i=0;
41             for my $name (split(' ', $Config{sig_name})) {
42             $signo{$name} = $i;
43             $signame[$i] = $name;
44             $i++;
45             }
46              
47              
48             # Find the corresponding signal name for 'num'. Return undef
49             # if 'num' is invalid.
50             sub lookup_signame($)
51             {
52 74     74   29494 my $num = shift;
53 74         142 $num = abs($num);
54 74 100       245 return undef unless $num < scalar @signame;
55 72         235 return $signame[$num];
56             }
57              
58             # Find the corresponding signal number for 'name'. Return under
59             # if 'name' is invalid.
60             sub lookup_signum($)
61             {
62 954     954   3228 my $name = shift;
63 954         1565 my $uname = uc $name;
64 954 100       2364 $uname = substr($uname, 3) if 0 == index($uname, 'SIG');
65 954 100       3486 return $signo{$uname} if exists $signo{$uname};
66 5         16 return undef;
67             }
68              
69             # Return a signal name for a signal name or signal
70             # number. Return undef is $name_num is an int but not a valid signal
71             # number and undef if $name_num is a not number. If $name_num is a
72             # signal name or signal number, the canonic if name is returned.
73             sub canonic_signame($)
74             {
75 287     287   3570 my $name_num = shift;
76 287         532 my $signum = lookup_signum($name_num);
77 287         454 my $signame;
78 287 100       614 unless (defined $signum) {
79             # Maybe signame is a number?
80 4 100       27 if ($name_num =~ /^[+-]?[0-9]+$/) {
81 3         12 $signame = lookup_signame($name_num);
82 3 100       16 return undef unless defined($signame);
83             } else {
84 1         7 return undef;
85             }
86 2         12 return $signame
87             }
88              
89 283         433 $signame = uc $name_num;
90 283 100       624 return substr($signame, 3) if 0 == index($signame, 'SIG');
91 282         825 return $signame;
92             }
93              
94             my %FATAL_SIGNALS = ('KILL' => 1, 'STOP' => 1);
95              
96             # I copied these from GDB source code.
97             my %SIGNAL_DESCRIPTION = (
98             "HUP" => "Hangup",
99             "INT" => "Interrupt",
100             "QUIT" => "Quit",
101             "ILL" => "Illegal instruction",
102             "TRAP" => "Trace/breakpoint trap",
103             "ABRT" => "Aborted",
104             "EMT" => "Emulation trap",
105             "FPE" => "Arithmetic exception",
106             "KILL" => "Killed",
107             "BUS" => "Bus error",
108             "SEGV" => "Segmentation fault",
109             "SYS" => "Bad system call",
110             "PIPE" => "Broken pipe",
111             "ALRM" => "Alarm clock",
112             "TERM" => "Terminated",
113             "URG" => "Urgent I/O condition",
114             "STOP" => "Stopped (signal)",
115             "TSTP" => "Stopped (user)",
116             "CONT" => "Continued",
117             "CHLD" => "Child status changed",
118             "TTIN" => "Stopped (tty input)",
119             "TTOU" => "Stopped (tty output)",
120             "IO" => "I/O possible",
121             "XCPU" => "CPU time limit exceeded",
122             "XFSZ" => "File size limit exceeded",
123             "VTALRM" => "Virtual timer expired",
124             "PROF" => "Profiling timer expired",
125             "WINCH" => "Window size changed",
126             "LOST" => "Resource lost",
127             "USR1" => "User-defined signal 1",
128             "USR2" => "User-defined signal 2",
129             "PWR" => "Power fail/restart",
130             "POLL" => "Pollable event occurred",
131             "WIND" => "WIND",
132             "PHONE" => "PHONE",
133             "WAITING"=> "Process's LWPs are blocked",
134             "LWP" => "Signal LWP",
135             "DANGER" => "Swap space dangerously low",
136             "GRANT" => "Monitor mode granted",
137             "RETRACT"=> "Need to relinquish monitor mode",
138             "MSG" => "Monitor mode data available",
139             "SOUND" => "Sound completed",
140             "SAK" => "Secure attention"
141             );
142              
143              
144             # Signal Handling information Object for the debugger
145             # - Do we print/not print when signal is caught
146             # - Do we pass/not pass the signal to the program
147             # - Do we stop/not stop when signal is caught
148             #
149             # Parameter dbgr is a Debugger object. ignore is a list of
150             # signals to ignore. If you want no signals, use [] as None uses the
151             # default set. Parameter default_print specifies whether or not we
152             # print receiving a signals that is not ignored.
153             #
154             # All the methods which change these attributes return None on error, or
155             # True/False if we have set the action (pass/print/stop) for a signal
156             # handler.
157             sub new($$$$$$)
158             {
159 4     4   489 my ($class, $handler, $print_fn, $errprint_fn, $secprint_fn,
160             $ignore_list) = @_;
161             # Ignore signal handling initially for these known signals.
162 4 50       21 unless (defined($ignore_list)) {
163 4         79 $ignore_list = {
164             'ALRM' => 1,
165             'CHLD' => 1,
166             'URG' => 1,
167             'IO' => 1,
168             'CLD' => 1,
169             'VTALRM' => 1,
170             'PROF' => 1,
171             'WINCH' => 1,
172             'POLL' => 1,
173             'WAITING' => 1,
174             'LWP' => 1,
175             'CANCEL' => 1,
176             'TRAP' => 1,
177             'TERM' => 1,
178             'TSTP' => 1,
179             'QUIT' => 1,
180             'ILL' => 1
181             };
182             };
183              
184 4   66     71 my $self = {
      66        
185             handler => $handler,
186             print_fn => $print_fn,
187             errprint_fn => $errprint_fn || $print_fn,
188             secprint_fn => $secprint_fn || $print_fn,
189             sigs => {},
190             ignore_list => $ignore_list,
191             orig_set_signal => \%SIG,
192             info_fmt => "%-14s%-4s\t%-4s\t%-5s\t%-4s\t%s",
193             };
194              
195 4         15 bless $self, $class;
196              
197 4         58 $self->{header} = sprintf($self->{info_fmt}, 'Signal', 'Stop', 'Print',
198             'Stack', 'Pass', 'Description');
199              
200 4         80 for my $signame (keys %SIG) {
201 272         614 initialize_handler($self, $signame);
202 272 100 100     1081 next if $signame eq 'CHLD' || $signame eq 'CLD';
203 264         540 $self->check_and_adjust_sighandler($signame);
204             }
205 4         41 $self->action('INT stop print nostack nopass');
206              
207             # printing WINCH is annoying, especially in Emacs
208 4         51 $self->action('WINCH nostop noprint nostack pass');
209              
210             # for my $sig ('CHLD', 'CLD') {
211             # $self->action("$sig nostop noprint nostack pass") if exists $SIG{$sig};
212             # }
213 4         37 $self;
214             }
215              
216             sub initialize_handler($$)
217             {
218 272     272   526 my ($self, $sig) = @_;
219 272         492 my $signame = canonic_signame($sig);
220 272 50       591 return 0 unless defined($signame);
221 272 100       593 return 0 if exists($FATAL_SIGNALS{$signame});
222              
223             # try:
224             # except ValueError:
225             # On some OS's (Redhat 8), SIGNUM's are listed (like
226             # SIGRTMAX) that getsignal can't handle.
227             # if (exists($self->{sigs}{$signame})) {
228             # $self->{sigs}->pop($signame);
229             # }
230              
231 264         460 my $signum = lookup_signum($signame);
232 264         480 my $print_fn = $self->{print_fn};
233 264 100       549 if (exists($self->{ignore_list}{$signame})) {
234             $self->{sigs}{$signame} =
235             Devel::Trepan::SigHandler->new($print_fn, $signame,
236 56         138 $self->{handler}, 0, 0, 1);
237             } else {
238             $self->{sigs}{$signame} =
239             Devel::Trepan::SigHandler->new($print_fn, $signame,
240 208         468 $self->{handler}, 1, 0, 0);
241             }
242 264         438 return 1;
243             }
244              
245             # Check to see if a single signal handler that we are interested in
246             # has changed or has not been set initially. On return self->{sigs}{$signame}
247             # should have our signal handler. True is returned if the same or adjusted,
248             # False or undef if error or not found.
249             sub check_and_adjust_sighandler($$)
250             {
251 272     272   472 my ($self, $signame) = @_;
252 272         447 my $sigs = $self->{sigs};
253             # try:
254 272         494 my $current_handler = $SIG{$signame};
255             # except ValueError:
256             # On some OS's (Redhat 8), SIGNUM's are listed (like
257             # SIGRTMAX) that getsignal can't handle.
258             #if signame in self.sigs:
259             # sigs.pop(signame)
260             # pass
261             # return None
262 272         412 my $sig = $sigs->{$signame};
263 272 100 66     992 if (!defined($current_handler) ||
      100        
264             (defined($sig->{handle}) && $current_handler ne $sig->{handle})) {
265             # if old_handler not in [signal.SIG_IGN, signal.SIG_DFL]:
266             # Save the debugged program's signal handler
267 264 100       553 $sig->{old_handler} = $current_handler if defined $current_handler;
268             # (re)set signal handler the debugger signal handler.
269             #
270 264 100       566 if (exists $sig->{handle}) {
271 256         987 $SIG{$signame} = $sig->{handle};
272             }
273             }
274 272         580 return 1;
275             }
276              
277             # Check to see if any of the signal handlers we are interested in have
278             # changed or is not initially set. Change any that are not right.
279             sub check_and_adjust_sighandlers($)
280             {
281 0     0   0 my $self = shift;
282 0         0 for my $signame (keys %{$self->{sigs}}) {
  0         0  
283 0 0       0 last unless ($self->check_and_adjust_sighandler($signame));
284             }
285             }
286              
287             # Print status for a single signal name (signame)
288             sub print_info_signal_entry($$)
289             {
290 0     0   0 my ($self, $signame) = @_;
291             my $description = (exists $SIGNAL_DESCRIPTION{$signame}) ?
292 0 0       0 $SIGNAL_DESCRIPTION{$signame} : '';
293 0         0 my $msg;
294 0         0 my $sig_obj = $self->{sigs}{$signame};
295 0 0       0 if (exists $self->{sigs}{$signame}) {
296             $msg = sprintf($self->{info_fmt}, $signame,
297             bool2YN($sig_obj->{b_stop}),
298             bool2YN($sig_obj->{print_fn}),
299             bool2YN($sig_obj->{print_stack}),
300 0         0 bool2YN($sig_obj->{pass_along}),
301             $description);
302             } else {
303             # Fake up an entry as though signame were in sigs.
304 0         0 $msg = sprintf($self->{info_fmt}, $signame,
305             'No', 'No', 'No', 'Yes', $description);
306             }
307 0         0 $self->{print_fn}->($msg);
308             }
309              
310             # Print information about a signal
311             sub info_signal($$)
312             {
313 0     0   0 my ($self, $args) = @_;
314 0         0 my @args = @$args;
315 0         0 my $print_fn = $self->{print_fn};
316 0         0 my $secprint_fn = $self->{secprint_fn};
317 0 0       0 @args = @signame if (0 == scalar @args);
318 0         0 $secprint_fn->($self->{header});
319 0         0 for my $signame (@args) {
320 0         0 my $canonic_signame = canonic_signame($signame);
321 0 0       0 if (defined($canonic_signame)) {
322 0         0 $self->print_info_signal_entry($canonic_signame);
323             } else {
324 0         0 $self->{errprint_fn}->("$signame is not a signal I know about");
325             }
326             }
327             }
328              
329             # Delegate the actions specified in string $arg to another
330             # method.
331             sub action($$)
332             {
333 8     8   28 my ($self, $arg) = @_;
334 8 50       30 if (!defined($arg)) {
335 0         0 $self->info_signal(['handle']);
336 0         0 return 1;
337             }
338 8         50 my @args = split ' ', $arg;
339 8         29 my $signame = canonic_signame(shift @args);
340 8 50       30 return 0 unless defined $signame;
341              
342 8 50       34 if (scalar @args == 0) {
343 0         0 $self->info_signal([$signame]);
344 0         0 return 1;
345             }
346              
347             # We can display information about 'fatal' signals, but not
348             # change their actions.
349 8 50       29 return 0 if (exists $FATAL_SIGNALS{$signame});
350              
351 8 50       28 unless (exists $self->{sigs}{$signame}) {
352 0 0       0 return 0 unless $self->initialize_handler($signame);
353             }
354              
355             # multiple commands might be specified, i.e. 'nopass nostop'
356 8         27 for my $attr (@args) {
357 32         50 my $on = 1;
358 32 100       87 if (0 == index($attr, 'no')) {
359 20         36 $on = 0;
360 20         46 $attr = substr($attr, 2);
361             }
362 32 100       126 if (0 == index($attr, 'stop')) {
    100          
    100          
    50          
    50          
363 8         31 $self->handle_stop($signame, $on);
364             } elsif (0 == index($attr, 'print')) {
365 8         33 $self->handle_print($signame, $on);
366             } elsif (0 == index($attr, 'pass')) {
367 8         26 $self->handle_pass($signame, $on);
368             } elsif (0 == index($attr, 'ignore')) {
369 0         0 $self->handle_ignore($signame, $on);
370             } elsif (0 == index($attr, 'stack')) {
371 8         35 $self->handle_print_stack($signame, $on);
372             } else {
373 0         0 $self->{errprint_fn}->("Invalid argument $attr");
374             }
375             }
376 8         27 $self->check_and_adjust_sighandler($signame);
377 8         21 return 1;
378              
379             }
380              
381             # Set whether we stop or not when this signal is caught.
382             # If 'set_stop' is True your program will stop when this signal
383             # happens.
384             sub handle_print_stack($$$)
385             {
386 8     8   26 my ($self, $signame, $print_stack) = @_;
387 8         24 $self->{sigs}{$signame}{print_stack} = $print_stack;
388             }
389              
390             # Set whether we stop or not when this signal is caught.
391             # If 'set_stop' is True your program will stop when this signal
392             # happens.
393             sub handle_stop($$$)
394             {
395 8     8   21 my ($self, $signame, $set_stop) = @_;
396 8 100       26 if ($set_stop) {
397 4         15 $self->{sigs}{$signame}{b_stop} = 1;
398             # stop keyword implies print AND nopass
399 4         14 $self->{sigs}{$signame}{print_fn} = $self->{print_fn};
400 4         13 $self->{sigs}{$signame}{pass_along} = 0;
401             } else {
402 4         14 $self->{sigs}{$signame}{b_stop} = 0;
403             }
404             }
405              
406             # Set whether we pass this signal to the program (or not)
407             # when this signal is caught. If set_pass is True, Dbgr should allow
408             # your program to see this signal.
409             sub handle_pass($$$)
410             {
411 8     8   21 my ($self, $signame, $set_pass) = @_;
412 8         20 $self->{sigs}{$signame}{pass_along} = $set_pass;
413 8 100       30 if ($set_pass) {
414             # Pass implies nostop
415 4         14 $self->{sigs}{$signame}{b_stop} = 0;
416             }
417             }
418              
419             # 'pass' and 'noignore' are synonyms. 'nopass and 'ignore' are
420             # synonyms.
421             sub handle_ignore($$$)
422             {
423 0     0   0 my ($self, $signame, $set_ignore) = @_;
424 0         0 $self->handle_pass($signame, !$set_ignore);
425             }
426              
427             # Set whether we print or not when this signal is caught.
428             sub handle_print($$$)
429             {
430 8     8   25 my ($self, $signame, $set_print) = @_;
431 8 100       24 if ($set_print) {
432 4         20 $self->{sigs}{$signame}{print_fn} = $self->{print_fn};
433             } else {
434 4         14 $self->{sigs}{$signame}{print_fn} = undef;
435             }
436             }
437              
438             # Store information about what we do when we handle a signal,
439             #
440             # - Do we print/not print when signal is caught
441             # - Do we pass/not pass the signal to the program
442             # - Do we stop/not stop when signal is caught
443             #
444             # Parameters:
445             # signame : name of signal (e.g. SIGUSR1 or USR1)
446             # print_fn routine to use for "print"
447             # stop routine to call to invoke debugger when stopping
448             # pass_along: True is signal is to be passed to user's handler
449             package Devel::Trepan::SigHandler;
450              
451             sub new($$$$$;$$)
452             {
453 264     264 0 540 my($class, $print_fn, $signame, $handler,
454             $b_stop, $print_stack, $pass_along) = @_;
455              
456 264 50       557 $print_stack = 0 unless defined $print_stack;
457 264 50       520 $pass_along = 1 unless defined $pass_along;
458              
459             my $self = {
460             print_fn => $print_fn,
461             handler => $handler,
462 264         698 old_handler => $SIG{$signame},
463             pass_along => $pass_along,
464             print_stack => $print_stack,
465             signame => $signame,
466             signum => Devel::Trepan::SigMgr::lookup_signum($signame),
467             b_stop => $b_stop,
468             };
469 264         521 bless $self, $class;
470 264     0   953 $self->{handle} = sub{ $self->handle(@_) };
  0         0  
471 264         732 $self;
472             }
473              
474             # This method is called when a signal is received.
475             sub handle
476             {
477 0     0 0   my ($self) = @_;
478 0           my $signame = $self->{signame};
479 0 0 0       if (exists($self->{print_fn}) && $self->{print_fn}) {
480 0           my $msg = sprintf("\ntrepan.pl: Program received signal $signame.");
481 0           $self->{print_fn}->($msg);
482             }
483              
484             # if ($self->{print_stack}) {
485             # import traceback;
486             # my @strings = traceback.format_stack(frame);
487             # for my $s (@strings) {
488             # chomp $s;
489             # $self->{print_fn}->($s);
490             # }
491             # }
492              
493 0 0         if ($self->{b_stop}) {
494 0           $self->{handler}->($signame);
495             }
496              
497 0 0         if ($self->{pass_along}) {
498             # pass the signal to the program
499 0 0         if ($self->{old_handler}) {
500 0 0         if (ref($self->{old_handler})) {
    0          
501 0           $self->{old_handler}->($signame);
502             } elsif ($self->{old_handler}) {
503 0 0 0       eval {$self->{old_handler}($signame)}; warn $@ if $@ and $^W;
  0            
  0            
504             }
505             } else {
506             # Set default and reraise
507 0 0         if ($signame eq 'TSTP') {
508             # in principle, SIGSTOP cannot be trapped.
509             # This also might not work on Windows
510 0           return kill 'STOP', $$;
511             } else {
512 0           $SIG{$signame} = 'DEFAULT';
513 0           kill $signame, $$;
514             }
515             # $SIG{$signame} = $self->{handle};
516             }
517             }
518             }
519              
520             # When invoked as main program, do some basic tests of a couple of functions
521             unless (caller) {
522             print join(', ', keys %Devel::Trepan::SigMgr::signo), "\n";
523             print join(', ', sort {$a <=> $b} values %Devel::Trepan::SigMgr::signo), "\n";
524             for my $i (15, -15, 300) {
525             printf("lookup_signame(%d) => %s\n", $i,
526             Devel::Trepan::SigMgr::lookup_signame($i) || 'undef');
527             }
528              
529             for my $sig ('term', 'TERM', 'NotThere') {
530             printf("lookup_signum(%s) => %s\n", $sig,
531             Devel::Trepan::SigMgr::lookup_signum($sig) || 'undef');
532             }
533              
534             for my $i ('15', '-15', 'term', 'sigterm', 'TERM', '300', 'bogus') {
535             printf("canonic_signame(%s) => %s\n", $i,
536             Devel::Trepan::SigMgr::canonic_signame($i) || 'undef');
537             }
538              
539             my $h; # Is used in myhandler.
540             eval <<'EOE'; # Have to eval else fns defined when caller() is false
541             sub do_action($$$) {
542             my ($h, $arg, $sig) = @_;
543             print "$arg\n";
544             $h->action($arg);
545             }
546             sub myprint($) {
547             my $msg = shift;
548             print "$msg\n";
549             }
550             sub orig_sighandler($) {
551             my $name = shift;
552             print "++ Orig Signal $name caught\n";
553             $h->info_signal(["USR1"]);
554             }
555             sub stop_sighandler($) {
556             my $name = shift;
557             print "++ Stop Signal $name caught\n";
558             $h->info_signal(["USR1"]);
559             }
560             EOE
561              
562             $SIG{'USR1'} = \&orig_sighandler;
563             $h = Devel::Trepan::SigMgr->new(\&stop_sighandler, \&myprint);
564             $h->info_signal(["TRAP"]);
565             # USR1 is set to known value
566             $h->action('SIGUSR1');
567              
568             do_action($h, 'usr1 print pass', 'USR1');
569             $h->info_signal(['USR1']);
570             # noprint implies no stop
571             # do_action($h, 'usr1 noprint');
572             print '-' x 30, "\n";
573             kill 10, $$;
574             do_action($h, 'foo nostop');
575             do_action($h, 'usr1 print nopass', 'USR1');
576             $h->info_signal(['USR1']);
577             kill 10, $$;
578             # stop keyword implies print
579             do_action($h, 'USR1 stop', 'USR1');
580             $h->info_signal(['USR2', 'USR1']);
581             kill 10, $$;
582             # h.action('SIGUSR1 noprint')
583             print '-' x 30, "\n";
584             $h->info_signal([]);
585             # $h->action('SIGUSR1 nopass stack');
586             # $h->info_signal(['SIGUSR1']);
587             }