# -*- coding: utf-8 -*- # Copyright (C) 2011 Rocky Bernstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . #TODO: # - Doublecheck handle_pass and other routines. # - can remove signal handler altogether when # ignore=True, print=False, pass=True # # use rlib '../..'; # Manages Signal Handling information for the debugger package Devel::Trepan::SigMgr; use Devel::Trepan::Util; use Exporter; use vars qw(@EXPORT %signo @signame); @EXPORT = qw( lookup_signum lookup_signame %signo @signame); @ISA = qw(Exporter); use warnings; use strict; our %signo; our @signame; use Config; my $i=0; for my $name (split(' ', $Config{sig_name})) { $signo{$name} = $i; $signame[$i] = $name; $i++; } # Find the corresponding signal name for 'num'. Return undef # if 'num' is invalid. sub lookup_signame($) { my $num = shift; $num = abs($num); return undef unless $num < scalar @signame; return $signame[$num]; } # Find the corresponding signal number for 'name'. Return under # if 'name' is invalid. sub lookup_signum($) { my $name = shift; my $uname = uc $name; $uname = substr($uname, 3) if 0 == index($uname, 'SIG'); return $signo{$uname} if exists $signo{$uname}; return undef; } # Return a signal name for a signal name or signal # number. Return undef is $name_num is an int but not a valid signal # number and undef if $name_num is a not number. If $name_num is a # signal name or signal number, the canonic if name is returned. sub canonic_signame($) { my $name_num = shift; my $signum = lookup_signum($name_num); my $signame; unless (defined $signum) { # Maybe signame is a number? if ($name_num =~ /^[+-]?[0-9]+$/) { $signame = lookup_signame($name_num); return undef unless defined($signame); } else { return undef; } return $signame } $signame = uc $name_num; return substr($signame, 3) if 0 == index($signame, 'SIG'); return $signame; } my %FATAL_SIGNALS = ('KILL' => 1, 'STOP' => 1); # I copied these from GDB source code. my %SIGNAL_DESCRIPTION = ( "HUP" => "Hangup", "INT" => "Interrupt", "QUIT" => "Quit", "ILL" => "Illegal instruction", "TRAP" => "Trace/breakpoint trap", "ABRT" => "Aborted", "EMT" => "Emulation trap", "FPE" => "Arithmetic exception", "KILL" => "Killed", "BUS" => "Bus error", "SEGV" => "Segmentation fault", "SYS" => "Bad system call", "PIPE" => "Broken pipe", "ALRM" => "Alarm clock", "TERM" => "Terminated", "URG" => "Urgent I/O condition", "STOP" => "Stopped (signal)", "TSTP" => "Stopped (user)", "CONT" => "Continued", "CHLD" => "Child status changed", "TTIN" => "Stopped (tty input)", "TTOU" => "Stopped (tty output)", "IO" => "I/O possible", "XCPU" => "CPU time limit exceeded", "XFSZ" => "File size limit exceeded", "VTALRM" => "Virtual timer expired", "PROF" => "Profiling timer expired", "WINCH" => "Window size changed", "LOST" => "Resource lost", "USR1" => "User-defined signal 1", "USR2" => "User-defined signal 2", "PWR" => "Power fail/restart", "POLL" => "Pollable event occurred", "WIND" => "WIND", "PHONE" => "PHONE", "WAITING"=> "Process's LWPs are blocked", "LWP" => "Signal LWP", "DANGER" => "Swap space dangerously low", "GRANT" => "Monitor mode granted", "RETRACT"=> "Need to relinquish monitor mode", "MSG" => "Monitor mode data available", "SOUND" => "Sound completed", "SAK" => "Secure attention" ); # Signal Handling information Object for the debugger # - Do we print/not print when signal is caught # - Do we pass/not pass the signal to the program # - Do we stop/not stop when signal is caught # # Parameter dbgr is a Debugger object. ignore is a list of # signals to ignore. If you want no signals, use [] as None uses the # default set. Parameter default_print specifies whether or not we # print receiving a signals that is not ignored. # # All the methods which change these attributes return None on error, or # True/False if we have set the action (pass/print/stop) for a signal # handler. sub new($$$$$$) { my ($class, $handler, $print_fn, $errprint_fn, $secprint_fn, $ignore_list) = @_; # Ignore signal handling initially for these known signals. unless (defined($ignore_list)) { $ignore_list = { 'ALRM' => 1, 'CHLD' => 1, 'URG' => 1, 'IO' => 1, 'CLD' => 1, 'VTALRM' => 1, 'PROF' => 1, 'WINCH' => 1, 'POLL' => 1, 'WAITING' => 1, 'LWP' => 1, 'CANCEL' => 1, 'TRAP' => 1, 'TERM' => 1, 'QUIT' => 1, 'ILL' => 1 }; }; my $self = { handler => $handler, print_fn => $print_fn, errprint_fn => $errprint_fn || $print_fn, secprint_fn => $secprint_fn || $print_fn, sigs => {}, ignore_list => $ignore_list, orig_set_signal => \%SIG, info_fmt => "%-14s%-4s\t%-4s\t%-5s\t%-4s\t%s", }; bless $self, $class; $self->{header} = sprintf($self->{info_fmt}, 'Signal', 'Stop', 'Print', 'Stack', 'Pass', 'Description'); for my $signame (keys %SIG) { initialize_handler($self, $signame); next if $signame eq 'CHLD' || $signame eq 'CLD'; $self->check_and_adjust_sighandler($signame); } $self->action('INT stop print nostack nopass'); # for my $sig ('CHLD', 'CLD') { # $self->action("$sig nostop noprint nostack pass") if exists $SIG{$sig}; # } $self; } sub initialize_handler($$) { my ($self, $sig) = @_; my $signame = canonic_signame($sig); return 0 unless defined($signame); return 0 if exists($FATAL_SIGNALS{$signame}); # try: # except ValueError: # On some OS's (Redhat 8), SIGNUM's are listed (like # SIGRTMAX) that getsignal can't handle. # if (exists($self->{sigs}{$signame})) { # $self->{sigs}->pop($signame); # } my $signum = lookup_signum($signame); my $print_fn = $self->{print_fn}; if (exists($self->{ignore_list}{$signame})) { $self->{sigs}{$signame} = Devel::Trepan::SigHandler->new($print_fn, $signame, $self->{handler}, 0, 0, 1); } else { $self->{sigs}{$signame} = Devel::Trepan::SigHandler->new($print_fn, $signame, $self->{handler}, 1, 0, 0); } return 1; } # Check to see if a single signal handler that we are interested in # has changed or has not been set initially. On return self->{sigs}{$signame} # should have our signal handler. True is returned if the same or adjusted, # False or undef if error or not found. sub check_and_adjust_sighandler($$) { my ($self, $signame) = @_; my $sigs = $self->{sigs}; # try: my $current_handler = $SIG{$signame}; # except ValueError: # On some OS's (Redhat 8), SIGNUM's are listed (like # SIGRTMAX) that getsignal can't handle. #if signame in self.sigs: # sigs.pop(signame) # pass # return None my $sig = $sigs->{$signame}; if (!defined($current_handler) || (defined($sig->{handle}) && $current_handler ne $sig->{handle})) { # if old_handler not in [signal.SIG_IGN, signal.SIG_DFL]: # Save the debugged program's signal handler $sig->{old_handler} = $current_handler if defined $current_handler; # (re)set signal handler the debugger signal handler. # if (exists $sig->{handle}) { $SIG{$signame} = $sig->{handle}; } } return 1; } # Check to see if any of the signal handlers we are interested in have # changed or is not initially set. Change any that are not right. sub check_and_adjust_sighandlers($) { my $self = shift; for my $signame (keys %{$self->{sigs}}) { last unless ($self->check_and_adjust_sighandler($signame)); } } # Print status for a single signal name (signame) sub print_info_signal_entry($$) { my ($self, $signame) = @_; my $description = (exists $SIGNAL_DESCRIPTION{$signame}) ? $SIGNAL_DESCRIPTION{$signame} : ''; my $msg; my $sig_obj = $self->{sigs}{$signame}; if (exists $self->{sigs}{$signame}) { $msg = sprintf($self->{info_fmt}, $signame, bool2YN($sig_obj->{b_stop}), bool2YN($sig_obj->{print_fn}), bool2YN($sig_obj->{print_stack}), bool2YN($sig_obj->{pass_along}), $description); } else { # Fake up an entry as though signame were in sigs. $msg = sprintf($self->{info_fmt}, $signame, 'No', 'No', 'No', 'Yes', $description); } $self->{print_fn}->($msg); } # Print information about a signal sub info_signal($$) { my ($self, $args) = @_; my @args = @$args; my $print_fn = $self->{print_fn}; my $secprint_fn = $self->{secprint_fn}; @args = @signame if (0 == scalar @args); $secprint_fn->($self->{header}); for my $signame (@args) { my $canonic_signame = canonic_signame($signame); if (defined($canonic_signame)) { $self->print_info_signal_entry($canonic_signame); } else { $self->{errprint_fn}->("$signame is not a signal I know about"); } } } # Delegate the actions specified in string $arg to another # method. sub action($$) { my ($self, $arg) = @_; if (!defined($arg)) { $self->info_signal(['handle']); return 1; } my @args = split ' ', $arg; my $signame = canonic_signame(shift @args); return 0 unless defined $signame; if (scalar @args == 0) { $self->info_signal([$signame]); return 1; } # We can display information about 'fatal' signals, but not # change their actions. return 0 if (exists $FATAL_SIGNALS{$signame}); unless (exists $self->{sigs}{$signame}) { return 0 unless $self->initialize_handler($signame); } # multiple commands might be specified, i.e. 'nopass nostop' for my $attr (@args) { my $on = 1; if (0 == index($attr, 'no')) { $on = 0; $attr = substr($attr, 2); } if (0 == index($attr, 'stop')) { $self->handle_stop($signame, $on); } elsif (0 == index($attr, 'print')) { $self->handle_print($signame, $on); } elsif (0 == index($attr, 'pass')) { $self->handle_pass($signame, $on); } elsif (0 == index($attr, 'ignore')) { $self->handle_ignore($signame, $on); } elsif (0 == index($attr, 'stack')) { $self->handle_print_stack($signame, $on); } else { $self->{errprint_fn}->("Invalid argument $attr"); } } $self->check_and_adjust_sighandler($signame); return 1; } # Set whether we stop or not when this signal is caught. # If 'set_stop' is True your program will stop when this signal # happens. sub handle_print_stack($$$) { my ($self, $signame, $print_stack) = @_; $self->{sigs}{$signame}{print_stack} = $print_stack; } # Set whether we stop or not when this signal is caught. # If 'set_stop' is True your program will stop when this signal # happens. sub handle_stop($$$) { my ($self, $signame, $set_stop) = @_; if ($set_stop) { $self->{sigs}{$signame}{b_stop} = 1; # stop keyword implies print AND nopass $self->{sigs}{$signame}{print_fn} = $self->{print_fn}; $self->{sigs}{$signame}{pass_along} = 0; } else { $self->{sigs}{$signame}{b_stop} = 0; } } # Set whether we pass this signal to the program (or not) # when this signal is caught. If set_pass is True, Dbgr should allow # your program to see this signal. sub handle_pass($$$) { my ($self, $signame, $set_pass) = @_; $self->{sigs}{$signame}{pass_along} = $set_pass; if ($set_pass) { # Pass implies nostop $self->{sigs}{$signame}{b_stop} = 0; } } # 'pass' and 'noignore' are synonyms. 'nopass and 'ignore' are # synonyms. sub handle_ignore($$$) { my ($self, $signame, $set_ignore) = @_; $self->handle_pass($signame, !$set_ignore); } # Set whether we print or not when this signal is caught. sub handle_print($$$) { my ($self, $signame, $set_print) = @_; if ($set_print) { $self->{sigs}{$signame}{print_fn} = $self->{print_fn}; } else { $self->{sigs}{$signame}{print_fn} = undef; } } # Store information about what we do when we handle a signal, # # - Do we print/not print when signal is caught # - Do we pass/not pass the signal to the program # - Do we stop/not stop when signal is caught # # Parameters: # signame : name of signal (e.g. SIGUSR1 or USR1) # print_fn routine to use for "print" # stop routine to call to invoke debugger when stopping # pass_along: True is signal is to be passed to user's handler package Devel::Trepan::SigHandler; sub new($$$$$;$$) { my($class, $print_fn, $signame, $handler, $b_stop, $print_stack, $pass_along) = @_; $print_stack = 0 unless defined $print_stack; $pass_along = 1 unless defined $pass_along; my $self = { print_fn => $print_fn, handler => $handler, old_handler => $SIG{$signame}, pass_along => $pass_along, print_stack => $print_stack, signame => $signame, signum => Devel::Trepan::SigMgr::lookup_signum($signame), b_stop => $b_stop, }; bless $self, $class; $self->{handle} = sub{ $self->handle(@_) }; $self; } # This method is called when a signal is received. sub handle { my ($self) = @_; my $signame = $self->{signame}; if (exists($self->{print_fn}) && $self->{print_fn}) { my $msg = sprintf("\nProgram received signal $signame."); $self->{print_fn}->($msg); } # if ($self->{print_stack}) { # import traceback; # my @strings = traceback.format_stack(frame); # for my $s (@strings) { # chomp $s; # $self->{print_fn}->($s); # } # } if ($self->{b_stop}) { $self->{handler}->($signame); } if ($self->{pass_along}) { # pass the signal to the program if ($self->{old_handler}) { $self->{old_handler}->($signame); } } } # When invoked as main program, do some basic tests of a couple of functions unless (caller) { print join(', ', keys %Devel::Trepan::SigMgr::signo), "\n"; print join(', ', sort {$a <=> $b} values %Devel::Trepan::SigMgr::signo), "\n"; for my $i (15, -15, 300) { printf("lookup_signame(%d) => %s\n", $i, Devel::Trepan::SigMgr::lookup_signame($i) || 'undef'); } for my $sig ('term', 'TERM', 'NotThere') { printf("lookup_signum(%s) => %s\n", $sig, Devel::Trepan::SigMgr::lookup_signum($sig) || 'undef'); } for my $i ('15', '-15', 'term', 'sigterm', 'TERM', '300', 'bogus') { printf("canonic_signame(%s) => %s\n", $i, Devel::Trepan::SigMgr::canonic_signame($i) || 'undef'); } my $h; # Is used in myhandler. eval <<'EOE'; # Have to eval else fns defined when caller() is false sub do_action($$$) { my ($h, $arg, $sig) = @_; print "$arg\n"; $h->action($arg); } sub myprint($) { my $msg = shift; print "$msg\n"; } sub orig_sighandler($) { my $name = shift; print "++ Orig Signal $name caught\n"; $h->info_signal(["USR1"]); } sub stop_sighandler($) { my $name = shift; print "++ Stop Signal $name caught\n"; $h->info_signal(["USR1"]); } EOE $SIG{'USR1'} = \&orig_sighandler; $h = Devel::Trepan::SigMgr->new(\&stop_sighandler, \&myprint); $h->info_signal(["TRAP"]); # USR1 is set to known value $h->action('SIGUSR1'); do_action($h, 'usr1 print pass', 'USR1'); $h->info_signal(['USR1']); # noprint implies no stop # do_action($h, 'usr1 noprint'); print '-' x 30, "\n"; kill 10, $$; do_action($h, 'foo nostop'); do_action($h, 'usr1 print nopass', 'USR1'); $h->info_signal(['USR1']); kill 10, $$; # stop keyword implies print do_action($h, 'USR1 stop', 'USR1'); $h->info_signal(['USR2', 'USR1']); kill 10, $$; # h.action('SIGUSR1 noprint') print '-' x 30, "\n"; $h->info_signal([]); # $h->action('SIGUSR1 nopass stack'); # $h->info_signal(['SIGUSR1']); }