diff options
Diffstat (limited to 'tools/apbuild/relaytool')
-rwxr-xr-x | tools/apbuild/relaytool | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/tools/apbuild/relaytool b/tools/apbuild/relaytool new file mode 100755 index 00000000..40b6de03 --- /dev/null +++ b/tools/apbuild/relaytool @@ -0,0 +1,584 @@ +#!/bin/bash + +# relaytool 1.2 +# Copyright 2004-2005 Mike Hearn <mike@plan99.net> +# Copyright 2005 Vincent Béron <vberon@mecano.gme.usherb.ca> +# Copyright 2006 Psyche <psyche@ruidoabsurdo.com> +# Copyright 2007 Taj Morton <tajmorton@gmail.com> +# +############################################################################# +# +# 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 <<EOF >"$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 <mike@navi.cx> + * See http://autopackage.org/ for details. + */ +#include <dlfcn.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/mman.h> + +#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 <<EOF >>"$outfile" +}; + +static char *variables[] = { +EOF + + for s in $variables; do + echo " \"$s\"," >>"$outfile" + done + echo " 0" >>"$outfile" + + cat <<EOF >>"$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 <<EOF >>"$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 <<EOF >>"$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 <<EOF >>"$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 <<EOF >>"$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 <<EOF >"$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 <mike@navi.cx> + * 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 |