2020-12-14 07:27:58 +01:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
2018-02-01 22:54:18 +01:00
|
|
|
declare -a autoPatchelfLibs
|
2020-10-20 14:09:32 +02:00
|
|
|
declare -Ag autoPatchelfFailedDeps
|
2018-02-01 22:54:18 +01:00
|
|
|
|
|
|
|
gatherLibraries() {
|
|
|
|
autoPatchelfLibs+=("$1/lib")
|
|
|
|
}
|
|
|
|
|
2020-10-20 14:09:32 +02:00
|
|
|
# wrapper around patchelf to raise proper error messages
|
|
|
|
# containing the tried file name and command
|
|
|
|
runPatchelf() {
|
2020-10-22 05:15:42 +02:00
|
|
|
patchelf "$@" || (echo "Command failed: patchelf $*" && exit 1)
|
2020-10-20 14:09:32 +02:00
|
|
|
}
|
|
|
|
|
2020-12-14 07:27:58 +01:00
|
|
|
# shellcheck disable=SC2154
|
|
|
|
# (targetOffset is referenced but not assigned.)
|
2018-02-01 22:54:18 +01:00
|
|
|
addEnvHooks "$targetOffset" gatherLibraries
|
|
|
|
|
|
|
|
isExecutable() {
|
autoPatchelfHook: Correctly detect PIE binaries
I originally thought it would just be enough to just check for an INTERP
section in isExecutable, however this would mean that we don't detect
statically linked ELF files, which would break our recent improvement to
gracefully handle those.
In theory, we are only interested in ELF files that have an INTERP
section, so checking for INTERP would be enough. Unfortunately the
isExecutable function is already used outside of autoPatchelfHook, so we
can't easily get rid of it now, so let's actually strive for more
correctness and make isExecutable actually match ELF files that are
executable.
So what we're doing instead now is to check whether either the ELF type
is EXEC *or* we have an INTERP section and if one of them is true we
should have an ELF executable, even if it's statically linked.
Along the way I also set LANG=C for the invocations of readelf, just to
be sure we don't get locale-dependent output.
Tested this with the following command (which contains almost[1] all the
packages using autoPatchelfHook), checking whether we run into any
library-related errors:
nix-build -E 'with import ./. { config.allowUnfree = true; };
runCommand "test-executables" {
drvs = [
anydesk cups-kyodialog3 elasticsearch franz gurobi
masterpdfeditor oracle-instantclient powershell reaper
sourcetrail teamviewer unixODBCDrivers.msodbcsql17 virtlyst
vk-messenger wavebox zoom-us
];
} ("for i in $drvs; do for b in $i/bin/*; do " +
"[ -x \"$b\" ] && timeout 10 \"$b\" || :; done; done")
'
Apart from testing against library-related errors I also compared the
resulting store paths against the ones prior to this commit. Only
anydesk and virtlyst had the same as they didn't have self-references,
everything else differed only because of self-references, except
elasticsearch, which had the following PIE binaries:
* modules/x-pack/x-pack-ml/platform/linux-x86_64/bin/autoconfig
* modules/x-pack/x-pack-ml/platform/linux-x86_64/bin/autodetect
* modules/x-pack/x-pack-ml/platform/linux-x86_64/bin/categorize
* modules/x-pack/x-pack-ml/platform/linux-x86_64/bin/controller
* modules/x-pack/x-pack-ml/platform/linux-x86_64/bin/normalize
These binaries were now patched, which is what this commit is all about.
[1]: I didn't include the "maxx" package (MaXX Interactive Desktop)
because the upstream URLs are no longer existing and I couldn't
find them elsewhere on the web.
Signed-off-by: aszlig <aszlig@nix.build>
Fixes: https://github.com/NixOS/nixpkgs/issues/48330
Cc: @gnidorah (for MaXX Interactive Desktop)
2018-11-03 04:50:26 +01:00
|
|
|
# For dynamically linked ELF files it would be enough to check just for the
|
|
|
|
# INTERP section. However, we won't catch statically linked executables as
|
|
|
|
# they only have an ELF type of EXEC but no INTERP.
|
|
|
|
#
|
|
|
|
# So what we do here is just check whether *either* the ELF type is EXEC
|
|
|
|
# *or* there is an INTERP section. This also catches position-independent
|
|
|
|
# executables, as they typically have an INTERP section but their ELF type
|
|
|
|
# is DYN.
|
2020-04-02 04:22:59 +02:00
|
|
|
isExeResult="$(LANG=C $READELF -h -l "$1" 2> /dev/null \
|
2019-03-20 13:57:59 +01:00
|
|
|
| grep '^ *Type: *EXEC\>\|^ *INTERP\>')"
|
|
|
|
# not using grep -q, because it can cause Broken pipe
|
|
|
|
[ -n "$isExeResult" ]
|
2018-02-01 22:54:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# We cache dependencies so that we don't need to search through all of them on
|
|
|
|
# every consecutive call to findDependency.
|
2020-10-20 14:09:32 +02:00
|
|
|
declare -Ag autoPatchelfCachedDepsAssoc
|
|
|
|
declare -ag autoPatchelfCachedDeps
|
|
|
|
|
2018-02-01 22:54:18 +01:00
|
|
|
|
|
|
|
addToDepCache() {
|
2020-10-20 14:09:32 +02:00
|
|
|
if [[ ${autoPatchelfCachedDepsAssoc[$1]+f} ]]; then return; fi
|
|
|
|
|
|
|
|
# store deps in an assoc. array for efficient lookups
|
|
|
|
# otherwise findDependency would have quadratic complexity
|
|
|
|
autoPatchelfCachedDepsAssoc["$1"]=""
|
|
|
|
|
2020-10-26 11:17:07 +01:00
|
|
|
# also store deps in normal array to maintain their order
|
2020-10-20 14:09:32 +02:00
|
|
|
autoPatchelfCachedDeps+=("$1")
|
2018-02-01 22:54:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
declare -gi depCacheInitialised=0
|
|
|
|
declare -gi doneRecursiveSearch=0
|
|
|
|
declare -g foundDependency
|
|
|
|
|
|
|
|
getDepsFromSo() {
|
|
|
|
ldd "$1" 2> /dev/null | sed -n -e 's/[^=]*=> *\(.\+\) \+([^)]*)$/\1/p'
|
|
|
|
}
|
|
|
|
|
|
|
|
populateCacheWithRecursiveDeps() {
|
|
|
|
local so found foundso
|
2020-10-20 14:09:32 +02:00
|
|
|
for so in "${autoPatchelfCachedDeps[@]}"; do
|
2018-02-01 22:54:18 +01:00
|
|
|
for found in $(getDepsFromSo "$so"); do
|
|
|
|
local base="${found##*/}"
|
|
|
|
local soname="${base%.so*}"
|
|
|
|
for foundso in "${found%/*}/$soname".so*; do
|
|
|
|
addToDepCache "$foundso"
|
|
|
|
done
|
|
|
|
done
|
|
|
|
done
|
|
|
|
}
|
|
|
|
|
|
|
|
getSoArch() {
|
|
|
|
objdump -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p'
|
|
|
|
}
|
|
|
|
|
|
|
|
# NOTE: If you want to use this function outside of the autoPatchelf function,
|
|
|
|
# keep in mind that the dependency cache is only valid inside the subshell
|
|
|
|
# spawned by the autoPatchelf function, so invoking this directly will possibly
|
|
|
|
# rebuild the dependency cache. See the autoPatchelf function below for more
|
|
|
|
# information.
|
|
|
|
findDependency() {
|
|
|
|
local filename="$1"
|
|
|
|
local arch="$2"
|
|
|
|
local lib dep
|
|
|
|
|
|
|
|
if [ $depCacheInitialised -eq 0 ]; then
|
|
|
|
for lib in "${autoPatchelfLibs[@]}"; do
|
|
|
|
for so in "$lib/"*.so*; do addToDepCache "$so"; done
|
|
|
|
done
|
|
|
|
depCacheInitialised=1
|
|
|
|
fi
|
|
|
|
|
2020-10-20 14:09:32 +02:00
|
|
|
for dep in "${autoPatchelfCachedDeps[@]}"; do
|
2018-02-01 22:54:18 +01:00
|
|
|
if [ "$filename" = "${dep##*/}" ]; then
|
|
|
|
if [ "$(getSoArch "$dep")" = "$arch" ]; then
|
|
|
|
foundDependency="$dep"
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
|
|
# Populate the dependency cache with recursive dependencies *only* if we
|
|
|
|
# didn't find the right dependency so far and afterwards run findDependency
|
|
|
|
# again, but this time with $doneRecursiveSearch set to 1 so that it won't
|
|
|
|
# recurse again (and thus infinitely).
|
|
|
|
if [ $doneRecursiveSearch -eq 0 ]; then
|
|
|
|
populateCacheWithRecursiveDeps
|
|
|
|
doneRecursiveSearch=1
|
|
|
|
findDependency "$filename" "$arch" || return 1
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
autoPatchelfFile() {
|
|
|
|
local dep rpath="" toPatch="$1"
|
|
|
|
|
2020-11-07 12:08:48 +01:00
|
|
|
local interpreter
|
|
|
|
interpreter="$(< "$NIX_CC/nix-support/dynamic-linker")"
|
2018-02-01 22:54:18 +01:00
|
|
|
if isExecutable "$toPatch"; then
|
2020-10-20 14:09:32 +02:00
|
|
|
runPatchelf --set-interpreter "$interpreter" "$toPatch"
|
2020-12-14 07:27:58 +01:00
|
|
|
# shellcheck disable=SC2154
|
|
|
|
# (runtimeDependencies is referenced but not assigned.)
|
2018-02-01 22:54:18 +01:00
|
|
|
if [ -n "$runtimeDependencies" ]; then
|
|
|
|
for dep in $runtimeDependencies; do
|
|
|
|
rpath="$rpath${rpath:+:}$dep/lib"
|
|
|
|
done
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
|
|
|
echo "searching for dependencies of $toPatch" >&2
|
|
|
|
|
|
|
|
# We're going to find all dependencies based on ldd output, so we need to
|
|
|
|
# clear the RPATH first.
|
2020-10-20 14:09:32 +02:00
|
|
|
runPatchelf --remove-rpath "$toPatch"
|
2018-02-01 22:54:18 +01:00
|
|
|
|
2020-12-20 09:24:10 +01:00
|
|
|
# If the file is not a dynamic executable, ldd/sed will fail,
|
|
|
|
# in which case we return, since there is nothing left to do.
|
2020-12-14 07:27:58 +01:00
|
|
|
local missing
|
|
|
|
missing="$(
|
2018-02-01 22:54:18 +01:00
|
|
|
ldd "$toPatch" 2> /dev/null | \
|
|
|
|
sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p'
|
2020-12-14 07:27:58 +01:00
|
|
|
)" || return 0
|
2018-02-01 22:54:18 +01:00
|
|
|
|
|
|
|
# This ensures that we get the output of all missing dependencies instead
|
|
|
|
# of failing at the first one, because it's more useful when working on a
|
|
|
|
# new package where you don't yet know its dependencies.
|
|
|
|
|
|
|
|
for dep in $missing; do
|
|
|
|
echo -n " $dep -> " >&2
|
|
|
|
if findDependency "$dep" "$(getSoArch "$toPatch")"; then
|
|
|
|
rpath="$rpath${rpath:+:}${foundDependency%/*}"
|
|
|
|
echo "found: $foundDependency" >&2
|
|
|
|
else
|
|
|
|
echo "not found!" >&2
|
2020-10-23 08:21:08 +02:00
|
|
|
autoPatchelfFailedDeps["$dep"]="$toPatch"
|
2018-02-01 22:54:18 +01:00
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
|
|
if [ -n "$rpath" ]; then
|
|
|
|
echo "setting RPATH to: $rpath" >&2
|
2020-10-20 14:09:32 +02:00
|
|
|
runPatchelf --set-rpath "$rpath" "$toPatch"
|
2018-02-01 22:54:18 +01:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2018-11-25 16:17:14 +01:00
|
|
|
# Can be used to manually add additional directories with shared object files
|
|
|
|
# to be included for the next autoPatchelf invocation.
|
|
|
|
addAutoPatchelfSearchPath() {
|
2018-11-19 23:23:38 +01:00
|
|
|
local -a findOpts=()
|
|
|
|
|
2018-11-25 16:17:14 +01:00
|
|
|
# XXX: Somewhat similar to the one in the autoPatchelf function, maybe make
|
|
|
|
# it DRY someday...
|
2018-11-19 23:23:38 +01:00
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
case "$1" in
|
|
|
|
--) shift; break;;
|
|
|
|
--no-recurse) shift; findOpts+=("-maxdepth" 1);;
|
2018-11-25 16:17:14 +01:00
|
|
|
--*)
|
|
|
|
echo "addAutoPatchelfSearchPath: ERROR: Invalid command line" \
|
|
|
|
"argument: $1" >&2
|
|
|
|
return 1;;
|
|
|
|
*) break;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
2020-12-14 07:27:58 +01:00
|
|
|
while IFS= read -r -d '' file; do
|
|
|
|
addToDepCache "$file"
|
|
|
|
done < <(find "$@" "${findOpts[@]}" \! -type d \
|
|
|
|
\( -name '*.so' -o -name '*.so.*' \) -print0)
|
2018-11-25 16:17:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
autoPatchelf() {
|
2018-11-26 01:13:59 +01:00
|
|
|
local norecurse=
|
2018-11-25 16:17:14 +01:00
|
|
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
case "$1" in
|
|
|
|
--) shift; break;;
|
|
|
|
--no-recurse) shift; norecurse=1;;
|
2018-11-19 23:23:38 +01:00
|
|
|
--*)
|
|
|
|
echo "autoPatchelf: ERROR: Invalid command line" \
|
|
|
|
"argument: $1" >&2
|
|
|
|
return 1;;
|
|
|
|
*) break;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
if [ $# -eq 0 ]; then
|
|
|
|
echo "autoPatchelf: No paths to patch specified." >&2
|
|
|
|
return 1
|
|
|
|
fi
|
|
|
|
|
2018-02-01 22:54:18 +01:00
|
|
|
echo "automatically fixing dependencies for ELF files" >&2
|
|
|
|
|
|
|
|
# Add all shared objects of the current output path to the start of
|
2020-10-26 11:17:07 +01:00
|
|
|
# autoPatchelfCachedDeps so that it's chosen first in findDependency.
|
2018-11-25 16:17:14 +01:00
|
|
|
addAutoPatchelfSearchPath ${norecurse:+--no-recurse} -- "$@"
|
2018-02-01 22:54:18 +01:00
|
|
|
|
2018-09-23 15:45:32 +02:00
|
|
|
while IFS= read -r -d $'\0' file; do
|
|
|
|
isELF "$file" || continue
|
2020-04-02 04:22:59 +02:00
|
|
|
segmentHeaders="$(LANG=C $READELF -l "$file")"
|
2018-11-26 01:58:36 +01:00
|
|
|
# Skip if the ELF file doesn't have segment headers (eg. object files).
|
2019-03-20 13:57:59 +01:00
|
|
|
# not using grep -q, because it can cause Broken pipe
|
|
|
|
[ -n "$(echo "$segmentHeaders" | grep '^Program Headers:')" ] || continue
|
2018-09-25 04:11:33 +02:00
|
|
|
if isExecutable "$file"; then
|
|
|
|
# Skip if the executable is statically linked.
|
2019-03-20 13:57:59 +01:00
|
|
|
[ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue
|
2018-09-25 04:11:33 +02:00
|
|
|
fi
|
2020-10-20 14:09:32 +02:00
|
|
|
# Jump file if patchelf is unable to parse it
|
|
|
|
# Some programs contain binary blobs for testing,
|
|
|
|
# which are identified as ELF but fail to be parsed by patchelf
|
2020-11-07 12:08:48 +01:00
|
|
|
patchelf "$file" || continue
|
2018-09-23 15:45:32 +02:00
|
|
|
autoPatchelfFile "$file"
|
2018-11-25 16:17:14 +01:00
|
|
|
done < <(find "$@" ${norecurse:+-maxdepth 1} -type f -print0)
|
2020-10-20 14:09:32 +02:00
|
|
|
|
|
|
|
# fail if any dependencies were not found and
|
|
|
|
# autoPatchelfIgnoreMissingDeps is not set
|
|
|
|
local depsMissing=0
|
|
|
|
for failedDep in "${!autoPatchelfFailedDeps[@]}"; do
|
2020-10-23 08:16:23 +02:00
|
|
|
echo "autoPatchelfHook could not satisfy dependency $failedDep wanted by ${autoPatchelfFailedDeps[$failedDep]}"
|
2020-10-20 14:09:32 +02:00
|
|
|
depsMissing=1
|
|
|
|
done
|
2020-12-14 07:27:58 +01:00
|
|
|
# shellcheck disable=SC2154
|
|
|
|
# (autoPatchelfIgnoreMissingDeps is referenced but not assigned.)
|
2020-10-22 05:15:42 +02:00
|
|
|
if [[ $depsMissing == 1 && -z "$autoPatchelfIgnoreMissingDeps" ]]; then
|
2020-10-20 14:09:32 +02:00
|
|
|
echo "Add the missing dependencies to the build inputs or set autoPatchelfIgnoreMissingDeps=true"
|
|
|
|
exit 1
|
|
|
|
fi
|
2018-02-01 22:54:18 +01:00
|
|
|
}
|
|
|
|
|
2018-07-16 01:06:43 +02:00
|
|
|
# XXX: This should ultimately use fixupOutputHooks but we currently don't have
|
|
|
|
# a way to enforce the order. If we have $runtimeDependencies set, the setup
|
|
|
|
# hook of patchelf is going to ruin everything and strip out those additional
|
|
|
|
# RPATHs.
|
|
|
|
#
|
|
|
|
# So what we do here is basically run in postFixup and emulate the same
|
|
|
|
# behaviour as fixupOutputHooks because the setup hook for patchelf is run in
|
|
|
|
# fixupOutput and the postFixup hook runs later.
|
2018-11-19 17:36:22 +01:00
|
|
|
postFixupHooks+=('
|
2019-10-31 17:50:15 +01:00
|
|
|
if [ -z "${dontAutoPatchelf-}" ]; then
|
2018-11-19 23:23:38 +01:00
|
|
|
autoPatchelf -- $(for output in $outputs; do
|
2018-11-19 17:36:22 +01:00
|
|
|
[ -e "${!output}" ] || continue
|
|
|
|
echo "${!output}"
|
|
|
|
done)
|
|
|
|
fi
|
|
|
|
')
|