From de8d737e82b46b5d55c1769a7c5f77364cf48aa8 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 7 Oct 2020 15:58:42 -0400 Subject: [PATCH] exported symbol name pollution test This runs from inside a rank and uses ldd on itself to figure out what libraries to examine, then nm on those MPI libraries to look for symbols without some accepted OMPI prefix. Signed-off-by: Mark Allen --- packaging/Makefile.am | 10 ++ packaging/README.md | 62 +++++++++++ packaging/configure.ac | 144 ++++++++++++++++++++++++++ packaging/nmcheck_prefix.pl | 198 ++++++++++++++++++++++++++++++++++++ packaging/run_nmcheck.c | 26 +++++ 5 files changed, 440 insertions(+) create mode 100644 packaging/Makefile.am create mode 100644 packaging/README.md create mode 100644 packaging/configure.ac create mode 100755 packaging/nmcheck_prefix.pl create mode 100644 packaging/run_nmcheck.c diff --git a/packaging/Makefile.am b/packaging/Makefile.am new file mode 100644 index 0000000..f5be827 --- /dev/null +++ b/packaging/Makefile.am @@ -0,0 +1,10 @@ +# +# Copyright (c) 2020 IBM Corporation. All rights reserved. +# +# $COPYRIGHT$ +# + +noinst_PROGRAMS = \ + run_nmcheck + +EXTRA_DIST = nmcheck_prefix.pl diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..b0d8ade --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,62 @@ +# Test for exported symbol name pollution + +This runs from inside a rank and uses ldd on itself to figure out what +libraries to examine (it looks for a libmpi.so in the ldd output, and +then decides that any other libraries that come from the same directory +as libmpi.so must also be part of OMPI and should be examined too), then +nm on those MPI libraries to look for symbols without some accepted +OMPI prefix. + +Example runs with good/bad output:: + +``` +% export OPAL_PREFIX=/some/path +% export OLDPATH=$PATH +% export PATH=$OPAL_PREFIX/bin:${PATH} +% export LD_LIBRARY_PATH=$OPAL_PREFIX/lib +% autoreconf -i +% make + +------------------------------------------------ +*** Example of a passing run: + +% $OPAL_PREFIX/bin/mpirun -np 1 ./run_nmcheck + +> Checking for bad symbol names: +> *** checking /some/path/lib/libpmix.so.0 +> *** checking /some/path/lib/libopen-pal.so.0 +> *** checking /some/path/lib/libmpi.so.0 +> *** checking /some/path/lib/libmpi_mpifh.so +> *** checking /some/path/lib/libmpi_usempif08.so +> *** checking /some/path/lib/libmpi_usempi_ignore_tkr.so + +------------------------------------------------ +*** Example of a failing run: + +Then if I edit one of the opal C files to add a couple +extraneous globally exported symbols + int myfunction() { return 0; } + int myglobal = 123; +and rerun the test: + +% $OPAL_PREFIX/bin/mpirun -np 1 ./run_nmcheck + +> Checking for bad symbol names: +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libmpi.so.0 +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libpmix.so.0 +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libopen-pal.so.0 +> [error] myfunction +> [error] myglobal +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libmpi_mpifh.so +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libmpi_usempif08.so +> *** checking /u/markalle/space/Projects/OMPIDev.m/install/lib/libmpi_usempi_ignore_tkr.so +> -------------------------------------------------------------------------- +> MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD +> Proc: [[27901,1],0] +> Errorcode: 16 +> +> NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes. +> You may or may not see output from other processes, depending on +> exactly when Open MPI kills them. +> -------------------------------------------------------------------------- +``` diff --git a/packaging/configure.ac b/packaging/configure.ac new file mode 100644 index 0000000..7eaa315 --- /dev/null +++ b/packaging/configure.ac @@ -0,0 +1,144 @@ +# -*- shell-script -*- +# +# Copyright (c) 2012-2020 Cisco Systems, Inc. All rights reserved. +# Copyright (c) 2020 IBM Corporatio. All rights reserved. +# +# $COPYRIGHT$ +# + +dnl +dnl Init autoconf +dnl + +AC_PREREQ([2.67]) +AC_INIT([packaging-test], [1.0], [http://www.open-mpi.org]) +AC_CONFIG_AUX_DIR([config]) +AC_CONFIG_MACRO_DIR([config]) +AC_CONFIG_SRCDIR([.]) + +echo "Configuring packaging test" + +AM_INIT_AUTOMAKE([1.11 foreign -Wall -Werror]) + +# If Automake supports silent rules, enable them. +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AH_TOP([/* -*- c -*- + * + * ompitest test suite configuation header file. + * See the top-level LICENSE file for license and copyright + * information. + */ + +#ifndef OMPITEST_CONFIG_H +#define OMPITEST_CONFIG_H +]) +AH_BOTTOM([#endif /* OMPITEST_CONFIG_H */]) + +dnl +dnl Make automake clean emacs ~ files for "make clean" +dnl + +CLEANFILES="*~" +AC_SUBST(CLEANFILES) + +dnl +dnl Get various programs +dnl Bias towards mpicc/mpic++/mpif77 +dnl C compiler +dnl + +if test "$CC" != ""; then + BASE="`basename $CC`" +else + BASE= +fi +if test "$BASE" = "" -o "$BASE" = "." -o "$BASE" = "cc" -o \ + "$BASE" = "gcc" -o "$BASE" = "xlc" -o "$BASE" = "pgcc" -o \ + "$BASE" = "icc"; then + AC_CHECK_PROG(HAVE_MPICC, mpicc, yes, no) + if test "$HAVE_MPICC" = "yes"; then + CC=mpicc + export CC + fi +fi + +CFLAGS_save=$CFLAGS +AC_PROG_CC +CFLAGS=$CFLAGS_save + +dnl +dnl Fortran compiler +dnl + +if test "$FC" != ""; then + BASE="`basename $FC`" +else + BASE= +fi +if test "$BASE" = "" -o "$BASE" = "." -o "$BASE" = "f77" -o \ + "$BASE" = "g77" -o "$BASE" = "f90" -o "$BASE" = "g90" -o \ + "$BASE" = "xlf" -o "$BASE" = "ifc" -o "$BASE" = "pgf77"; then + AC_CHECK_PROG(HAVE_MPIFORT, mpifort, yes, no) + AS_IF([test "$HAVE_MPIFORT" = "yes"], + [FC=mpifort], + [AC_CHECK_PROG([HAVE_MPIF90], mpif90, yes, no) + AS_IF([test "$HAVE_MPIF90" = "yes"], + [FC=mpif90], + [AC_CHECK_PROG([HAVE_MPIF77], mpif77, yes, no) + AS_IF([test "$HAVE_MPIF77" = "yes"], + [FC=mpif77], + [AC_MSG_WARN([Cannot find a suitable MPI compiler]) + AC_MSG_ERROR([Cannot continue]) + ]) + ]) + ]) + export FC +fi + +FCFLAGS_save=$FCFLAGS +AC_PROG_FC +FCFLAGS=$FCFLAGS_save + +dnl +dnl Because these are meant to be used for debugging, after all +dnl + +if test -z "$CFLAGS"; then + CFLAGS="-g" +fi +if test -z "$FCFLAGS"; then + FCFLAGS="-g"; +fi +AC_SUBST(FCFLAGS) +if test -z "$FFLAGS"; then + FFLAGS="-g"; +fi +AC_SUBST(FFLAGS) + +dnl +dnl Ensure that we can compile and link a C MPI program +dnl + +AC_LANG_PUSH([C]) +AC_CHECK_HEADERS(mpi.h) + +AC_MSG_CHECKING([if linking MPI program works]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include +]], + [[MPI_Comm a = MPI_COMM_WORLD]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_WARN([Simple MPI program fails to link]) + AC_MSG_ERROR([Cannot continue]) + ]) +AC_LANG_POP([C]) + +dnl +dnl Party on +dnl + +AC_CONFIG_FILES([ + Makefile +]) +AC_OUTPUT diff --git a/packaging/nmcheck_prefix.pl b/packaging/nmcheck_prefix.pl new file mode 100755 index 0000000..d2b8b80 --- /dev/null +++ b/packaging/nmcheck_prefix.pl @@ -0,0 +1,198 @@ +#!/usr/bin/env perl + +# Copyright (c) 2020 IBM Corporation. All rights reserved. + +sub identify_mpi_libraries_for_executable { + my $exe = $_[0]; + my $cmd; + my @tmp; + my @line; + my @libs; + my $lib; + my $dir; + my $mpidir; + my %libs_dir; + + %libs_dir = (); + @tmp = split(/\n/, `ldd $exe 2>/dev/null`); + for $line (@tmp) { + if ($line =~ /^.* => *([^ (]*)/) { + $lib = $1; + if ($lib =~ m#(.*)/([^/]*)#) { + $dir = $1; + if (! -z "$dir" && -d "$dir") { + $dir = `cd $dir ; pwd`; chomp $dir; + $libs_dir{$2} = $dir; + } + } + } + } + + # One of these libraries should be libmpi.so or libmpi_something.so + # and we'll keep every lib that's in the same directory as that lib + $mpidir = ''; + for $lib (keys(%libs_dir)) { + if ($lib =~ /libmpi_.*\./) { + $mpidir = $libs_dir{$lib}; + } + } + for $lib (keys(%libs_dir)) { + if ($lib =~ /libmpi\./) { + $mpidir = $libs_dir{$lib}; + } + } + @libs = (); + for $lib (keys(%libs_dir)) { + if ($libs_dir{$lib} eq $mpidir) { + push(@libs, "$mpidir/$lib"); + } + } + + # Update: in SMPI at least we also put libevent in our /lib + # and even though those symbols could be a problem too, I lean toward + # not caling them out here. + @libs = grep(!/^libevent/, @libs); + + # I don't want to hard-code too much into this, but I'd like + # to artificually make sure the fortran libraries are checked + # and those wouldn't naturally show up for a C program. + @tmp = split(/\n/, `cd $mpidir ; ls libmpi*_mpifh.so libmpi*_usempi.so libmpi_usempi_ignore_tkr.so libmpi_usempif08.so 2>/dev/null`); + for $lib (@tmp) { + if (-e "$mpidir/$lib") { + push(@libs, "$mpidir/$lib"); + } + } + + # print join("\n", @libs), "\n"; + + return(@libs); +} + +sub main { + $x = @ARGV[0]; + if (! -e "$x") { + print("One argument required: MPI executable\n"); + exit(-1); + } + @libs = identify_mpi_libraries_for_executable($x); + + print "Checking for bad symbol names:\n"; + $isbad = 0; + for $lib (@libs) { + print "*** checking $lib\n"; + check_lib_for_bad_exports($lib); + } + if ($isbad) { exit(-1); } +} + +sub check_lib_for_bad_exports { + my $lib = $_[0]; + my @symbols; + my $s; + + @symbols = get_nm($lib, 'all'); + + # grep to get rid of symbol prefixes that are considered acceptable, + # leaving behind anything bad: + @symbols = grep(!/^ompi_/i, @symbols); + @symbols = grep(!/^ompix_/i, @symbols); + @symbols = grep(!/^opal_/i, @symbols); + @symbols = grep(!/^orte_/i, @symbols); + @symbols = grep(!/^orted_/i, @symbols); + @symbols = grep(!/^oshmem_/i, @symbols); + @symbols = grep(!/^libnbc_/i, @symbols); + @symbols = grep(!/^mpi_/i, @symbols); + @symbols = grep(!/^mpix_/i, @symbols); + @symbols = grep(!/^mpiext_/i, @symbols); + @symbols = grep(!/^pmpi_/i, @symbols); + @symbols = grep(!/^pmpix_/i, @symbols); + @symbols = grep(!/^pmix_/i, @symbols); + @symbols = grep(!/^pmix2x_/i, @symbols); + @symbols = grep(!/^PMI_/i, @symbols); + @symbols = grep(!/^PMI2_/i, @symbols); + @symbols = grep(!/^MPIR_/, @symbols); + @symbols = grep(!/^MPIX_/, @symbols); + @symbols = grep(!/^mpidbg_dll_locations$/, @symbols); + @symbols = grep(!/^mpimsgq_dll_locations$/, @symbols); + @symbols = grep(!/^ompit_/i, @symbols); + @symbols = grep(!/^ADIO_/i, @symbols); + @symbols = grep(!/^ADIOI_/i, @symbols); + @symbols = grep(!/^MPIO_/i, @symbols); + @symbols = grep(!/^MPIOI_/i, @symbols); + @symbols = grep(!/^MPIU_/i, @symbols); + @symbols = grep(!/^NBC_/i, @symbols); # seems sketchy to me + @symbols = grep(!/^tm_/, @symbols); # tempted to require ompi_ here + @symbols = grep(!/^mca_/, @symbols); + @symbols = grep(!/^smpi_/, @symbols); + + @symbols = grep(!/^_fini$/, @symbols); + @symbols = grep(!/^_init$/, @symbols); + @symbols = grep(!/^_edata$/, @symbols); + @symbols = grep(!/^_end$/, @symbols); + @symbols = grep(!/^__bss_start$/, @symbols); + @symbols = grep(!/^__malloc_initialize_hook$/, @symbols); + + # Fortran compilers can apparently put some odd symbols in through + # no fault of OMPI code. I've at least seen "D &&N&mpi_types" created + # by xlf from module mpi_types. What we're trying to catch with this + # testcase are OMPI bugs that need fixed, and I don't think OMPI is + # likely to be creating such symbols through other means, so I'm + # inclined to ignore any non-typical starting char as long as it's + # in one of the fortran libs. + if ($lib =~ /libmpi.*_usempi\./ || + $lib =~ /libmpi.*_usempi_ignore_tkr\./ || + $lib =~ /libmpi.*_usempif08\./) + { + @symbols = grep(!/^[^a-zA-Z0-9_]/, @symbols); + + @symbols = grep(!/^__mpi_/, @symbols); + @symbols = grep(!/^_mpi_/, @symbols); + @symbols = grep(!/^__pmpi_/, @symbols); + @symbols = grep(!/^_pmpi_/, @symbols); + @symbols = grep(!/^__mpiext_/, @symbols); + @symbols = grep(!/^_mpiext_/, @symbols); + @symbols = grep(!/^__ompi_/, @symbols); + @symbols = grep(!/^_ompi_/, @symbols); + @symbols = grep(!/^pompi_buffer_detach/, @symbols); + } + + if ($lib =~ /libpmix\./) { + # I'm only making this exception since the construct_dictionary.py + # that creates dictionary.h is in a separate pmix repot. + @symbols = grep(!/^dictionary$/, @symbols); + } + + for $s (@symbols) { + print " [error] $s\n"; + $isbad = 1; + } +} + +# get_nm /path/to/some/libfoo.so + +sub get_nm { + my $lib = $_[0]; + my $mode = $_[1]; + my $search_char; + my @tmp; + my @symbols; + + $search_char = "TWBCDVR"; + if ($mode eq 'func') { $search_char = "T"; } + if ($mode eq 'wfunc') { $search_char = "W"; } + + @symbols = (); + @tmp = split(/\n/, `nm $lib 2>/dev/null`); + for $line (@tmp) { + if ($line =~ /.* [$search_char] +([^ ]*)/) { + push(@symbols, $1); + } + } + + @symbols = sort(@symbols); + # print join("\n", @symbols), "\n"; + + return(@symbols); +} + +main(); diff --git a/packaging/run_nmcheck.c b/packaging/run_nmcheck.c new file mode 100644 index 0000000..79e7a42 --- /dev/null +++ b/packaging/run_nmcheck.c @@ -0,0 +1,26 @@ +/* + * $HEADER$ + * + * Run nmcheck_prefix.pl on itself + * only needs to be run with one rank + */ + +#include +#include +#include + +int main (int argc, char *argv[]) { + char cmd[256]; + int myrank; + int rv; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &myrank); + if (myrank == 0) { + sprintf(cmd, "./nmcheck_prefix.pl \"%s\"", argv[0]); + rv = system(cmd); + if (rv) { MPI_Abort(MPI_COMM_WORLD, MPI_ERR_OTHER); } + } + MPI_Finalize(); + return 0; +}