Created
March 30, 2024 18:55
-
-
Save courtc/1ac4221a428953df96c48da9b1cc640d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# 'ldd' alternative which reads ELF files (with readelf) for DT_NEEDED | |
# entries to collect dependencies. Supports list, tree and JSON output | |
# formats. | |
# requirements: | |
# - bash | |
# - readelf | |
# - sed | |
# read and include ld.so.conf files | |
# outputs colon separated directories | |
# param: <conf path> | |
read_conf() { | |
local conf="$1" | |
while read line; do | |
if [ "${line:0:7}" == "include" ]; then | |
local incl_wild="$(echo "${line:8}" | sed 's/^[ \t]*//')" | |
for f in $incl_wild; do | |
read_conf "$f" | |
done | |
else | |
echo -n "$line:" | |
fi | |
done < <(sed '/^\s*$/d;/^#/d;s/^[ \t]*//;s/[ \t]*$//' "$conf") | |
} | |
# read ld.so.conf | |
# outputs colon separated directories | |
read_ld_conf() { | |
read_conf "/etc/ld.so.conf" | |
} | |
# read ELF RPATH or RUNPATH | |
# outputs colon separated runpath | |
# param: <elf path> | |
read_runpath() { | |
local libname="$1" | |
# fixme: should mux rpath/runpath | |
while read line; do | |
echo -n "$line:" | |
done < <(readelf -d "$libname" | sed -n '/PATH/s/.*\[\(.*\)\]/\1/p') | |
} | |
# read all load paths for ELF | |
# Paths, in order: | |
# 1. RPATH/RUNPATH | |
# 2. LD_LIBRARY_PATH | |
# 3. /lib:/usr/lib | |
# 4. Those specified in /etc/ld.so.conf | |
# outputs possible search directories for ELF libraries, newline delimited | |
# param: <elf path> | |
load_paths() { | |
local libname="$1" | |
local lib_path="$LD_LIBRARY_PATH" | |
[ -n "$lib_path" ] && lib_path="$lib_path:" | |
local paths="$(read_runpath "$libname")$lib_path/lib:/usr/lib:$(read_ld_conf)" | |
IFS=: read -r -a patharr <<< "$paths" | |
for path in "${patharr[@]}"; do | |
echo "$path" | |
done | |
} | |
# read ELF DT_NEEDED entries | |
# outputs needed libraries, newline delimited | |
# param: <elf path> | |
read_needed() { | |
local libname="$1" | |
readelf -d "$libname" | sed -n '/NEEDED/s/.*\[\(.*\)\]/\1/p' | |
} | |
# locate needed libraries | |
# outputs needed library paths, newline delimited | |
# param: <elf path> | |
find_needed() { | |
local libname="$1" | |
local needed=( $(read_needed "$libname") ) | |
local paths=( $(load_paths "$libname") ) | |
for lib in "${needed[@]}"; do | |
local found=0 | |
for path in "${paths[@]}"; do | |
if [ -e "$path/$lib" ]; then | |
echo "$path/$lib" | |
found=1 | |
break | |
fi | |
done | |
[ $found -eq 0 ] && echo "$indent - MISSING: $lib" >&2 | |
done | |
} | |
# recursive tree printer | |
# params: <elf path>, <indent string> | |
_print_needed_tree() { | |
local libname="$1" | |
local indent="$2" | |
local libs=( $(find_needed "$libname") ) | |
echo "$indent- $libname" | |
for lib in "${libs[@]}"; do | |
if [ -n "${all_libs[$lib]}" ]; then | |
echo "$indent - $lib (cached)" | |
continue | |
fi | |
_print_needed_tree "$lib" "$indent " | |
done | |
all_libs[$libname]="found" | |
} | |
# print needed libraries as tree | |
# param: <elf path> | |
print_needed_tree() { | |
local libname="$1" | |
declare -A all_libs | |
_print_needed_tree "$libname" | |
} | |
# recursive list printer | |
# param: <elf path> | |
_print_needed_list() { | |
local libname="$1" | |
local libs=( $(find_needed "$libname") ) | |
for lib in "${libs[@]}"; do | |
if [ -n "${all_libs[$lib]}" ]; then | |
continue | |
fi | |
echo "$lib" | |
_print_needed_list "$lib" | |
done | |
all_libs[$libname]="found" | |
} | |
# print needed libraries as list | |
# param: <elf path> | |
print_needed_list() { | |
local libname="$1" | |
declare -A all_libs | |
_print_needed_list "$libname" | |
} | |
# recursive JSON printer | |
# param: <elf path>, [comma/newline prefix] | |
_print_needed_json() { | |
local libname="$1" | |
local gpfx="$2" | |
local libs=( $(find_needed "$libname") ) | |
[ ${#libs[@]} -eq 0 ] && return | |
echo -e "$gpfx \"$libname\": [" | |
all_libs[$libname]="found" | |
local pfx="" | |
for lib in "${libs[@]}"; do | |
echo -en "$pfx \"$lib\"" | |
pfx=",\n" | |
done | |
echo -en "\n ]" | |
for lib in "${libs[@]}"; do | |
if [ -n "${all_libs[$lib]}" ]; then | |
continue | |
fi | |
_print_needed_json "$lib" ",\n" | |
done | |
} | |
# print needed libraries as JSON | |
# param: <elf path> | |
print_needed_json() { | |
local libname="$1" | |
declare -A all_libs | |
echo "{" | |
_print_needed_json "$libname" | |
echo -e "\n}" | |
} | |
# print usage message | |
# param: <script path>, [error message] | |
usage() { | |
[ -n "$2" ] && echo "$(basename $1): $2" | |
echo "Usage: $(basename $1) [-hjlt] [--json|--list|--tree] FILE..." | |
echo " -l, --list list format" | |
echo " -t, --tree tree format" | |
echo " -j, --json JSON format" | |
echo " -h, --help this help" | |
} | |
FILES=( ) | |
run_mode="list" | |
while [[ $# -gt 0 ]]; do | |
while getopts ":hjlt-:" optchar; do | |
case "${optchar}" in | |
-) | |
case "${OPTARG}" in | |
json) run_mode="json" ;; | |
tree) run_mode="tree" ;; | |
list) run_mode="list" ;; | |
help) | |
usage "$0" | |
exit 0;; | |
*) | |
usage "$0" "Unknown option --${OPTARG}" >&2 | |
exit 1;; | |
esac;; | |
j) run_mode="json" ;; | |
t) run_mode="tree" ;; | |
l) run_mode="list" ;; | |
h) | |
usage "$0" | |
exit 0;; | |
*) | |
usage "$0" "Invalid option: '-${optchar}'" >&2 | |
exit 1;; | |
esac | |
done | |
shift $((OPTIND - 1)) | |
OPTIND=1 | |
if [[ ${1+set} = set ]]; then | |
FILES+=( "$1" ) | |
shift | |
fi | |
done | |
if [ ${#FILES[@]} -eq 0 ]; then | |
usage "$0" "Missing file arguments" | |
exit 1 | |
fi | |
for lib in "${FILES[@]}"; do | |
print_needed_$run_mode "$lib" | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment