[dpkg] 129/200: dpkg-shlibdeps: Improve ELF ABI mismatch detector

Ximin Luo infinity0 at debian.org
Wed Apr 5 15:17:28 UTC 2017


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository dpkg.

commit 8ae966ae7d3635b8359829085db4262923ceae96
Author: Guillem Jover <guillem at debian.org>
Date:   Wed Jan 25 04:39:08 2017 +0100

    dpkg-shlibdeps: Improve ELF ABI mismatch detector
    
    The previous ELF ABI mismatch detector was very naïve, as the string
    returned by «objdump -a» is a very simplistic representation of the
    ELF ABI used.
    
    Switch to our own ELF header parser, so that we can distinguish based
    on the fields that define the object ABI.
    
    This is still not enough, and we will have collisions with things such
    as linux-i386 and hurd-i386, but most of the previously colliding
    objects are now filtered.
    
    This also makes us independent of objdump not supporting any unknown
    ELF object ABI.
    
    Closes: #849913
---
 debian/changelog               |   5 ++
 scripts/Dpkg/Shlibs.pm         |   3 +
 scripts/Dpkg/Shlibs/Objdump.pm | 127 ++++++++++++++++++++++++++++++++---------
 scripts/dpkg-shlibdeps.pl      |  11 ++--
 4 files changed, 113 insertions(+), 33 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index 03939b2..4488de0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -15,6 +15,11 @@ dpkg (1.18.19) UNRELEASED; urgency=medium
   * Generate Testsuite-Restrictions fields from the test restrictions in
     dpkg-source into .dsc files. Closes: #847926
     Based on a patch by Iain Lane <laney at debian.org>.
+  * Improve the ELF ABI mismatch detector in dpkg-shlibdeps, by parsing the
+    ELF header ourselves. While still not perfect (things like linux-i386 and
+    hurd-i386 will still match), it will filter lots of previously matching
+    objects that should have been ignored, and will work even when objdump
+    does not know about the specific object details. Closes: #849913
   * Portability:
     - On GNU/Hurd try to use the new process executable name attribute from
       libps, to properly match on start-stop-daemon --exec.
diff --git a/scripts/Dpkg/Shlibs.pm b/scripts/Dpkg/Shlibs.pm
index 69b1baf..9816dbf 100644
--- a/scripts/Dpkg/Shlibs.pm
+++ b/scripts/Dpkg/Shlibs.pm
@@ -158,6 +158,9 @@ sub find_library {
 	    my $libformat = Dpkg::Shlibs::Objdump::get_format("$checkdir/$lib");
 	    if ($format eq $libformat) {
 		push @libs, canonpath("$checkdir/$lib");
+	    } else {
+		debug(1, "Skipping lib $checkdir/$lib, libabi=0x%s != objabi=0x%s",
+		      unpack('H*', $libformat), unpack('H*', $format));
 	    }
 	}
     }
diff --git a/scripts/Dpkg/Shlibs/Objdump.pm b/scripts/Dpkg/Shlibs/Objdump.pm
index 1c2ec5e..63b5825 100644
--- a/scripts/Dpkg/Shlibs/Objdump.pm
+++ b/scripts/Dpkg/Shlibs/Objdump.pm
@@ -83,40 +83,109 @@ sub has_object {
     return exists $self->{objects}{$objid};
 }
 
+use constant {
+    ELF_BITS_NONE           => 0,
+    ELF_BITS_32             => 1,
+    ELF_BITS_64             => 2,
+
+    ELF_ORDER_NONE          => 0,
+    ELF_ORDER_2LSB          => 1,
+    ELF_ORDER_2MSB          => 2,
+
+    ELF_MACH_MIPS           => 8,
+    ELF_MACH_PPC64          => 21,
+    ELF_MACH_ARM            => 40,
+    ELF_MACH_SH             => 42,
+    ELF_MACH_IA64           => 50,
+
+    ELF_VERSION_NONE        => 0,
+    ELF_VERSION_CURRENT     => 1,
+
+    # List of processor flags that might influence the ABI.
+
+    ELF_FLAG_ARM_ALIGN8     => 0x00000040,
+    ELF_FLAG_ARM_NEW_ABI    => 0x00000080,
+    ELF_FLAG_ARM_OLD_ABI    => 0x00000100,
+    ELF_FLAG_ARM_SOFT_FLOAT => 0x00000200,
+    ELF_FLAG_ARM_HARD_FLOAT => 0x00000400,
+    ELF_FLAG_ARM_EABI_MASK  => 0xff000000,
+
+    ELF_FLAG_IA64_ABI64     => 0x00000010,
+
+    ELF_FLAG_MIPS_ABI2      => 0x00000020,
+    ELF_FLAG_MIPS_32BIT     => 0x00000100,
+    ELF_FLAG_MIPS_FP64      => 0x00000200,
+    ELF_FLAG_MIPS_NAN2008   => 0x00000400,
+    ELF_FLAG_MIPS_ABI_MASK  => 0x0000f000,
+    ELF_FLAG_MIPS_ARCH_MASK => 0xf0000000,
+
+    ELF_FLAG_PPC64_ABI64    => 0x00000003,
+
+    ELF_FLAG_SH_MACH_MASK   => 0x0000001f,
+};
+
+# These masks will try to expose processor flags that are ABI incompatible,
+# and as such are part of defining the architecture ABI. If uncertain it is
+# always better to not mask a flag, because that preserves the historical
+# behavior, and we do not drop dependencies.
+my %elf_flags_mask = (
+    ELF_MACH_ARM()      => ELF_FLAG_ARM_EABI_MASK |
+                           ELF_FLAG_ARM_NEW_ABI | ELF_FLAG_ARM_OLD_ABI |
+                           ELF_FLAG_ARM_SOFT_FLOAT | ELF_FLAG_ARM_HARD_FLOAT,
+    ELF_MACH_IA64()     => ELF_FLAG_IA64_ABI64,
+    ELF_MACH_MIPS()     => ELF_FLAG_MIPS_ARCH_MASK | ELF_FLAG_MIPS_ABI_MASK |
+                           ELF_FLAG_MIPS_ABI2,
+    ELF_MACH_PPC64()    => ELF_FLAG_PPC64_ABI64,
+    ELF_MACH_SH()       => ELF_FLAG_SH_MACH_MASK,
+);
+
 sub get_format {
-    my ($file, $objdump) = @_;
+    my ($file) = @_;
     state %format;
 
-    $objdump //= $OBJDUMP;
+    return $format{$file} if exists $format{$file};
 
-    if (exists $format{$file}) {
-        return $format{$file};
-    } else {
-        my ($output, %opts, $pid, $res);
-        local $_;
+    my $header;
 
-        if ($objdump ne 'objdump') {
-            $opts{error_to_file} = '/dev/null';
-        }
-        $pid = spawn(exec => [ $objdump, '-a', '--', $file ],
-                     env => { LC_ALL => 'C' },
-                     to_pipe => \$output, %opts);
-        while (<$output>) {
-            chomp;
-            if (/^\s*\S+:\s*file\s+format\s+(\S+)\s*$/) {
-                $format{$file} = $1;
-                $res = $format{$file};
-                last;
-            }
-        }
-        close($output);
-        wait_child($pid, nocheck => 1);
-        if ($?) {
-            subprocerr('objdump') if $objdump eq 'objdump';
-            $res = get_format($file, 'objdump');
-        }
-        return $res;
+    open my $fh, '<', $file or syserr(g_('cannot read %s'), $file);
+    read($fh, $header, 64) == 64 or syserr(g_('cannot read %s'), $file);
+    close $fh;
+
+    my %elf;
+
+    # Unpack the identifier field.
+    @elf{qw(magic bits endian vertype osabi verabi)} = unpack 'a4C5', $header;
+
+    return unless $elf{magic} eq "\x7fELF";
+    return unless $elf{vertype} == ELF_VERSION_CURRENT;
+
+    my ($elf_word, $elf_endian);
+    if ($elf{bits} == ELF_BITS_32) {
+        $elf_word = 'L';
+    } elsif ($elf{bits} == ELF_BITS_64) {
+        $elf_word = 'Q';
+    } elsif ($elf{bits} == ELF_BITS_NONE) {
+        return;
+    }
+    if ($elf{endian} == ELF_ORDER_2LSB) {
+        $elf_endian = '<';
+    } elsif ($elf{endian} == ELF_ORDER_2MSB) {
+        $elf_endian = '>';
+    } elsif ($elf{endian} == ELF_ORDER_NONE) {
+        return;
     }
+
+    # Unpack the endianness and size dependent fields.
+    my $tmpl = "x16(S2Lx[${elf_word}3]L)${elf_endian}";
+    @elf{qw(type mach version flags)} = unpack $tmpl, $header;
+
+    # Mask any processor flags that might not change the architecture ABI.
+    $elf{flags} &= $elf_flags_mask{$elf{mach}} // 0;
+
+    # Repack for easy comparison.
+    $format{$file} = pack 'C2SL', @elf{qw(bits endian mach flags)};
+
+    return $format{$file};
 }
 
 sub is_elf {
@@ -181,6 +250,8 @@ sub analyze {
     $self->reset;
     $self->{file} = $file;
 
+    $self->{exec_abi} = Dpkg::Shlibs::Objdump::get_format($file);
+
     local $ENV{LC_ALL} = 'C';
     open(my $objdump, '-|', $OBJDUMP, '-w', '-f', '-p', '-T', '-R', $file)
         or syserr(g_('cannot fork for %s'), $OBJDUMP);
diff --git a/scripts/dpkg-shlibdeps.pl b/scripts/dpkg-shlibdeps.pl
index 892aeff..8e8cb99 100755
--- a/scripts/dpkg-shlibdeps.pl
+++ b/scripts/dpkg-shlibdeps.pl
@@ -194,17 +194,18 @@ foreach my $file (keys %exec) {
     my %soname_notfound;
     my %alt_soname;
     foreach my $soname (@sonames) {
-	my @libs = my_find_library($soname, $obj->{RPATH}, $obj->{format}, $file);
+	my @libs = my_find_library($soname, $obj->{RPATH}, $obj->{exec_abi}, $file);
 	unless (scalar @libs) {
 	    $soname_notfound{$soname} = 1;
 	    $global_soname_notfound{$soname} = 1;
-	    my $msg = g_("couldn't find library %s needed by %s (ELF " .
-			 "format: '%s'; RPATH: '%s')");
+	    my $msg = g_('cannot find library %s needed by %s (ELF ' .
+	                 "format: '%s' abi: '%s'; RPATH: '%s')");
+	    my $exec_abi = unpack 'H*', $obj->{exec_abi};
 	    if (scalar(split_soname($soname))) {
-		errormsg($msg, $soname, $file, $obj->{format}, join(':', @{$obj->{RPATH}}));
+		errormsg($msg, $soname, $file, $obj->{format}, $exec_abi, join(':', @{$obj->{RPATH}}));
 		$error_count++;
 	    } else {
-		warning($msg, $soname, $file, $obj->{format}, join(':', @{$obj->{RPATH}}));
+		warning($msg, $soname, $file, $obj->{format}, $exec_abi, join(':', @{$obj->{RPATH}}));
 	    }
 	    next;
 	}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/dpkg.git



More information about the Reproducible-commits mailing list