From 1ea84bebeaafd4bd22fe021ebef94e7eeb424804 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 2 Dec 2020 04:26:15 -0500 Subject: fix glibc linkage on debian again --- build/fix-glibc-function-versions | 147 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100755 build/fix-glibc-function-versions (limited to 'build') diff --git a/build/fix-glibc-function-versions b/build/fix-glibc-function-versions new file mode 100755 index 0000000..213687a --- /dev/null +++ b/build/fix-glibc-function-versions @@ -0,0 +1,147 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.020; +use experimental 'signatures'; +no warnings 'experimental::signatures'; + +# see http://www.lightofdawn.org/wiki/wiki.cgi/NewAppsOnOldGlibc for details + +# as of currently, only `log2` is being used from GLIBC_2.29 and +# `pthread_sigmask` and `pthread_getattr_np` is being used from GLIBC_2.32, and +# corresponding versions from GLIBC_2.2.5 do exist, so we can just swap them +# out. + +my $bin = $ARGV[0]; + +system("readelf -sW $bin | grep -q 'GLIBC_2.\\(29\\|32\\)'"); +if ($?) { + warn "no symbols from GLIBC_2.29 or GLIBC_2.32 found, skipping\n"; + exit(0) +} + +my %readelf_v = parse_readelf_v($bin); +my %readelf_s = parse_readelf_s($bin); + +my $bin_contents = do { + local $/; + open my $bin_fh, '<', $bin or die "couldn't open $bin: $!"; + <$bin_fh> +}; + +# we could just make the GLIBC_2.29 symbol weak (as described in the above +# page), but that causes a warning to be printed every time you run the binary, +# which is obnoxious + +# use constant VER_FLG_WEAK => 0x02; +# my $prev_glibc_229_flags = substr $bin_contents, $glibc_229_offset + 0x04, 2, pack('s', VER_FLG_WEAK); +# if (unpack('s', $prev_glibc_229_flags) != 0) { +# die "GLIBC_2.29 did not have flags set to 0\n"; +# } + +# instead, we overwrite vna_hash, vna_flags, vna_other, vna_name with the 2.2.5 +# versions (but leave vna_next the same) - this causes there to be multiple +# entries describing GLIBC_2.2.5, but this appears to be harmless +my $gnu_version_r_offset = $readelf_v{'.gnu.version_r'}{offset}; +my $fix_libc = defined($readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{offset}); +my $fix_libm = defined($readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{offset}); + +if ($fix_libc) { + my $glibc_232_libc_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{offset}; + my $glibc_225_libc_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{offset}; + substr($bin_contents, $glibc_232_libc_offset + 0x00, 12, substr($bin_contents, $glibc_225_libc_offset + 0x00, 12)); +} + +if ($fix_libm) { + my $glibc_229_libm_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{offset}; + my $glibc_225_libm_offset = $gnu_version_r_offset + $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{offset}; + substr($bin_contents, $glibc_229_libm_offset + 0x00, 12, substr($bin_contents, $glibc_225_libm_offset + 0x00, 12)); +} + +my $gnu_version_offset = $readelf_v{'.gnu.version'}{offset}; +my $fix_log2 = defined($readelf_s{'log2@GLIBC_2.29'}{ver}) && $readelf_s{'log2@GLIBC_2.29'}{ver} == $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}; +my $fix_pthread_sigmask = defined($readelf_s{'pthread_sigmask@GLIBC_2.32'}{ver}) && $readelf_s{'pthread_sigmask@GLIBC_2.32'}{ver} == $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}; +my $fix_pthread_getattr_np = defined($readelf_s{'pthread_getattr_np@GLIBC_2.32'}{ver}) && $readelf_s{'pthread_getattr_np@GLIBC_2.32'}{ver} == $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}; + +if ($fix_log2) { + my $log2_offset = $gnu_version_offset + 2 * $readelf_s{'log2@GLIBC_2.29'}{num}; + my $prev_log2_version = substr $bin_contents, $log2_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.2.5'}{ver}); + if (unpack('s', $prev_log2_version) != $readelf_v{'.gnu.version_r'}{'libm.so.6'}{'GLIBC_2.29'}{ver}) { + die "log2 doesn't appear to use the symbol version from GLIBC_2.29\n"; + } +} + +if ($fix_pthread_sigmask) { + my $pthread_sigmask_offset = $gnu_version_offset + 2 * $readelf_s{'pthread_sigmask@GLIBC_2.32'}{num}; + my $prev_pthread_sigmask_version = substr $bin_contents, $pthread_sigmask_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{ver}); + if (unpack('s', $prev_pthread_sigmask_version) != $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}) { + die "pthread_sigmask doesn't appear to use the symbol version from GLIBC_2.32\n"; + } +} + +if ($fix_pthread_getattr_np) { + my $pthread_getattr_np_offset = $gnu_version_offset + 2 * $readelf_s{'pthread_getattr_np@GLIBC_2.32'}{num}; + my $prev_pthread_getattr_np_version = substr $bin_contents, $pthread_getattr_np_offset, 2, pack('s', $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.2.5'}{ver}); + if (unpack('s', $prev_pthread_getattr_np_version) != $readelf_v{'.gnu.version_r'}{'libc.so.6'}{'GLIBC_2.32'}{ver}) { + die "pthread_getattr_np doesn't appear to use the symbol version from GLIBC_2.32\n"; + } +} + +open my $fh, '>', "$bin.new" or die "couldn't open $bin.new: $!"; +print $fh $bin_contents; +chmod 0755, "$bin.new"; + +my $remaining = `readelf -sW $bin.new | grep 'GLIBC_2.\\(29\\|32\\)'`; +if (!$?) { + die "additional symbols from GLIBC_2.29 or GLIBC_2.32 found:\n$remaining\n"; +} + +sub parse_readelf_s($bin) { + my $readelf = `readelf -sW $bin`; + + my %ret; + for my $line (split "\n", $readelf) { + if ($line =~ /^ *[0-9]+:/) { + my ($num, $value, $size, $type, $bind, $vis, $ndx, $name, $ver) = split ' ', $line; + if (defined $ver) { + ($ver) = $ver =~ /\((.*)\)/; + } + if (defined $num) { + ($num) = $num =~ /(.*):/; + } + $ret{$name} = { + num => $num, + ver => $ver, + } + } + } + + %ret +} + +sub parse_readelf_v($bin) { + my $readelf = `readelf -V $bin`; + + my $section; + my $file; + my %ret; + for my $line (split "\n", $readelf) { + if ($line =~ /^Version (?:symbols|needs) section '([^']+)'/) { + $section = $1; + } + if ($line =~ /^ Addr: (?:[^ ]+) Offset: ([^ ]+)/) { + $ret{$section}{offset} = hex($1); + } + if ($line =~ /^ [^:]+: Version: [^ ]+ File: ([^ ]+)/) { + $file = $1; + } + if ($line =~ /^ ([^:]+): Name: ([^ ]+) Flags: (?:[^ ]+) Version: (.*)/) { + $ret{$section}{$file}{$2} = { + offset => hex($1), + ver => $3, + }; + } + } + + %ret +} -- cgit v1.2.3-54-g00ecf