Skip to content

Instantly share code, notes, and snippets.

@baryluk
Last active September 4, 2022 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baryluk/ddce950c825eb0966aec96ceaf58319b to your computer and use it in GitHub Desktop.
Save baryluk/ddce950c825eb0966aec96ceaf58319b to your computer and use it in GitHub Desktop.
Graph binary / library / dynamic library / shared object / so / ELF files, dependencies as a graph
#!/bin/bash
# NOTE: Deprecated. Use https://gist.github.com/baryluk/09cbabb215351117b32aee994e5619a0 instead
# This small program takes one parameter, a binary (or library), and outputs
# a dependency graph. This is done recursively for all subdependencies.
# Some common dependencies are ignored like this ones to glibc basic libraries.
# The ones related to stdc++ / gcc are not ignored.
# After script is done execute this command to generate dependency graph:
#
# (echo "digraph {"; ./analyzer_binary.sh ${BINARY} | sort | uniq; echo "}") | dot -Grankdir=LR -Nshape=box -Tpng -o dependencies.png /dev/fd/0
#
# Copyright: Witold Baryluk, 2019. MIT license
# TODO(baryluk): Automatically use proper architectures.
# TODO(baryluk): Rewrite in Python3 / Go / D.
# TODO(baryluk): Make it more parallel.
# TODO(baryluk): Don't call recursively, but process things iteratively using
# a queue and arrays. Don't process dependencies we already processed before
# (or are processing in parallel).
# TODO(baryluk): Use `/lib/ld-linux.so.2 --list`, /lib64/ld-linux-x86-64.so.2 --list or ldd directly somehow?
# TODO(baryluk): Name it something short. ldx? ldv? lld?
# TODO(baryluk): Also traverse LD_LIBRARY_PATH
# TODO(baryluk): Support deprecated DT_RPATH just in case.
# TODO(baryluk): Support dependencies with slashes, which indicate to not search
# for them in RPATH, RUNPATH or system default search paths, but instead to use
# the path directly (it can be relative or absolute. When relative is the path
# relative to the main executable path, or to the library that is using it?)
#
# LD_DEBUG=libs,files /bin/ls 2>&1
# LD_DEBUG=libs,files LD_DEBUG_OUTPUT=output_log.txt /bin/ls
#
# Can be used to capture so info, but it is unsecure.
#
# Similarly `ld-linux.so.2 --list` and `ldd` are not secure.
# ldd /bin/ls is basically equivalent do running LD_TRACE_LOADED_OBJECTS=1 ld-linux.so.2 /bin/ls
# In fact ldd is just a bash script that detects elf object architecture and invokes
# proper dynamic linker with proper flags and environment variables.
# but dynamic linker will most likely still invoke init/fini sections of elf objects!
BINARY=$1
NAME=${2:-$BINARY}
INDENT=""
LEVEL=${3:-0}
ORGBINARY="${BINARY}"
for i in $(seq ${LEVEL}); do
INDENT="${INDENT} "
done
if ! [ -f "${BINARY}" ]; then
while read path; do
if [ -f "${path}/${BINARY}" ]; then
BINARY="${path}/${BINARY}"
break
fi
done <<<$(grep -E -v '^#' /etc/ld.so.conf.d/i386-linux-gnu.conf)
fi
DEPENDENCIES=$(objdump -p "${BINARY}" | grep -E '\bNEEDED\b' | awk '{print $2;}')
# Also known as DT_RUNPATH
RUNPATH=$(objdump -p "${BINARY}" | grep -E '\bRUNPATH\b' | awk '{print $2;}')
if [ "x${RUNPATH}" != "x" ]; then
RUNPATH="${RUNPATH}/"
fi
IGNORED="ld-linux.so.2 libc.so.6 libm.so.6 libdl.so.2 libpthread.so.0 librt.so.1"
for d in ${DEPENDENCIES}
do
if echo "${IGNORED}" | grep -E "\b${d}\b" >/dev/null; then
continue
fi
# echo "${INDENT}" $d
echo "\"${NAME}\" -> \"${d}\";"
if [ -f "${RUNPATH}${d}" ]; then
$0 "${RUNPATH}${d}" "${d}" $((${LEVEL}+1))
else
$0 "${d}" "${d}" $((${LEVEL}+1))
fi
done | sort | uniq # This is pretty inefficient (sorting and sorting over and over again), but does work and isn't too slow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment