#!/bin/bash # This is a script to built "fat" ARM binaries that contain two separate # sets of executable code, one using the soft-float API and one using # the hard-float API. # # Copyright (c) Red Hat 2011 # Written by: Nick Clifton # Released under the LGPL v3. See: http://www.gnu.org/licenses/lgpl.html # For a description of the command line options supported by this script # see the show_help() function. # # Note: if you are modifying this script to run in a new enviroment then # you should only need to change the contents of the init_user() function. # # Note: the intention is that this script should be named "gcc" and # placed in the execution path ahead of the real gcc. Then it can # be used inside rpm build scripts and makefiles. # # Note: The latest version of this script can be found here: # FIXME: FILL IN # # To Do: # Support linking against shared libraries. # Add detection of already swapped binaries. # Add C++ support. # Support building libraries. # Support compiling and linking in one command. # Support multiple compilations on the same command line. # Detect alt API binaries that are the same as the original API binaries #---------------------------------------------------------------- # Initialise user modifiable global variables. init_user () { # The command line switch(es) to add to the end of a compilation # in order to generate the alternative API: ALT_API="-mfloat-abi=hard" # The prefix of the section name that is inserted into a binary # that contains the alternative API code: ALT_PREFIX=".note.alt-api" # The exec base for the tools to use. If this is initialised # and any of the program variables are not initialised # then they will be automatically filled in based on this variable. EXEC_BASE=/work/builds/gcc/current/arm-eabi/install/bin/arm-eabi # The compiler to use: GCC=${EXEC_BASE}-gcc # This is an example of how this variable would # be automatically initialised. # The other program variables are: # AR # OBJCOPY # OBJDUMP # READELF } #---------------------------------------------------------------- show_help () { # The following exec goop is so that we don't have to manually # redirect every message to stderr in this function. exec 4>&1 # Save stdout fd to fd #4. exec 1>&2 # Redirect stdout to stderr. cat <<__EOM__ This is a shell script that acts as a wrapper to gcc and which allows the building of "fat binaries". These are files that contain code that supports two different APIs. The normal API code is visible to other tools and the kernel, whilst the alternative API code is stored hidden inside the binary. The --fat-swap option of this script can be used to swap the two APIs over. Usage: $SCRIPT_NAME {} [|] is zero or more of: --fat-version Report the version of this script, plus the user-customisable variables. --fat-help Displays this information. --fat-verbose This tells the script to be verbose about what it is doing. The same effect can achieved by using gcc's --verbose command line option, except that that makes gcc generate verbose output as well. --fat-save-temps Do not delete any temporary files created by the script. is one of: --fat-status Reports on the status of the specified files, showing which API is in use and which is stored internally. --fat-swap Swaps the contents of the specified files so that the stored alternative version is the one that will be seen by other tools. This is a normal gcc command line. If the -c option is present then a compilation will be performed, creating a fat object file. Otherwise a link will be performed, creating a fat executable. __EOM__ exec 1>&4 # Copy stdout fd back from temporary save fd, #4 } #---------------------------------------------------------------- # Utility functions: report () { if [ $SHOW_PROGRESS -ne 0 ] then echo $SCRIPT_NAME": " ${1+"$@"} fi } verbose () { if [ $SHOW_PROGRESS -gt 1 ] || [ $SHOW_PROGRESS -lt -1 ] then report ${1+"$@"} fi } warn () { if test "x$1" != "x" ; then report "Warning: $1" fi } fail () { echo "$SCRIPT_NAME: Internal error: " ${1+"$@"} exit 1 } verbose () { if [ $SHOW_PROGRESS -gt 1 ] || [ $SHOW_PROGRESS -lt -1 ] then report ${1+"$@"} fi } init_internal () { # The version of this script: VERSION=0.4 # The name that was used to invoke this script. SCRIPT_NAME=`basename $0` # FIXME: If the $prog is "gcc" then maybe an alternative name should be used ? # Multi-state variable: # -2 => Do not do anything, be verbose about what would be done. # -1 => Do not do anything, just show what would be done. # 0 => Do not report anything, just return an exit code. # 1 => Provide a basic desription of what is going on. # 2 => Be verbose about what is going on. SHOW_PROGRESS=1 # Set to 1 if temporary files should not be deleted. SAVE_TEMPS=0 # Fill in the executable variables, if necessary. if test "x$GCC" == "x" ; then if test "x$EXEC_BASE" == "x" ; then fail "No GCC executable specified and no base name either." else GCC=${EXEC_BASE}-gcc fi fi if test "x$OBJCOPY" == "x" ; then if test "x$EXEC_BASE" == "x" ; then fail "No OBJCOPY executable specified and no base name either." else OBJCOPY=${EXEC_BASE}-objcopy fi fi if test "x$OBJDUMP" == "x" ; then if test "x$EXEC_BASE" == "x" ; then fail "No OBJDUMP executable specified and no base name either." else OBJDUMP=${EXEC_BASE}-objdump fi fi if test "x$READELF" == "x" ; then if test "x$EXEC_BASE" == "x" ; then fail "No READELF executable specified and no base name either." else READELF=${EXEC_BASE}-readelf fi fi if test "x$AR" == "x" ; then if test "x$EXEC_BASE" == "x" ; then fail "No AR executable specified and no base name either." else AR=${EXEC_BASE}-ar fi fi } # Delete a file or directory. delete_file () { if [ $SAVE_TEMPS -eq 0 ] then verbose "Deleting: $1" if [ $SHOW_PROGRESS -ge 0 ] then rm -fr $1 fi fi } # syntax: run_command [] # If being verbose report the command being run, and # the directory in which it is run. # If not performing a dry run execute the command and # issue a warning message if the command fails. # Sets RUN_RESULT to 0 upon success, 1 otherwise. run_command () { RUN_RESULT=0 verbose "Executing: $*" if [ $SHOW_PROGRESS -lt 0 ] then return fi ( $* ) && return verbose "Problems encountered whilst executing: $*" RUN_RESULT=1 } # Determine which API is in use in a given file. # Written as separate function in order to make it easier to port # this script to other architectures. uses_hard_api () { verbose "Running: $READELF --arch-specific $1 | grep --quiet Tag_ABI_HardFP_use" return `$READELF --arch-specific $1 | grep --quiet Tag_ABI_HardFP_use` } #---------------------------------------------------------------- show_version () { report "Fat Binary building script" report "Version: $VERSION" report "Copyright (c) 2011 Red Hat" report " " report "GCC executable: $GCC" report "Objcopy executable: $OBJCOPY" report "Objdump executable: $OBJDUMP" report "Readelf executable: $READELF" report "Ar executable: $AR" report "Alternative API option(s): $ALT_API" report "Alternative API section prefix: $ALT_PREFIX" } # Returns the status of the given file in FILE_STATUS. # Values are: # -1 Does not exist/Not readable # 0 Does not contain alternate API # 1 Contains alternate API, using soft-API # 2 Contains alternate API, using hard-API get_file_status () { if test "x$1" = "x" ; then fail "get_file_status needs a filename argument" fi if test "x$2" != "x" ; then fail "get_file_status only takes one argument" fi if [ -r $1 ] then if `$OBJDUMP --section-headers $1 | grep --quiet " $ALT_PREFIX"` then if uses_hard_api $1 then FILE_STATUS=2 else FILE_STATUS=1 fi else FILE_STATUS=0 fi else FILE_STATUS=-1 fi } # syntax: extract_api FILE SECTION NEW_FILE # Extracts the named section from file and stores it into the new file. extract_api () { if test "x$3" = "x" ; then fail "extract_api needs three arguments" fi if test "x$4" != "x" ; then fail "extract_api only takes three arguments" fi local input_name=$1 local section_name=$2 local output_name=$3 verbose "Extracting section $sec_name from $input_name and storing it in $output_name" run_command $OBJCOPY \ --only-section $section_name \ --output-target=binary \ --set-section-flags $section_name=alloc,contents,load \ $input_name $output_name if [ $RUN_RESULT -ne 0 ] then fail "could not extract section $section_name from $input_name" fi # Paranoia check. get_file_status $output_name if [ $FILE_STATUS -gt 0 ] then fail "extracted alternative API file '$output_name' already contains another API section!" fi verbose "Removing section $sec_name from $input_name" run_command $OBJCOPY --remove-section $section_name $input_name if [ $RUN_RESULT -ne 0 ] then fail "could not remove section $section_name from $input_name" fi } # Switch the API on the specified file. swap_file () { if test "x$1" = "x" ; then fail "swap_file needs a filename argument" fi if test "x$2" != "x" ; then fail "swap_file only takes one argument" fi local file_name=`basename $1` verbose "Swapping APIs in: $file_name" get_file_status $1 if [ $FILE_STATUS -le 0 ] then if [ $FILE_STATUS -lt 0 ] then warn "could not find/read file: $file_name" elif [ $WARN_NO_SWAP -eq 1 ] then verbose "$file_name: does not contain an alternative API binary" fi return fi local alt_api_file=`mktemp fat-alt-XXXXXXXXXX-$file_name` local sec_name=$ALT_PREFIX.$file_name extract_api $1 $sec_name $alt_api_file # Insert the original file as an alternative API section into the temporary file. run_command $OBJCOPY --add-section $sec_name=$file_name $alt_api_file if [ $RUN_RESULT -ne 0 ] then fail "Could not insert the alternative API code" fi verbose "Replace $file_name with $alt_api_file" mv $1 $1.tmp mv $alt_api_file $1 delete_file $1.tmp # Tidy up. delete_file $alt_api_file } # Switch over the APIs in use in the given files. swap_files () { if test "x$1" = "x" ; then warn "no file names specified for --fat-swap option" fi WARN_NO_SWAP=1 while [ $# -gt 0 ] do if [ -r $1 ] then swap_file $1 else warn "Unable to read file: $1" fi shift done } # Link together object files and libraries to create an executable. # Then re-link them to create an alternative-API executable and # insert this into the first executable. perform_link () { if test "x$OUTPUT_FILE" == "x" ; then OUTPUT_FILE="a.out" fi if test "x$SOURCE_FILE" != "x" ; then # FIXME: We should allow compilation and linking in one command line. fail "Source file found when object files were expected" fi if [ ${#OBJECT_FILES[*]} -eq 0 ] then warn "Nothing to do!" return fi run_command $GCC ${1+"$@"} if [ $RUN_RESULT -ne 0 ] then warn "Alternative executable not created because original link failed" return fi # Note - we do not have to find and swap any static libraries, or the object # files involved in the link. The linker will have done all of the searching # for us, and placed the alternative API files in ALT_PREFIX sections in the # executable. All we need to do is to extract *all* of the alternate API # object files from inside the linked binary and then link them using the # original command line. (This assumes that there will not be any duplicated # object file names). declare -a alt_sections verbose "Runing: $OBJDUMP --section-headers $OUTPUT_FILE | grep $ALT_PREFIX" read -a alt_sections <<<`$OBJDUMP --section-headers $OUTPUT_FILE | grep --only-matching "$ALT_PREFIX.[^ ]*"` verbose "Alt section list: ${alt_sections[*]}" local -i index=0 while [ $index -lt ${#alt_sections[*]} ] do alt_api_file=`mktemp fat-sec-XXXXXXXXXX-${alt_sections[$index]}` extract_api $OUTPUT_FILE ${alt_sections[$index]} $alt_api_file alt_sections[$index]=$alt_api_file index+=1 done # Now we duplicate the linker command line, with the object files removed # (since we will be supplying our own list). declare -a command_line while [ $# -gt 0 ] do case "$1" in *.o | *.a) ;; -o) # Remove the -o switch as well, since we supply our own output filename. shift ;; *) verbose "Add command line option: $1" command_line+=($1) ;; esac shift done # Rerun the gcc command line with the swapped binaries in place # but creating a new output file. new_output_file=`mktemp fat-aout-XXXXXXXXXX-$OUTPUT_FILE` run_command $GCC ${alt_sections[@]} ${command_line[@]} $ALT_API -o $new_output_file if [ $RUN_RESULT -ne 0 ] then fail "Alternative link failed" fi get_file_status $new_output_file if [ $FILE_STATUS -gt 0 ] then fail "New alt file still contains alternative API sections" fi # Insert the new output file into the original output file. run_command $OBJCOPY --add-section $ALT_PREFIX.`basename $OUTPUT_FILE`=$new_output_file $OUTPUT_FILE if [ $RUN_RESULT -ne 0 ] then fail "Unable to store alternative API code inside original binary" fi # Tidy up. delete_file $new_output_file index=0 while [ $index -lt ${#alt_sections[*]} ] do delete_file ${alt_sections[$index]} index+=1 done } # Compile a source file to an object file. perform_compile () { if test "x$SOURCE_FILE" == "x" ; then fail "Source file name not found" fi if test "x$OUTPUT_FILE" == "x" ; then OUTPUT_FILE="`echo $SOURCE_FILE | sed 's,.c,.o,'`" fi run_command $GCC ${1+"$@"} if [ $RUN_RESULT -ne 0 ] then verbose "Alternative compilation not run because original compilation failed." return fi local temp_file local file_name file_name=`basename $OUTPUT_FILE` temp_file=`mktemp fat-obj-XXXXXXXXXX-$file_name` run_command $GCC ${1+"$@"} $ALT_API "-o $temp_file" if [ $RUN_RESULT -ne 0 ] then fail "Alternative compilation failed" fi # FIXME: As an optimization we could check here to see if the # fat binary is essentially identical to the thin binary. If so # then there is no need to store the fat binary inside the thin one. # Store the fat version inside the thin version run_command $OBJCOPY --add-section $ALT_PREFIX.$OUTPUT_FILE=$temp_file $OUTPUT_FILE if [ $RUN_RESULT -ne 0 ] then fail "Unable to store alternative API code inside original binary" fi delete_file $temp_file } # Scans a list of files, reporting on their API status. show_status () { if test "x$1" = "x" ; then warn "no file names specified for --fat-status option" fi while [ $# -gt 0 ] do get_file_status $1 case "$FILE_STATUS" in -1) warn "Unable to read file: $1" ;; 0) report "$1: does not contain an alternative API binary" ;; 1) report "$1: uses the soft API, contains a hard API alternative" ;; 2) report "$1: uses the hard API, contains a soft API alternative" ;; *) fail "unexpected result from get_file_status" ;; esac shift done } # Parses a GCC command line. The script has already determined # that there are no script specific swithes on the command line. parse_args () { local optname optarg while [ $# -gt 0 ] do optname="`echo $1 | sed 's,=.*,,'`" optarg="`echo $1 | sed 's,^[^=]*=,,'`" case "$1" in # Look for options controlling verbosity. # FIXME: we only do a simplistic scan. Ideally we would # scan the command line twice, once for verbosity switches # and then afterwards for other types of switches. --verbose | -v) if [ $SHOW_PROGRESS -eq -1 ] then SHOW_PROGRESS=-2 else SHOW_PROGRESS=2 fi ;; --quiet | -q) if [ $SHOW_PROGRESS -gt 0 ] then SHOW_PROGRESS=0 fi ;; # Look for filenames -o) if test "x$OUTPUT_FILE" != "x" ; then fail "Multiple output file names detected" else OUTPUT_FILE=$2 verbose "Output file name: $OUTPUT_FILE" fi ;; *.c) if test "x$SOURCE_FILE" == "x" ; then SOURCE_FILE=$optname verbose "Source file: $SOURCE_FILE" else warn "Multiple source files detected. This is not currently supported." exit 1 fi ;; # FIXME: Add C++ and other source file types... # Look for compling versus linking. -c) COMPILATION=1 ;; # We are not interested in any other command line options. -*) ;; # Anything else is assumed to be a filename,. # This fails with switches that take an argument. *) OBJECT_FILES+=$optarg ;; esac shift done } # Execute the rest of the command line, after the script specific # switches have been removed. process_files () { COMPILATION=0 SOURCE_FILE="" OUTPUT_FILE="" # Create an array for object files declare -a OBJECT_FILES parse_args ${1+"$@"} if [ $COMPILATION -eq 1 ] then perform_compile ${1+"$@"} else perform_link ${1+"$@"} fi } #------------------------------------------------------- main () { # Initialise user modifiable variables. init_user # Initialise internal variables. init_internal if test "x$1" = "x" ; then warn "use the --fat-help option to see a description of this script" exit 1 fi while [ $# -gt 0 ] do # For simplicity we specify that any script # specific switch(es) must come first. case "$1" in --version | --fat-version) show_version ;; --fat-help) show_help ;; --fat-save-temps) SAVE_TEMPS=1 ;; --fat-verbose) SHOW_PROGRESS=2 ;; --fat-status) shift ; show_status ${1+"$@"} ; break ;; --fat-swap) shift ; swap_files ${1+"$@"} ; break ;; *) process_files ${1+"$@"} ; break ;; esac shift done exit 0 } #------------------------------------------------------- # Invoke main main ${1+"$@"}