#!/usr/bin/perl # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" # # # ctfstabs requires an object file with CTF data, and a file containing # directives which indicate the types and members for which offsets are to # be generated. The developer provides a single input file containing both # the #include directives used to generate the object file as well as the # offset directives. This script automates the splitting of the master file, # the generation of the object file, the invocation of ctfstabs, and cleanup. # use strict; use warnings; use File::Basename; use Getopt::Std; use POSIX qw(:sys_wait_h); # Globals. our $PROGNAME = basename($0); our ($CTmp, $OTmp, $GenTmp, $GenPPTmp, $Keep, $Verbose); our ($SizeofTmp, $SizeofExe); # Hammerhead: sizeof helper temps sub usage { print STDERR "Usage: $PROGNAME [-k] [-s ctfstabs] [-r ctfconvert] ", "compiler [options]\n"; print STDERR " NOTE: compiler options must enable stabs or DWARF as ", "appropriate\n"; exit(2); } sub cleanup { return if ($Keep); unlink($CTmp) if (defined $CTmp && -f $CTmp); unlink($OTmp) if (defined $OTmp && -f $OTmp); unlink($GenTmp) if (defined $GenTmp && -f $GenTmp); unlink($GenPPTmp) if (defined $GenPPTmp && -f $GenPPTmp); # Hammerhead: Clean up sizeof helper files unlink($SizeofTmp) if (defined $SizeofTmp && -f $SizeofTmp); unlink($SizeofExe) if (defined $SizeofExe && -f $SizeofExe); } sub bail { print STDERR "$PROGNAME: ", join(" ", @_), "\n"; cleanup(); exit(1); } sub findprog { my ($arg, $name, $default) = @_; if (defined $arg) { return ($arg); } elsif (defined $ENV{$name}) { return ($ENV{$name}); } else { return ($default); } } sub runit { my (@argv) = @_; my $rc; if ($Verbose) { print STDERR "+ @argv\n"; } if ((my $rc = system(@argv)) == -1) { bail("Failed to execute $argv[0]: $!"); } elsif (WIFEXITED($rc)) { $_ = WEXITSTATUS($rc); if ($_ == 0) { return; } else { bail("$argv[0] failed with status $_"); } } elsif (WSIGNALLED($rc)) { $_ = WTERMSIG($rc); # WCOREDUMP isn't a POSIX macro, do it the non-portable way. if ($rc & 0x80) { bail("$argv[0] failed with signal $_ (core dumped)"); } else { bail("$argv[0] failed with signal $_"); } } } # # Main. # my %opts; getopts("kr:s:v", \%opts) || usage(); usage() if (@ARGV < 1); my $ctfstabs = findprog($opts{"s"}, "CTFSTABS", "ctfstabs"); my $ctfconvert = findprog($opts{"r"}, "CTFCONVERT", "ctfconvert"); $Keep = $opts{k}; $Verbose = $opts{k} || $opts{v}; my ($cc, @cflags) = @ARGV; $CTmp = "ctfstabs.tmp.$$.c"; # The C file used to generate CTF $OTmp = "ctfstabs.tmp.$$.o"; # Object file with CTF $GenTmp = "ctfstabs.tmp.$$.gen.c"; # genassym directives $GenPPTmp = "ctfstabs.tmp.$$.genpp"; # Post-processed genassym directives $SizeofTmp = "ctfstabs.tmp.$$.sizeof.c"; # Hammerhead: sizeof helper C file $SizeofExe = "ctfstabs.tmp.$$.sizeof"; # Hammerhead: sizeof helper executable my ($cfile, $genfile); open($cfile, '>', $CTmp) || bail("failed to create $CTmp: $!"); open($genfile, '>', $GenTmp) || bail("failed to create $GenTmp: $!"); if ($Verbose) { print STDERR "Splitting from stdin to $CTmp and $GenTmp\n"; } # Hammerhead: Collect type names to force them into debug info my @typenames; # Hammerhead: Track types that only need sizeof (no member offsets) # Format: { typename => SIZEOF_MACRO } my %sizeof_only_types; my @input_lines; # Save all input lines for parsing # Hammerhead: Add __EXTENSIONS__ to enable all type definitions (e.g., sigjmp_buf) print $cfile "#define __EXTENSIONS__ 1\n"; while () { push @input_lines, $_; # #includes go to the C file. All other preprocessor directives # go to both the C file and the offsets input file. Anything # that's not a preprocessor directive goes into the offsets input # file. Also strip comments from the genfile, as they can confuse # the preprocessor. if (/^#include/) { print $cfile $_; } elsif (/^#/) { print $cfile $_; print $genfile $_; } elsif (/^\\#/) { print $genfile $_; } elsif (!/^\\/) { # Hammerhead: Preserve blank lines using a marker that survives preprocessing. # ctfstabs uses blank lines to separate type stanzas. if (/^\s*$/) { print $genfile "__GENOFFSETS_BLANK__\n"; } else { print $genfile $_; # Hammerhead: Capture type names (lines starting with non-whitespace # that aren't preprocessor directives). Type names are at the start # of lines, optionally followed by a size macro name. if (/^([A-Za-z_][A-Za-z0-9_]*)(\s|$)/) { push @typenames, $1; } } } } # Hammerhead: Identify types that only need sizeof (no member fields following). # These are types followed by a blank line or EOF, with only a size macro. # Parse the input to find patterns like: # typename SIZEOF_TYPENAME # for (my $i = 0; $i < @input_lines; $i++) { my $line = $input_lines[$i]; # Look for type lines with a size macro: "typename SIZEOF_*" if ($line =~ /^([A-Za-z_][A-Za-z0-9_]*)\s+(SIZEOF_[A-Za-z0-9_]+)\s*$/) { my ($typename, $sizemacro) = ($1, $2); # Check if next non-comment, non-preprocessor line is blank or starts a new type my $next_is_member = 0; for (my $j = $i + 1; $j < @input_lines; $j++) { my $nextline = $input_lines[$j]; next if $nextline =~ /^\\/ || $nextline =~ /^#/; # Skip comments/preprocessor if ($nextline =~ /^\s+\S/) { # Indented line = member field $next_is_member = 1; } last; # Only check first relevant line } if (!$next_is_member) { $sizeof_only_types{$typename} = $sizemacro; if ($Verbose) { print STDERR "Detected sizeof-only type: $typename -> $sizemacro\n"; } } } } # Note: We don't need to force types into debug info with dummy declarations. # The types are already present in the CTF data from the compiled object, # and ctfstabs's find_type() function handles struct/union/enum prefixes. close($cfile) || bail("can't close $CTmp: $!"); close($genfile) || bail("can't close $GenTmp: $!"); # Compile the C file. runit($cc, @cflags, '-c', '-o', $OTmp, $CTmp); # Convert the debugging information to CTF. runit($ctfconvert, '-l', 'ctfstabs', $OTmp); # Run ctfstabs on the resulting mess. # Hammerhead: Use -x assembler-with-cpp for preprocessing. GCC 14 otherwise fails # on bare type names in the genassym directives (e.g. "proc PROCSIZE"). runit($cc, @cflags, "-x", "assembler-with-cpp", "-E", "-P", "-o", "$GenPPTmp.tmp", $GenTmp); # Hammerhead: Restore blank lines from __GENOFFSETS_BLANK__ markers. # GCC preprocessor strips blank lines, but ctfstabs needs them to separate type stanzas. open(my $ppin, '<', "$GenPPTmp.tmp") || bail("can't open $GenPPTmp.tmp: $!"); open(my $ppout, '>', $GenPPTmp) || bail("can't create $GenPPTmp: $!"); while (<$ppin>) { s/^__GENOFFSETS_BLANK__\s*$/\n/; print $ppout $_; } close($ppin); close($ppout); unlink("$GenPPTmp.tmp") unless $Keep; # Hammerhead: Generate sizeof values for types that don't have members. # CTF/ctfstabs can't handle array typedefs (like sigjmp_buf), so we # compute their sizes directly via a helper program. my %sizeof_values; if (%sizeof_only_types) { if ($Verbose) { print STDERR "Generating sizeof helper for: ", join(", ", keys %sizeof_only_types), "\n"; } # Generate sizeof helper C program open(my $sizeoffile, '>', $SizeofTmp) || bail("can't create $SizeofTmp: $!"); print $sizeoffile "#define __EXTENSIONS__ 1\n"; print $sizeoffile "#include \n"; # Copy the #include lines from the original input foreach my $line (@input_lines) { if ($line =~ /^#include/) { print $sizeoffile $line; } } print $sizeoffile "int main(void) {\n"; foreach my $type (keys %sizeof_only_types) { my $macro = $sizeof_only_types{$type}; print $sizeoffile "\tprintf(\"$macro\\t0x%lx\\n\", (unsigned long)sizeof($type));\n"; } print $sizeoffile "\treturn 0;\n}\n"; close($sizeoffile); # Compile and run the sizeof helper (use native compiler, not cross-compile flags) # Filter out cross-compilation flags that would prevent running the helper my @native_flags = grep { !/^-m64$/ && !/^-xarch/ && !/^-xmodel/ } @cflags; # Remove -g flags (not needed for sizeof helper) @native_flags = grep { !/^-g/ } @native_flags; # Keep only include paths and defines @native_flags = grep { /^-I/ || /^-D/ } @native_flags; if ($Verbose) { print STDERR "+ $cc @native_flags -o $SizeofExe $SizeofTmp\n"; } my $rc = system($cc, @native_flags, '-o', $SizeofExe, $SizeofTmp); if ($rc == 0) { if ($Verbose) { print STDERR "+ ./$SizeofExe\n"; } open(my $sizeout, '-|', "./$SizeofExe") || bail("can't run $SizeofExe: $!"); while (<$sizeout>) { chomp; if (/^(\S+)\t(\S+)$/) { $sizeof_values{$1} = $2; if ($Verbose) { print STDERR "Got sizeof: $1 = $2\n"; } } } close($sizeout); } else { print STDERR "$PROGNAME: warning: sizeof helper failed, trying ctfstabs for all types\n"; } # Clean up sizeof helper files unless ($Keep) { unlink($SizeofTmp) if -f $SizeofTmp; unlink($SizeofExe) if -f $SizeofExe; } } # Hammerhead: If we have sizeof values for types that ctfstabs can't handle, # filter them out of the genpp file before running ctfstabs. my $GenPPFiltered = $GenPPTmp; if (%sizeof_values) { $GenPPFiltered = "ctfstabs.tmp.$$.genpp.filtered"; open(my $gpin, '<', $GenPPTmp) || bail("can't open $GenPPTmp: $!"); open(my $gpout, '>', $GenPPFiltered) || bail("can't create $GenPPFiltered: $!"); my $skip_type = 0; while (<$gpin>) { # Check if this line starts a sizeof-only type that we've handled if (/^([A-Za-z_][A-Za-z0-9_]*)\s+(SIZEOF_[A-Za-z0-9_]+)\s*$/) { if (exists $sizeof_values{$2}) { $skip_type = 1; next; } } # Blank line ends any type section if (/^\s*$/) { $skip_type = 0; } print $gpout $_ unless $skip_type; } close($gpin); close($gpout); } # Run ctfstabs runit($ctfstabs, "-t", "genassym", "-i", $GenPPFiltered, $OTmp); # Hammerhead: Output sizeof values we computed directly foreach my $macro (sort keys %sizeof_values) { print "#define\t$macro\t$sizeof_values{$macro}\n"; } # Clean up filtered file unlink($GenPPFiltered) if ($GenPPFiltered ne $GenPPTmp && !$Keep); cleanup(); exit (0);