#!/bin/bash # relaytool 1.2 # Copyright 2004-2005 Mike Hearn # Copyright 2005 Vincent Béron # Copyright 2006 Psyche # Copyright 2007 Taj Morton # ############################################################################# # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ############################################################################# # # TODO: # - Figure out how to grab the GOT addr on PowerPC # - Port to more archs # - Figure out a way to check if we're on an ELF platform or not, # maybe check for _GLOBAL_OFFSET_TABLE_ ? using_partial_map=false using_minimal_list=false using_multilink=false outdir="." if [[ "$1" == "--version" ]]; then echo "Relaytool 1.11" echo "Copyright 2004 Mike Hearn" echo "Copyright 2005 Vincent Béron" echo echo "See $0 for license details." exit 1 fi if [[ "$@" == "" ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then echo "Relaytool will generate a file that can be used instead of linking" echo "directly against a library, which will dlopen the DSO at init time" echo echo "Usage: relaytool [OPTION]... [LINKER COMMAND]..." echo echo "Options:" echo " --relay LIB If a matching -lLIB is found, generate a file" echo " that can be used instead of linking directly to" echo " LIB. The name of the file is echoed on stdout." echo " Multiple --relay can be used together, a file will" echo " be generated for each matching ones." echo " --replace-all-libs Generate a file for every -lLIB parameter." echo " --minimal-list OBJ_LIST Will look in OBJ_LIST for undefined symbols, and" echo " generate a file creating only the needed symbols" echo " for each LIB." echo " --partial-map MAP_FILE Generate a file creating only the symbols contained" echo " in MAP_FILE. Will apply to all further -lLIB" echo " parameters, so in general is not suitable to" echo " multiple libs in the same invocation of relaytool." echo " --no-replace Echo -lLIB on stdout even if a --relay LIB is" echo " found, so it'll be linked in normally." echo " --multilink [SONAMES...] If a library has different SONAMES on different" echo " Linux distributions you can specify the various" echo " SONAMES that it's known by here. Relaytool will" echo " attempt to load them (in the order provided) until" echo " one if found. This cannot be used with multiple" echo " --relay options. The first SONAME in the list will" echo " be used as the name in the _is_present variable and" echo " _symbol_is_present function." echo " --out-dir DIRECTORY Write stub file to DIRECTORY instead of CWD." echo "Linker commands:" echo " -LPATH Add PATH to the list of paths to search for LIBs." echo " -lLIB If a matching --relay LIB is found (or if" echo " --replace-all-libs is specified), generate a file" echo " that can be used instead of linking directly to" echo " LIB. If there's no --relay LIB, echo -lLIB to" echo " stdout." echo " All other linker commands are passed as is to stdout." echo "Other commands:" echo " --help|-h Print this help." echo " --version Print version information." exit 1 fi function error() { echo $@ >/dev/stderr exit 1 } function readfinallink() { link_name="$1" while [ -L "$link_name" ]; do new_name=$( readlink "$link_name" ) if [ "${new_name:0:1}" == "/" ]; then link_name="$new_name" else link_name="`dirname "$link_name"`/$new_name" fi done if [ -f "$link_name" ]; then echo -n "$link_name" exit 0 else exit 1 fi } function relay() { lib="$1" if $using_multilink; then libname=$( echo $( basename ${multilinklist[0]} ) | sed 's/\.so.*//' | tr '-' '_' | tr '.' '_' ) else libname=$( echo $( basename "$lib" ) | sed 's/\.so.*//' | tr '-' '_' | tr '.' '_' ) fi soname=$( objdump -x "$lib" |grep SONAME | awk '{print $2}' ) outfile="$outdir/`basename "$soname"`.stub.c" echo -n "$outfile" if $using_partial_map; then functions=$( grep "^F " "$partial_map" | cut -d' ' -f2 ) variables=$( grep "^V " "$partial_map" | cut -d' ' -f2 ) else functions=$( nm --extern-only -D "$lib" | awk '{ if (($2 == "T") || ($2 == "W")) print $3; }' | LC_ALL=C grep -v '\(\<_init\>\|\<_fini\>\)' | LC_ALL=C sort -u ) variables=$( nm --extern-only -D "$lib" | awk '{ if (($2 == "D") || ($2 == "G") || ($2 == "B") || ($2 == "V")) print $3; }' | LC_ALL=C sort -u ) fi if $using_minimal_list; then functions="$functions $( nm `echo "$object_list"` | awk '{ if ($1 == "U") print $2; }' | LC_ALL=C sort -u )" functions=$( echo "$functions" | LC_ALL=C sort | LC_ALL=C uniq -d ) variables="$variables $( nm `echo "$object_list"` | awk '{ if ($1 == "U") print $2; }' | LC_ALL=C sort -u )" variables=$( echo "$variables" | LC_ALL=C sort | LC_ALL=C uniq -d ) fi if [ "$functions" == "" ] && [ "$variables" == "" ]; then # Nothing will be used, so do nothing for that lib exit 1 fi cat <"$outfile" /* automatically generated: `date` by `id -un`@`uname -n`, do not edit * * Built by relaytool, a program for building delay-load jumptables * relaytool is (C) 2004 Mike Hearn * See http://autopackage.org/ for details. */ #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif static void **ptrs; static char *functions[] = { EOF for s in $functions; do echo " \"$s\"," >>"$outfile" done echo " 0" >>"$outfile" cat <>"$outfile" }; static char *variables[] = { EOF for s in $variables; do echo " \"$s\"," >>"$outfile" done echo " 0" >>"$outfile" cat <>"$outfile" }; /* 1 if present, 0 if not */ int ${libname}_is_present = 0; static void *handle = 0; /* 1 if present, 0 if not, 0 with warning to stderr if lib not present or symbol not found */ int ${libname}_symbol_is_present(char *s) { int i; if( !${libname}_is_present ) { fprintf(stderr, "%s: relaytool: `basename "$lib"` not present so cannot check for symbol %s.\n", getenv("_"), s); fprintf(stderr, "%s: relaytool: This probably indicates a bug in the application, please report.\n", getenv("_")); return 0; } i = 0; while (functions[i++]) if (!strcmp( functions[i - 1], s )) return ptrs[i - 1] > 0 ? 1 : 0; i = 0; while (variables[i++]) if (!strcmp( variables[i - 1], s )) return dlsym( handle, s ) > 0 ? 1 : 0; fprintf( stderr, "%s: relaytool: %s is an unknown symbol in `basename "$lib"`.\n", getenv("_"), s ); fprintf( stderr, "%s: relaytool: If you are the developer of this program, please correct the symbol name or rerun relaytool.\n", getenv("_") ); return 0; } __attribute__((noreturn)) void _relaytool_stubcall_${libname}(int offset) { fprintf( stderr, "%s: relaytool: stub call to `basename "${lib}"`:%s, aborting.\n", getenv("_"), functions[offset / sizeof(void*)] ); exit( 1 ); } #if defined( __i386__ ) #define FIXUP_GOT_RELOC(sym, addr) \\ asm("\tmovl %0, %%eax\n" \\ "\tmovl %%eax, " sym "@GOT(%%ebx)\n" : : "r" (addr)); #elif defined( __powerpc__ ) /* The PowerPC ELF ABI is a twisted nightmare. Until I figure it out, for now we don't support GOT fixup on this architecture */ #error Variables are not currently supported on PowerPC #elif defined( __x86_64__ ) #define FIXUP_GOT_RELOC(sym, addr) \\ asm("\tmovq %0, %%rax\n" \\ "\tmovq %%rax, " sym "@GOT(%%rbx)\n" : : "r" (addr)); #else #error Please define FIXUP_GOT_RELOC for your architecture #endif void __attribute__((constructor)) _relaytool_init_${libname}() { int i = 0; ptrs = malloc( sizeof(functions) ); memset( ptrs, 0, sizeof(functions) ); EOF if $using_multilink; then echo -n "char *multilink_libs[${#multilinklist[@]}] = {" | cat >> "$outfile" for l in ${multilinklist[@]}; do echo -n "\"$l\"" | cat >> "$outfile"; if [[ "$l" != "${multilinklist[${#multilinklist[@]}-1]}" ]]; then echo -n ", " | cat >> "$outfile"; else echo "};" | cat >> "$outfile" fi done echo 'int multilink_count=0;' | cat >> "$outfile" echo 'while (!handle) { handle = dlopen(multilink_libs[multilink_count++], RTLD_LAZY );' | cat >> "$outfile" echo "if (multilink_count==${#multilinklist[@]}) break;}"| cat >> "$outfile" else echo "handle = dlopen( \"$soname\", RTLD_LAZY );" | cat >> "$outfile" fi cat <>"$outfile" if (!handle) return; ${libname}_is_present = 1; /* build function jumptable */ while (functions[i++]) ptrs[i - 1] = dlsym( handle, functions[i - 1] ); EOF if [ "$variables" != "" ]; then echo " /* now fixup the global offset table for variable imports */" >>"$outfile"; fi for s in $variables; do echo " FIXUP_GOT_RELOC( \"$s\", dlsym(handle, \"$s\") );" >>"$outfile" done cat <>"$outfile" } #if defined( __i386__ ) #define JUMP_SLOT(name, index) \\ asm(".section .text." name ", \"ax\", @progbits\n" \\ ".globl " name "\n" \\ ".hidden " name "\n" \\ " .type " name ", @function\n" \\ name ":\n" \\ " movl ptrs, %eax\n" \\ " movl " #index "(%eax), %eax\n" \\ " test %eax, %eax\n" \\ " jnz JS" #index "\n" \\ " push \$" #index "\n" \\ " call _relaytool_stubcall_${libname}\n" \\ "JS" #index ": jmp *%eax\n"); #elif defined( __x86_64__ ) #define JUMP_SLOT(name, index) \\ asm(".section .text." name ", \"ax\", @progbits\n" \\ ".globl " name "\n" \\ ".hidden " name "\n" \\ " .type " name ", @function\n" \\ name ":\n" \\ " movq ptrs, %r11\n" \\ " movq " #index "(%r11), %r11\n" \\ " test %r11, %r11\n" \\ " jnz JS" #index "\n" \\ " push $" #index "\n" \\ " call _relaytool_stubcall_${libname}\n" \\ "JS" #index ": jmp *%r11\n"); #elif defined( __powerpc__ ) #define JUMP_SLOT(name, index) \ \ asm(".section .text." name ", \"ax\", @progbits\n" \\ ".globl " name "\n" \\ ".hidden " name "\n" \\ " .type " name ", @function\n" \\ name ":\n" \\ " lis r11, ptrs@ha\n" \\ " lwz r11, " #index "(r11)\n" \\ " cmpi cr0,r11,0\n" \\ " beq- 1f\n" \\ " mtctr r11\n" \\ " bctr\n" \\ "1: li r3, " #index "\n" \\ " b _relaytool_stubcall_${libname}\n" \\ ); #else #error Please define JUMP_SLOT for your architecture #endif /* define placeholders for the variable imports: their type doesn't matter, however we must restrict ELF symbol scope to prevent the definition in the imported shared library being bound to this dummy symbol (not all libs are compiled -Bsymbolic) */ EOF for s in $variables; do echo "int $s __attribute__(( visibility(\"hidden\") )) = -1;" >>"$outfile" done cat <>"$outfile" /* define each jump slot in its own section. this increases generated code size, but it means unused slots can be deleted by the linker when --gc-sections is used. */ EOF # now generate the stubs c=0 for s in $functions; do echo "JUMP_SLOT(\"$s\", $[c * $arch_ptr_size]);" >>"$outfile" (( c++ )) done echo >>"$outfile" cat <>"$outfile" #ifdef __cplusplus } #endif EOF } function fakerelay() { lib="$1" libname=$( echo $( basename "$lib" ) | sed 's/\.so.*//' | tr '-' '_' | tr '.' '_' ) soname=$( objdump -x "$lib" |grep SONAME | awk '{print $2}' ) outfile="$outdir/`basename "$soname"`.stub.c" echo -n "$outfile" cat <"$outfile" /* automatically generated: `date` by `id -un`@`uname -n`, do not edit * * Built by relaytool, a program for building delay-load jumptables * relaytool is (C) 2004 Mike Hearn * See http://autopackage.org/ for details. */ #ifdef __cplusplus extern "C" { #endif /* 1 if present, 0 if not */ int ${libname}_is_present = 1; /* 1 if present, 0 if not, 0 with warning to stderr if lib not present or symbol not found */ int ${libname}_symbol_is_present(char * s) { return 1; } #ifdef __cplusplus } #endif EOF } no_replace=false replace_all=false arch_ok=false arch_ptr_size=0 case `uname -m` in i386 | i486 | i586 | i686 ) arch_ok=true arch_ptr_size=4 ;; x86_64) arch_ok=true arch_ptr_size=8 ;; esac searchpath=( "/usr/lib" "/usr/local/lib" "/lib" `pwd` ) multilinklist=( ) relaylist=( ) # process arguments i=1 while (( i <= $# )); do a="${!i}" if [ "${a:0:2}" == "-L" ]; then searchpath[${#searchpath}]="${a:2}" echo -n "$a " # copy to stdout elif [ "$a" == "--replace-all-libs" ]; then replace_all=true elif [ "$a" == "--partial-map" ]; then using_partial_map=true (( i++ )) partial_map="${!i}" elif [ "$a" == "--minimal-list" ]; then using_minimal_list=true (( i++ )) object_list="${!i}" elif [ "$a" == "--no-replace" ]; then no_replace=true elif [ "$a" == "--multilink" ]; then using_multilink=true (( i++ )) while [[ $i -lt $# && ${!i:0:2} != "--" ]]; do multilinklist[${#multilinklist[@]}]="${!i}" (( i++ )) done continue # $i has already been incremented, just continue with the loop elif [ "$a" == "--relay" ]; then (( i++ )) relaylist[${#relaylist[@]}]="${!i}" elif [ "$a" == "--out-dir" ]; then (( i++ )) outdir="${!i}" elif [ "$a" == "-ldl" ]; then # libdl won't ever be supported by relaytool, so just pass it to stdout echo -n "$a " elif [ "${a:0:2}" == "-l" ]; then lib="${a:2}" # is this lib meant to be relayed? if $replace_all; then found=true else found=false for b in ${relaylist[@]}; do if [ "$b" == "$lib" ]; then found=true fi done fi if $found && $arch_ok; then # yes, so let's find its absolute filename by checking in each search path directory spfound=false for d in ${searchpath[@]}; do if [ -e "$d/lib$lib.so" ]; then absname=$( readfinallink "$d/lib$lib.so" ) if [ $? != 0 ] || [ ! -f "$absname" ]; then error broken symlink "$absname" fi stubfile=$( relay "$absname" ) # now we have to compile the stub if [ $? == 0 ]; then stubobj=$( echo "$stubfile" | sed 's/\.c$/\.o/' ) # remove -include flags from CFLAGS, if any CFLAGS=$( echo $CFLAGS | sed 's/-include .*\.h//g' ) if [ -e /dev/tty ]; then ${CC:-gcc} ${CFLAGS} -fPIC -c -o "$stubobj" "$stubfile" 2>/dev/tty else ${CC:-gcc} ${CFLAGS} -fPIC -c -o "$stubobj" "$stubfile" fi echo -n "$stubobj " fi if $no_replace; then echo -n "-l$lib " fi spfound=true break; fi done if ! $spfound; then error could not find "$lib" in search path fi elif $found && ! $arch_ok; then # yes, so let's find its absolute filename by checking in each search path directory spfound=false for d in ${searchpath[@]}; do if [ -e "$d/lib$lib.so" ]; then absname=$( readfinallink "$d/lib$lib.so" ) if [ $? != 0 ] || [ ! -f "$absname" ]; then error broken symlink "$absname" fi # Create a stub C source that just contains dummy # libwhatever_... support functions stubfile=$( fakerelay "$absname" ) # now we have to compile the stub if [ $? == 0 ]; then stubobj=$( echo "$stubfile" | sed 's/\.c$/\.o/' ) # remove -include flags from CFLAGS, if any CFLAGS=$( echo $CFLAGS | sed 's/-include .*\.h//g' ) if [ -e /dev/tty ]; then ${CC:-gcc} ${CFLAGS} -fPIC -c -o "$stubobj" "$stubfile" 2>/dev/tty else ${CC:-gcc} ${CFLAGS} -fPIC -c -o "$stubobj" "$stubfile" fi echo -n "$stubobj " fi if $no_replace; then echo -n "-l$lib " fi spfound=true break; fi done if ! $spfound; then error could not find "$lib" in search path fi else echo -n "$a " fi else # just copy whatever we don't recognise echo -n "$a " fi (( i++ )) done echo