|  | #!/bin/bash | 
|  | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  | # | 
|  | # Saves the gdb index for a given binary and its shared library dependencies. | 
|  | # | 
|  | # This will run gdb index in parallel on a number of binaries using SIGUSR1 | 
|  | # as the communication mechanism to simulate a semaphore. Because of the | 
|  | # nature of this technique, using "set -e" is very difficult. The SIGUSR1 | 
|  | # terminates a "wait" with an error which we need to interpret. | 
|  | # | 
|  | # When modifying this code, most of the real logic is in the index_one_file | 
|  | # function. The rest is cleanup + sempahore plumbing. | 
|  |  | 
|  | # Cleanup temp directory and ensure all child jobs are dead-dead. | 
|  | function on_exit { | 
|  | trap "" EXIT USR1  # Avoid reentrancy. | 
|  |  | 
|  | local jobs=$(jobs -p) | 
|  | if [ -n "$jobs" ]; then | 
|  | echo -n "Killing outstanding index jobs..." | 
|  | kill -KILL $(jobs -p) | 
|  | wait | 
|  | echo "done" | 
|  | fi | 
|  |  | 
|  | if [ -f "$DIRECTORY" ]; then | 
|  | echo -n "Removing temp directory $DIRECTORY..." | 
|  | rm -rf $DIRECTORY | 
|  | echo done | 
|  | fi | 
|  | } | 
|  |  | 
|  | # Add index to one binary. | 
|  | function index_one_file { | 
|  | local file=$1 | 
|  | local basename=$(basename "$file") | 
|  | local should_index="${SHOULD_INDEX}" | 
|  |  | 
|  | local readelf_out=$(readelf -S "$file") | 
|  | if [[ $readelf_out =~ "gdb_index" ]]; then | 
|  | if [ "${REMOVE_INDEX}" = 1 ]; then | 
|  | objcopy --remove-section .gdb_index "$file" | 
|  | echo "Removed index from $basename." | 
|  | else | 
|  | echo "Skipped $basename -- already contains index." | 
|  | should_index=0 | 
|  | fi | 
|  | fi | 
|  |  | 
|  | if [ "${should_index}" = 1 ]; then | 
|  | local start=$(date +"%s%N") | 
|  | echo "Adding index to $basename..." | 
|  |  | 
|  | gdb -batch "$file" -ex "save gdb-index $DIRECTORY" -ex "quit" | 
|  | local index_file="$DIRECTORY/$basename.gdb-index" | 
|  | if [ -f "$index_file" ]; then | 
|  | objcopy --add-section .gdb_index="$index_file" \ | 
|  | --set-section-flags .gdb_index=readonly "$file" "$file" | 
|  | local finish=$(date +"%s%N") | 
|  | local elappsed=$(((finish - start)/1000000)) | 
|  | echo "   ...$basename indexed. [${elappsed}ms]" | 
|  | else | 
|  | echo "   ...$basename unindexable." | 
|  | fi | 
|  | fi | 
|  | } | 
|  |  | 
|  | # Functions that when combined, concurrently index all files in FILES_TO_INDEX | 
|  | # array. The global FILES_TO_INDEX is declared in the main body of the script. | 
|  | function async_index { | 
|  | # Start a background subshell to run the index command. | 
|  | { | 
|  | index_one_file $1 | 
|  | kill -SIGUSR1 $$  # $$ resolves to the parent script. | 
|  | exit 129  # See comment above wait loop at bottom. | 
|  | } & | 
|  | } | 
|  |  | 
|  | CUR_FILE_NUM=0 | 
|  | function index_next { | 
|  | if (( CUR_FILE_NUM >= ${#FILES_TO_INDEX[@]} )); then | 
|  | return | 
|  | fi | 
|  |  | 
|  | async_index "${FILES_TO_INDEX[CUR_FILE_NUM]}" | 
|  | ((CUR_FILE_NUM += 1)) || true | 
|  | } | 
|  |  | 
|  |  | 
|  | ######## | 
|  | ### Main body of the script. | 
|  |  | 
|  | REMOVE_INDEX=0 | 
|  | SHOULD_INDEX=1 | 
|  | while getopts ":f:r" opt; do | 
|  | case $opt in | 
|  | f) | 
|  | REMOVE_INDEX=1 | 
|  | shift | 
|  | ;; | 
|  | r) | 
|  | REMOVE_INDEX=1 | 
|  | SHOULD_INDEX=0 | 
|  | shift | 
|  | ;; | 
|  | *) | 
|  | echo "Invalid option: -$OPTARG" >&2 | 
|  | ;; | 
|  | esac | 
|  | done | 
|  |  | 
|  | if [[ ! $# == 1 ]]; then | 
|  | echo "Usage: $0 [-f] [-r] path-to-binary" | 
|  | echo "  -f forces replacement of an existing index." | 
|  | echo "  -r removes the index section." | 
|  | exit 1 | 
|  | fi | 
|  |  | 
|  | FILENAME="$1" | 
|  | if [[ ! -f "$FILENAME" ]]; then | 
|  | echo "Path $FILENAME does not exist." | 
|  | exit 1 | 
|  | fi | 
|  |  | 
|  | # Ensure we cleanup on on exit. | 
|  | trap on_exit EXIT | 
|  |  | 
|  | # We're good to go! Create temp directory for index files. | 
|  | DIRECTORY=$(mktemp -d) | 
|  | echo "Made temp directory $DIRECTORY." | 
|  |  | 
|  | # Create array with the filename and all shared libraries that | 
|  | # have the same dirname. The dirname is a signal that these | 
|  | # shared libraries were part of the same build as the binary. | 
|  | declare -a FILES_TO_INDEX=($FILENAME | 
|  | $(ldd "$FILENAME" 2>/dev/null \ | 
|  | | grep $(dirname "$FILENAME") \ | 
|  | | sed "s/.*[ \t]\(.*\) (.*/\1/") | 
|  | ) | 
|  |  | 
|  | # Start concurrent indexing. | 
|  | trap index_next USR1 | 
|  |  | 
|  | # 4 is an arbitrary default. When changing, remember we are likely IO bound | 
|  | # so basing this off the number of cores is not sensible. | 
|  | INDEX_TASKS=${INDEX_TASKS:-4} | 
|  | for ((i=0;i<${INDEX_TASKS};i++)); do | 
|  | index_next | 
|  | done | 
|  |  | 
|  | # Do a wait loop. Bash waits that terminate due a trap have an exit | 
|  | # code > 128. We also ensure that our subshell's "normal" exit occurs with | 
|  | # an exit code > 128. This allows us to do consider a > 128 exit code as | 
|  | # an indication that the loop should continue. Unfortunately, it also means | 
|  | # we cannot use set -e since technically the "wait" is failing. | 
|  | wait | 
|  | while (( $? > 128 )); do | 
|  | wait | 
|  | done |