macro: create .notes.perlmod.package section in libraries

So all the macro-supported packages can be listed in the ELF file.
This way all the available packages in a library can be viewed with
`readelf -W -n libfoo.so`. In the future we can also embed metadata in
there.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2024-08-28 09:59:44 +02:00
parent ca64bc9fdf
commit 057f71a473
4 changed files with 88 additions and 2 deletions

View File

@ -34,6 +34,10 @@ my $opts = {
'PATH', 'PATH',
"Path to additional perl code to include in the package after the 'use' statements", "Path to additional perl code to include in the package after the 'use' statements",
], ],
'from-notes' => [
undef,
"Read the package list from ELF notes sections",
],
}; };
sub help { sub help {
@ -43,7 +47,7 @@ sub help {
print {$fd} "mandatory OPTIONS are:\n"; print {$fd} "mandatory OPTIONS are:\n";
for my $o (sort keys %$opts) { for my $o (sort keys %$opts) {
my ($arg, $desc) = $opts->{$o}->@*; my ($arg, $desc) = $opts->{$o}->@*;
my $p = "--$o=$arg"; my $p = defined($arg) ? "--$o=$arg" : "--$o";
printf {$fd} " %20s %s\n", $p, $desc; printf {$fd} " %20s %s\n", $p, $desc;
} }
} }
@ -74,6 +78,13 @@ ARGPARSE: while (@ARGV) {
} else { } else {
next; next;
}; };
if (!defined($opts->{$o}->[0])) {
unshift @ARGV, $arg;
$params->{$o} = 1;
next ARGPARSE;
}
die "--$o requires an argument\n" if !defined($arg); die "--$o requires an argument\n" if !defined($arg);
if (ref($params->{$o}) eq 'ARRAY') { if (ref($params->{$o}) eq 'ARRAY') {
push $params->{$o}->@*, $arg; push $params->{$o}->@*, $arg;
@ -99,7 +110,8 @@ my $lib_prefix = $params->{'lib-prefix'}
or die "missing --lib-prefix parameter\n"; or die "missing --lib-prefix parameter\n";
my $lib = $params->{'lib'} my $lib = $params->{'lib'}
or die "missing --lib parameter\n"; or die "missing --lib parameter\n";
my $lib_tag = $params->{'lib-tag'}; my $lib_tag = $params->{'lib-tag'}
or die "missing --lib-tag parameter\n";
my $debug_libpath = $params->{'debug-libpath'} // ''; my $debug_libpath = $params->{'debug-libpath'} // '';
my $extra_code = ''; my $extra_code = '';
for my $file ($params->{'include-file'}->@*) { for my $file ($params->{'include-file'}->@*) {
@ -108,6 +120,7 @@ for my $file ($params->{'include-file'}->@*) {
die "error reading '$file': $!\n" if !defined($more); die "error reading '$file': $!\n" if !defined($more);
$extra_code .= $more; $extra_code .= $more;
} }
my $from_notes = $params->{'from-notes'};
sub pkg2file { sub pkg2file {
return ($_[0] =~ s@::@/@gr) . ".pm"; return ($_[0] =~ s@::@/@gr) . ".pm";
@ -225,6 +238,29 @@ if ($lib ne '-') {
close($fh); close($fh);
} }
if ($from_notes) {
die "missing library file to read packages from\n" if !@ARGV;
die "--from-notes requires exactly one library\n" if @ARGV > 1;
open my $fh, '<', $ARGV[0] or die "failed to open $ARGV[0] for reading: $!\n";
my $fd = fileno($fh);
my $data = `objcopy -O binary --only-section .note.perlmod.package $ARGV[0] /dev/stdout`;
close($fh);
my @packages;
while (length($data)) {
my ($name_size, $desc_size, $ty) = unpack('LLL', substr($data, 0, 3*4, ''));
die "unexpected description in package note - incompatible perlmod version?\n"
if $desc_size;
my $name = substr($data, 0, $name_size, '');
my $desc = substr($data, 0, $desc_size, '');
print("Found package '$name'\n");
push @packages, $name;
}
die "trailing data in notes section\n" if length($data);
@ARGV = @packages;
}
for my $package (@ARGV) { for my $package (@ARGV) {
my $path = ($package =~ s@::@/@gr) . ".pm"; my $path = ($package =~ s@::@/@gr) . ".pm";

View File

@ -107,6 +107,9 @@ impl Package {
}); });
} }
let package_name_bytes =
syn::LitByteStr::new(self.attrs.package_name.as_bytes(), Span::call_site());
let package_name_len = self.attrs.package_name.len();
let bootstrap_name = format!("boot_{}", self.attrs.package_name).replace("::", "__"); let bootstrap_name = format!("boot_{}", self.attrs.package_name).replace("::", "__");
let bootstrap_ident = Ident::new(&bootstrap_name, Span::call_site()); let bootstrap_ident = Ident::new(&bootstrap_name, Span::call_site());
@ -120,7 +123,13 @@ impl Package {
pub extern "C" fn #bootstrap_ident( pub extern "C" fn #bootstrap_ident(
_cv: Option<&::perlmod::ffi::CV>, _cv: Option<&::perlmod::ffi::CV>,
) { ) {
#[used]
#[link_section = ".note.perlmod.package"]
static PACKAGE_ENTRY: ::perlmod::__private__::ElfNote<{#package_name_len}> =
::perlmod::__private__::ElfNote::new_package(*#package_name_bytes);
static ONCE: ::std::sync::Once = ::std::sync::Once::new(); static ONCE: ::std::sync::Once = ::std::sync::Once::new();
ONCE.call_once(|| { ONCE.call_once(|| {
unsafe { unsafe {
use ::perlmod::ffi::RSPL_newXS_flags; use ::perlmod::ffi::RSPL_newXS_flags;

32
perlmod/src/elf_notes.rs Normal file
View File

@ -0,0 +1,32 @@
//! The `#[package]` macro stores package lists in ELF file note sections.
//! This is the implementation for this.
//!
//! This is private and not meant to be a public API.
/*
#[repr(C, packed)]
pub struct Info {
extra stuff
}
*/
#[repr(C, packed)]
pub struct ElfNote<const N: usize> {
pub name_size: u32,
pub desc_size: u32,
pub ty: u32,
pub name: [u8; N],
//pub desc: Info,
}
impl<const N: usize> ElfNote<{ N }> {
pub const fn new_package(name: [u8; N]) -> Self {
Self {
name_size: N as u32,
desc_size: 0, // size_of::<Info>()
ty: 0,
name,
//desc: Info::new(),
}
}
}

View File

@ -113,3 +113,12 @@ pub use perlmod_macro::package;
/// ///
/// For an example on making blessed objects, see [`Value::bless_box`](Value::bless_box()). /// For an example on making blessed objects, see [`Value::bless_box`](Value::bless_box()).
pub use perlmod_macro::export; pub use perlmod_macro::export;
mod elf_notes;
#[doc(hidden)]
pub mod __private__ {
//! This is private and not meant to be a public API and thus semver exempt.
pub use super::elf_notes::ElfNote;
}