| #!/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. | |
| function usage_exit { | |
| echo "Usage: $0 [-f] [-r] [-n] <paths-to-binaries>..." | |
| echo " -f forces replacement of an existing index." | |
| echo " -r removes the index section." | |
| echo " -n don't extract the dependencies of each binary with lld." | |
| echo " e.g., $0 -n out/Debug/lib.unstripped/lib*" | |
| echo | |
| echo " Set TOOLCHAIN_PREFIX to use a non-default set of binutils." | |
| exit 1 | |
| } | |
| # 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 [ -d "$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_this_file="${should_index}" | |
| local readelf_out=$(${TOOLCHAIN_PREFIX}readelf -S "$file") | |
| if [[ $readelf_out =~ "gdb_index" ]]; then | |
| if $remove_index; then | |
| ${TOOLCHAIN_PREFIX}objcopy --remove-section .gdb_index "$file" | |
| echo "Removed index from $basename." | |
| else | |
| echo "Skipped $basename -- already contains index." | |
| should_index_this_file=false | |
| fi | |
| fi | |
| if $should_index_this_file; then | |
| local start=$(date +"%s%N") | |
| echo "Adding index to $basename..." | |
| ${TOOLCHAIN_PREFIX}gdb -batch "$file" -ex "save gdb-index $directory" \ | |
| -ex "quit" | |
| local index_file="$directory/$basename.gdb-index" | |
| if [ -f "$index_file" ]; then | |
| ${TOOLCHAIN_PREFIX}objcopy --add-section .gdb_index="$index_file" \ | |
| --set-section-flags .gdb_index=readonly "$file" "$file" | |
| local finish=$(date +"%s%N") | |
| local elapsed=$(((finish - start) / 1000000)) | |
| echo " ...$basename indexed. [${elapsed}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=false | |
| should_index=true | |
| should_index_deps=true | |
| files_to_index=() | |
| while (($# > 0)); do | |
| case "$1" in | |
| -h) | |
| usage_exit | |
| ;; | |
| -f) | |
| remove_index=true | |
| ;; | |
| -r) | |
| remove_index=true | |
| should_index=false | |
| ;; | |
| -n) | |
| should_index_deps=false | |
| ;; | |
| -*) | |
| echo "Invalid option: $1" >&2 | |
| usage_exit | |
| ;; | |
| *) | |
| if [[ ! -f "$1" ]]; then | |
| echo "Path $1 does not exist." | |
| exit 1 | |
| fi | |
| files_to_index+=("$1") | |
| ;; | |
| esac | |
| shift | |
| done | |
| if ((${#files_to_index[@]} == 0)); then | |
| usage_exit | |
| fi | |
| dependencies=() | |
| if $should_index_deps; then | |
| for file in "${files_to_index[@]}"; do | |
| # Append the shared library dependencies of this file that | |
| # have the same dirname. The dirname is a signal that these | |
| # shared libraries were part of the same build as the binary. | |
| dependencies+=( \ | |
| $(ldd "$file" 2>/dev/null \ | |
| | grep $(dirname "$file") \ | |
| | sed "s/.*[ \t]\(.*\) (.*/\1/") \ | |
| ) | |
| done | |
| fi | |
| files_to_index+=("${dependencies[@]}") | |
| # Ensure we cleanup on on exit. | |
| trap on_exit EXIT INT | |
| # We're good to go! Create temp directory for index files. | |
| directory=$(mktemp -d) | |
| echo "Made temp directory $directory." | |
| # 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 |