#! /usr/bin/perl # $OpenBSD: build-debug-info,v 1.49 2023/05/09 17:41:10 espie Exp $ # Copyright (c) 2019 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use v5.36; my $ports1; BEGIN { $ports1 = $ENV{PORTSDIR} || '/usr/ports'; } use lib "$ports1/infrastructure/lib"; use OpenBSD::BaseFS; use OpenBSD::BasePlistReader; package PlistFactory; our @ISA = qw(OpenBSD::BasePlistFactory); # note that contrary to update-plist, we never build the "new" plist, but # write it on the fly (should we use nlist as well here ?) sub stateclass($) { return 'PlistReader::State'; } sub command_name($) { return 'build-debug-info'; } sub process_next_subpackage($class, $o) { my $r = $class->SUPER::process_next_subpackage($o); if ($r->{state}{bad} != 0) { $o->{state}{exitcode} = 1; } } package PlistReader::State; our @ISA = qw(OpenBSD::BasePlistReader::State); sub substclass($) { return 'OpenBSD::Subst'; } sub handle_options($s) { $s->SUPER::handle_options; } # Most of the heavy lifting is done by visitor methods, as always package OpenBSD::PackingElement; # $self->write_debug_info($base, $fh, $o): # write debug info (duh) for packing element $self relative to # fake dir $base to filehandle $fh perusing debug object # $o for storing useful info sub write_debug_info($, $, $, $) { } package OpenBSD::PackingElement::PkgPath; sub write_debug_info($self, $, $fh, $) { print $fh "\@", $self->keyword, " debug/", $self->name, "\n"; } package OpenBSD::PackingElement::Cwd; # so the reader stuffs a default cwd in the packing-list, which is VERY # useful for absolute names, BUT we don't want to copy the first one. # copy the later ones, even if there is nothing to emit afterwards (this # is cheap) sub write_debug_info($self, $, $fh, $o) { if ($o->{first_cwd}) { $o->{first_cwd} = 0; } else { $self->write($fh); } } package OpenBSD::PackingElement::FileWithDebugInfo; use File::Basename; sub write_debug_info($self, $base, $fh, $o) { return if $self->{nodebug}; my $s = $self->name; my $dbg = $self->mogrify($s); my $dir = $dbg; $dir =~ s/(\/\.debug\/)[^\/]+/$1/; my $path = $base.$self->fullname; my $dbgpath = $base.$self->mogrify($self->fullname); if (-l $path) { # turns out we don't need to do anything, egdb follows symlinks return; } if (-f $path) { my $k = join('/', (stat $path)[0,1]); my $l = $o->{linkstash}{$k}; if (!defined $l) { $self->write_debug_entry($fh, $dir, $dbg, $o); $o->{linkstash}{$k} = $dbg; $self->write_rule($o, $fh, $s, $path, $dbgpath, "OBJCOPY_RULE"); return; } my $ldir = dirname($l)."/"; if ($ldir eq $dir) { # if both mogrified dirs are the same # the debug-info link will do the right thing return; } # so we need to create a link in our dir but that link # must have the right name my $n = $dir.basename($l); # that link may already exist! don't emit it again # TODO missing checks to be certain there's no ambiguity, just # need to write an actual example and verify I can detect it. return if exists $o->{ostash}{$n}; $self->write_debug_entry($fh, $dir, $n, $o); $self->write_rule($o, $fh, $l, $path, $dbgpath, "LINK_RULE"); } else { $o->{state}->fatal("Error: #1 does not exist", $path); } } sub write_debug_entry($self, $fh, $dir, $dbg, $o) { print $fh $dbg, "\n"; if (!exists $o->{dirstash}{$dir}) { print $fh $dir, "\n"; $o->{dirstash}{$dir} = 1; } $o->{ostash}{$dbg} = 1; delete $o->{empty}; } sub write_rule($self, $o, $fh, $s, $p1, $p2, $what) { my $mk = $o->{mk}; print $mk "all: $p2\n"; print $mk "$p2: $p1\n"; print $mk "\t\@\$\{$what\}\n\n"; } sub mogrify($self, $s) { $s =~ s,([^\/]+)$,.debug/$1.dbg,; return $s; } package OpenBSD::PackingElement::StaticLib; sub write_debug_info($, $, $, $) { # don't do anything } package OpenBSD::PackingElement::NoDefaultConflict; sub write_debug_info($self, $, $fh, $) { $self->write($fh); } package OpenBSD::PackingElement::IsBranch; sub write_debug_info($self, $, $fh, $) { $self->write($fh); } package OpenBSD::PackingElement::Conflict; sub write_debug_info($self, $, $fh, $) { my $m = join('|', map {"debug-$_"} split(/\|/, $self->name)); print $fh "\@", $self->keyword, " $m\n"; } # This is the BuildDebugInfo main code proper package BuildDebugInfo::State; our @ISA = qw(OpenBSD::AddCreateDelete::State); sub handle_options($state) { $state->SUPER::handle_options('vqFP:', '[-Fmnqvx]', '-P pkgdir', '-- pkg_create_args ...'); $state->{pkgdir} = $state->opt('P'); if (!defined $state->{pkgdir}) { $state->fatal("-P pkgdir is mandatory"); } $state->{verbose} = $state->opt('v'); $state->{quiet} = $state->opt('q'); } sub openfile($self, $name) { open my $fh, ">", $name or $self->fatal("Can't write to #1: #2", $name, $!); $self->say("Writing #1", $name) unless $self->{quiet}; return $fh; } package BuildDebugInfo; our @ISA = qw(OpenBSD::BaseFS); use File::Basename; use File::Compare; sub new($class) { return $class->SUPER::new(undef, BuildDebugInfo::State->new); } my $self = BuildDebugInfo->new; my $state = $self->{state}; $state->{exitcode} = 0; my $base = PlistFactory->parse_args($self); use File::Basename; $self->{mk} = $state->openfile($state->{pkgdir}."/Makefile.new"); print {$self->{mk}} << 'EOPREAMBLE'; # Makefile generated by build-debug-info $OpenBSD: build-debug-info,v 1.49 2023/05/09 17:41:10 espie Exp $ # No serviceable parts # Intended to run under the stage area after cd ${WRKINST} OBJCOPY_RULE = ${INSTALL_DATA_DIR} ${@D} && \ perm=`stat -f "%p" $?` && chmod u+rw $? && \ echo "> Extracting debug info from $?" && \ if readelf 2>/dev/null -wi $?|cmp -s /dev/null -; then \ echo "Warning: no debug-info in $?"; \ fi && \ llvm-objcopy --only-keep-debug $? $@ && \ ${DWZ} $@ && \ strip -d -o $?.stripped $? && cp $?.stripped $? && rm $?.stripped && \ llvm-objcopy --add-gnu-debuglink=$@ $? $?.debuglink && cp $?.debuglink $? && rm $?.debuglink && \ chmod $$perm $? && \ touch $@ LINK_RULE = ${INSTALL_DATA_DIR} ${@D} && \ echo "> Link debug info from $? to $@" && ln $? $@ all: .PHONY: all EOPREAMBLE # XXX even though links may end up in different subpackages, the # logic has to be global, as this is used by the generated makefile # to extract debug info. pkg_create/pkg_add may then create copies, # but they will contain the right information $self->{linkstash} = {}; $self->{ostash} = {}; for my $l (@{$self->{lists}}) { $self->{empty} = 1; $self->{dirstash} = {}; $self->{prefix} = $base."/".$l->{state}{prefix}; my $name = pop @{$l->{base_plists}}; $name = $state->{pkgdir}."/".(basename $name); my $fh = $state->openfile($name); $self->{first_cwd} = 1; $l->olist->write_debug_info($base, $fh, $self); close($fh) or $state->fatal("Can't write plist: #1", $!); $self->warn_if_empty($state, $l); } close($self->{mk}) or $state->fatal("Can't close Makefile.new: #1", $!); sub warn_if_empty { my ($self, $state, $l) = @_; return unless $self->{empty}; $state->errsay("Warning: no debug-info in #1", $l->olist->pkgname); return if @{$self->{lists}} == 1; $state->errsay("Set DEBUG_PACKAGES manually ?"); } if (!$state->{exitcode}) { my $f = $state->{pkgdir}."/Makefile"; $state->say("Renaming #1 to #2", "$f.new", "Makefile") unless $state->{quiet}; rename("$f.new", $f) or $state->fatal("Can't create Makefile: #1", $!); } exit($state->{exitcode});