Home | History | Annotate | Download | only in build
      1 #!/bin/bash
      2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 #
      6 # Saves the gdb index for a given binary and its shared library dependencies.
      7 #
      8 # This will run gdb index in parallel on a number of binaries using SIGUSR1
      9 # as the communication mechanism to simulate a semaphore. Because of the
     10 # nature of this technique, using "set -e" is very difficult. The SIGUSR1
     11 # terminates a "wait" with an error which we need to interpret.
     12 #
     13 # When modifying this code, most of the real logic is in the index_one_file
     14 # function. The rest is cleanup + sempahore plumbing.
     15 
     16 function usage_exit {
     17   echo "Usage: $0 [-f] [-r] [-n] <paths-to-binaries>..."
     18   echo "  -f forces replacement of an existing index."
     19   echo "  -r removes the index section."
     20   echo "  -n don't extract the dependencies of each binary with lld."
     21   echo "       e.g., $0 -n out/Debug/lib.unstripped/lib*"
     22   echo
     23   echo "  Set TOOLCHAIN_PREFIX to use a non-default set of binutils."
     24   exit 1
     25 }
     26 
     27 # Cleanup temp directory and ensure all child jobs are dead-dead.
     28 function on_exit {
     29   trap "" EXIT USR1  # Avoid reentrancy.
     30 
     31   local jobs=$(jobs -p)
     32   if [ -n "$jobs" ]; then
     33     echo -n "Killing outstanding index jobs..."
     34     kill -KILL $(jobs -p)
     35     wait
     36     echo "done"
     37   fi
     38 
     39   if [ -f "$directory" ]; then
     40     echo -n "Removing temp directory $directory..."
     41     rm -rf "$directory"
     42     echo done
     43   fi
     44 }
     45 
     46 # Add index to one binary.
     47 function index_one_file {
     48   local file=$1
     49   local basename=$(basename "$file")
     50   local should_index_this_file="${should_index}"
     51 
     52   local readelf_out=$(${TOOLCHAIN_PREFIX}readelf -S "$file")
     53   if [[ $readelf_out =~ "gdb_index" ]]; then
     54     if $remove_index; then
     55       ${TOOLCHAIN_PREFIX}objcopy --remove-section .gdb_index "$file"
     56       echo "Removed index from $basename."
     57     else
     58       echo "Skipped $basename -- already contains index."
     59       should_index_this_file=false
     60     fi
     61   fi
     62 
     63   if $should_index_this_file; then
     64     local start=$(date +"%s%N")
     65     echo "Adding index to $basename..."
     66 
     67     ${TOOLCHAIN_PREFIX}gdb -batch "$file" -ex "save gdb-index $directory" \
     68       -ex "quit"
     69     local index_file="$directory/$basename.gdb-index"
     70     if [ -f "$index_file" ]; then
     71       ${TOOLCHAIN_PREFIX}objcopy --add-section .gdb_index="$index_file" \
     72         --set-section-flags .gdb_index=readonly "$file" "$file"
     73       local finish=$(date +"%s%N")
     74       local elapsed=$(((finish - start) / 1000000))
     75       echo "   ...$basename indexed. [${elapsed}ms]"
     76     else
     77       echo "   ...$basename unindexable."
     78     fi
     79   fi
     80 }
     81 
     82 # Functions that when combined, concurrently index all files in FILES_TO_INDEX
     83 # array. The global FILES_TO_INDEX is declared in the main body of the script.
     84 function async_index {
     85   # Start a background subshell to run the index command.
     86   {
     87     index_one_file $1
     88     kill -SIGUSR1 $$  # $$ resolves to the parent script.
     89     exit 129  # See comment above wait loop at bottom.
     90   } &
     91 }
     92 
     93 cur_file_num=0
     94 function index_next {
     95   if ((cur_file_num >= ${#files_to_index[@]})); then
     96     return
     97   fi
     98 
     99   async_index "${files_to_index[cur_file_num]}"
    100   ((cur_file_num += 1)) || true
    101 }
    102 
    103 ########
    104 ### Main body of the script.
    105 
    106 remove_index=false
    107 should_index=true
    108 should_index_deps=true
    109 files_to_index=()
    110 while (($# > 0)); do
    111   case "$1" in
    112     -h)
    113       usage_exit
    114       ;;
    115     -f)
    116       remove_index=true
    117       ;;
    118     -r)
    119       remove_index=true
    120       should_index=false
    121       ;;
    122     -n)
    123       should_index_deps=false
    124       ;;
    125     -*)
    126       echo "Invalid option: $1" >&2
    127       usage_exit
    128       ;;
    129     *)
    130       if [[ ! -f "$1" ]]; then
    131         echo "Path $1 does not exist."
    132         exit 1
    133       fi
    134       files_to_index+=("$1")
    135       ;;
    136   esac
    137   shift
    138 done
    139 
    140 if ((${#files_to_index[@]} == 0)); then
    141   usage_exit
    142 fi
    143 
    144 dependencies=()
    145 if $should_index_deps; then
    146   for file in "${files_to_index[@]}"; do
    147       # Append the shared library dependencies of this file that
    148       # have the same dirname. The dirname is a signal that these
    149       # shared libraries were part of the same build as the binary.
    150       dependencies+=( \
    151         $(ldd "$file" 2>/dev/null \
    152           | grep $(dirname "$file") \
    153           | sed "s/.*[ \t]\(.*\) (.*/\1/") \
    154       )
    155   done
    156 fi
    157 files_to_index+=("${dependencies[@]}")
    158 
    159 # Ensure we cleanup on on exit.
    160 trap on_exit EXIT INT
    161 
    162 # We're good to go! Create temp directory for index files.
    163 directory=$(mktemp -d)
    164 echo "Made temp directory $directory."
    165 
    166 # Start concurrent indexing.
    167 trap index_next USR1
    168 
    169 # 4 is an arbitrary default. When changing, remember we are likely IO bound
    170 # so basing this off the number of cores is not sensible.
    171 index_tasks=${INDEX_TASKS:-4}
    172 for ((i = 0; i < index_tasks; i++)); do
    173   index_next
    174 done
    175 
    176 # Do a wait loop. Bash waits that terminate due a trap have an exit
    177 # code > 128. We also ensure that our subshell's "normal" exit occurs with
    178 # an exit code > 128. This allows us to do consider a > 128 exit code as
    179 # an indication that the loop should continue. Unfortunately, it also means
    180 # we cannot use set -e since technically the "wait" is failing.
    181 wait
    182 while (($? > 128)); do
    183   wait
    184 done
    185