aboutsummaryrefslogblamecommitdiffstats
path: root/build/fix-glibc-function-versions
blob: 213687a38b1bb5ade8d8dcd32ee1c3d93613b67d (plain) (tree)


















































































































































                                                                                                                                                                                                           
#!/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
}