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 # Cleanup temp directory and ensure all child jobs are dead-dead.
     17 function on_exit {
     18   trap "" EXIT USR1  # Avoid reentrancy.
     19 
     20   local jobs=$(jobs -p)
     21   if [ -n "$jobs" ]; then
     22     echo -n "Killing outstanding index jobs..."
     23     kill -KILL $(jobs -p)
     24     wait
     25     echo "done"
     26   fi
     27 
     28   if [ -f "$DIRECTORY" ]; then
     29     echo -n "Removing temp directory $DIRECTORY..."
     30     rm -rf $DIRECTORY
     31     echo done
     32   fi
     33 }
     34 
     35 # Add index to one binary.
     36 function index_one_file {
     37   local file=$1
     38   local basename=$(basename "$file")
     39   local should_index="${SHOULD_INDEX}"
     40 
     41   local readelf_out=$(readelf -S "$file")
     42   if [[ $readelf_out =~ "gdb_index" ]]; then
     43     if [ "${REMOVE_INDEX}" = 1 ]; then
     44       objcopy --remove-section .gdb_index "$file"
     45       echo "Removed index from $basename."
     46     else
     47       echo "Skipped $basename -- already contains index."
     48       should_index=0
     49     fi
     50   fi
     51 
     52   if [ "${should_index}" = 1 ]; then
     53     local start=$(date +"%s%N")
     54     echo "Adding index to $basename..."
     55 
     56     gdb -batch "$file" -ex "save gdb-index $DIRECTORY" -ex "quit"
     57     local index_file="$DIRECTORY/$basename.gdb-index"
     58     if [ -f "$index_file" ]; then
     59       objcopy --add-section .gdb_index="$index_file" \
     60         --set-section-flags .gdb_index=readonly "$file" "$file"
     61       local finish=$(date +"%s%N")
     62       local elappsed=$(((finish - start)/1000000))
     63       echo "   ...$basename indexed. [${elappsed}ms]"
     64     else
     65       echo "   ...$basename unindexable."
     66     fi
     67   fi
     68 }
     69 
     70 # Functions that when combined, concurrently index all files in FILES_TO_INDEX
     71 # array. The global FILES_TO_INDEX is declared in the main body of the script.
     72 function async_index {
     73   # Start a background subshell to run the index command.
     74   {
     75     index_one_file $1
     76     kill -SIGUSR1 $$  # $$ resolves to the parent script.
     77     exit 129  # See comment above wait loop at bottom.
     78   } &
     79 }
     80 
     81 CUR_FILE_NUM=0
     82 function index_next {
     83   if (( CUR_FILE_NUM >= ${#FILES_TO_INDEX[@]} )); then
     84     return
     85   fi
     86 
     87   async_index "${FILES_TO_INDEX[CUR_FILE_NUM]}"
     88   ((CUR_FILE_NUM += 1)) || true
     89 }
     90 
     91 
     92 ########
     93 ### Main body of the script.
     94 
     95 REMOVE_INDEX=0
     96 SHOULD_INDEX=1
     97 while getopts ":f:r" opt; do
     98   case $opt in
     99     f)
    100       REMOVE_INDEX=1
    101       shift
    102       ;;
    103     r)
    104       REMOVE_INDEX=1
    105       SHOULD_INDEX=0
    106       shift
    107       ;;
    108     *)
    109       echo "Invalid option: -$OPTARG" >&2
    110       ;;
    111   esac
    112 done
    113 
    114 if [[ ! $# == 1 ]]; then
    115   echo "Usage: $0 [-f] [-r] path-to-binary"
    116   echo "  -f forces replacement of an existing index."
    117   echo "  -r removes the index section."
    118   exit 1
    119 fi
    120 
    121 FILENAME="$1"
    122 if [[ ! -f "$FILENAME" ]]; then
    123   echo "Path $FILENAME does not exist."
    124   exit 1
    125 fi
    126 
    127 # Ensure we cleanup on on exit.
    128 trap on_exit EXIT
    129 
    130 # We're good to go! Create temp directory for index files.
    131 DIRECTORY=$(mktemp -d)
    132 echo "Made temp directory $DIRECTORY."
    133 
    134 # Create array with the filename and all shared libraries that
    135 # have the same dirname. The dirname is a signal that these
    136 # shared libraries were part of the same build as the binary.
    137 declare -a FILES_TO_INDEX=($FILENAME
    138  $(ldd "$FILENAME" 2>/dev/null \
    139   | grep $(dirname "$FILENAME") \
    140   | sed "s/.*[ \t]\(.*\) (.*/\1/")
    141 )
    142 
    143 # Start concurrent indexing.
    144 trap index_next USR1
    145 
    146 # 4 is an arbitrary default. When changing, remember we are likely IO bound
    147 # so basing this off the number of cores is not sensible.
    148 INDEX_TASKS=${INDEX_TASKS:-4}
    149 for ((i=0;i<${INDEX_TASKS};i++)); do
    150   index_next
    151 done
    152 
    153 # Do a wait loop. Bash waits that terminate due a trap have an exit
    154 # code > 128. We also ensure that our subshell's "normal" exit occurs with
    155 # an exit code > 128. This allows us to do consider a > 128 exit code as
    156 # an indication that the loop should continue. Unfortunately, it also means
    157 # we cannot use set -e since technically the "wait" is failing.
    158 wait
    159 while (( $? > 128 )); do
    160   wait
    161 done
    162