Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)

autoPatchelfHook: fix detection under crossSystem

In #84415, autoPatchelfHook was taught to use the correct path to the
readelf binary when a crossSystem is specified. Unfortunately, the
remainder of the functionality in the script depended on ldd, which only
reads ELF files of its own architecture. It has the further unfortunate
quality of not reporting any useful error, but rather that the file is
not a dynamic executable.

This change uses patchelf to directly analyze the DT_NEEDED tags in the
target files instead, which correctly works across architectures. It
also updates the use of objdump to be prefix-aware $OBJDUMP (which would
have been required in the PR mentioned above, but we never made it that
far into the script execution).

(cherry picked from commit b79483d2b72c8e349a8fa8a6e67e8061d82d6027)

authored by Noah Fontes and committed by Bjørn Forsman ff01aca2 1a7c2f41

Changed files
+71 -24
pkgs
build-support
setup-hooks
+71 -24
pkgs/build-support/setup-hooks/auto-patchelf.sh
··· 37 37 declare -Ag autoPatchelfCachedDepsAssoc 38 38 declare -ag autoPatchelfCachedDeps 39 39 40 - 41 40 addToDepCache() { 42 41 if [[ ${autoPatchelfCachedDepsAssoc[$1]+f} ]]; then return; fi 43 42 ··· 53 52 declare -gi doneRecursiveSearch=0 54 53 declare -g foundDependency 55 54 56 - getDepsFromSo() { 57 - ldd "$1" 2> /dev/null | sed -n -e 's/[^=]*=> *\(.\+\) \+([^)]*)$/\1/p' 55 + getDepsFromElfBinary() { 56 + # NOTE: This does not use runPatchelf because it may encounter non-ELF 57 + # files. Caller is expected to check the return code if needed. 58 + patchelf --print-needed "$1" 2> /dev/null 58 59 } 59 60 60 - populateCacheWithRecursiveDeps() { 61 - local so found foundso 62 - for so in "${autoPatchelfCachedDeps[@]}"; do 63 - for found in $(getDepsFromSo "$so"); do 64 - local base="${found##*/}" 65 - local soname="${base%.so*}" 66 - for foundso in "${found%/*}/$soname".so*; do 61 + getRpathFromElfBinary() { 62 + # NOTE: This does not use runPatchelf because it may encounter non-ELF 63 + # files. Caller is expected to check the return code if needed. 64 + local rpath 65 + rpath="$(patchelf --print-rpath "$1" 2> /dev/null)" || return $? 66 + 67 + local IFS=':' 68 + printf "%s\n" $rpath 69 + } 70 + 71 + populateCacheForDep() { 72 + local so="$1" 73 + local rpath found 74 + rpath="$(getRpathFromElfBinary "$so")" || return 1 75 + 76 + for found in $(getDepsFromElfBinary "$so"); do 77 + local rpathElem 78 + for rpathElem in $rpath; do 79 + # Ignore empty element or $ORIGIN magic variable which should be 80 + # deterministically resolved by adding this package's library 81 + # files early anyway. 82 + # 83 + # shellcheck disable=SC2016 84 + # (Expressions don't expand in single quotes, use double quotes for 85 + # that.) 86 + if [[ -z "$rpathElem" || "$rpathElem" == *'$ORIGIN'* ]]; then 87 + continue 88 + fi 89 + 90 + local soname="${found%.so*}" 91 + local foundso= 92 + for foundso in "$rpathElem/$soname".so*; do 67 93 addToDepCache "$foundso" 68 94 done 95 + 96 + # Found in this element of the rpath, no need to check others. 97 + if [ -n "$foundso" ]; then 98 + break 99 + fi 69 100 done 70 101 done 102 + 103 + # Not found in any rpath element. 104 + return 1 105 + } 106 + 107 + populateCacheWithRecursiveDeps() { 108 + # Dependencies may add more to the end of this array, so we use a counter 109 + # with while instead of a regular for loop here. 110 + local -i i=0 111 + while [ $i -lt ${#autoPatchelfCachedDeps[@]} ]; do 112 + populateCacheForDep "${autoPatchelfCachedDeps[$i]}" 113 + i=$i+1 114 + done 71 115 } 72 116 73 117 getSoArch() { 74 - objdump -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p' 118 + $OBJDUMP -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p' 75 119 } 76 120 77 121 # NOTE: If you want to use this function outside of the autoPatchelf function, ··· 129 173 fi 130 174 fi 131 175 176 + local libcLib 177 + libcLib="$(< "$NIX_CC/nix-support/orig-libc")/lib" 178 + 132 179 echo "searching for dependencies of $toPatch" >&2 133 180 134 - # We're going to find all dependencies based on ldd output, so we need to 135 - # clear the RPATH first. 136 - runPatchelf --remove-rpath "$toPatch" 137 - 138 - # If the file is not a dynamic executable, ldd/sed will fail, 139 - # in which case we return, since there is nothing left to do. 140 181 local missing 141 - missing="$( 142 - ldd "$toPatch" 2> /dev/null | \ 143 - sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p' 144 - )" || return 0 182 + missing="$(getDepsFromElfBinary "$toPatch")" || return 0 145 183 146 184 # This ensures that we get the output of all missing dependencies instead 147 185 # of failing at the first one, because it's more useful when working on a 148 186 # new package where you don't yet know its dependencies. 149 187 150 188 for dep in $missing; do 189 + # Check whether this library exists in libc. If so, we don't need to do 190 + # any futher searching -- it will be resolved correctly by the linker. 191 + if [ -f "$libcLib/$dep" ]; then 192 + continue 193 + fi 194 + 151 195 echo -n " $dep -> " >&2 152 196 if findDependency "$dep" "$(getSoArch "$toPatch")"; then 153 197 rpath="$rpath${rpath:+:}${foundDependency%/*}" ··· 184 228 done 185 229 186 230 while IFS= read -r -d '' file; do 187 - addToDepCache "$file" 231 + addToDepCache "$file" 188 232 done < <(find "$@" "${findOpts[@]}" \! -type d \ 189 233 \( -name '*.so' -o -name '*.so.*' \) -print0) 190 234 } ··· 220 264 segmentHeaders="$(LANG=C $READELF -l "$file")" 221 265 # Skip if the ELF file doesn't have segment headers (eg. object files). 222 266 # not using grep -q, because it can cause Broken pipe 223 - [ -n "$(echo "$segmentHeaders" | grep '^Program Headers:')" ] || continue 267 + grep -q '^Program Headers:' <<<"$segmentHeaders" || continue 224 268 if isExecutable "$file"; then 225 269 # Skip if the executable is statically linked. 226 - [ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue 270 + grep -q "^ *INTERP\\>" <<<"$segmentHeaders" || continue 227 271 fi 228 272 # Jump file if patchelf is unable to parse it 229 273 # Some programs contain binary blobs for testing, ··· 255 299 # So what we do here is basically run in postFixup and emulate the same 256 300 # behaviour as fixupOutputHooks because the setup hook for patchelf is run in 257 301 # fixupOutput and the postFixup hook runs later. 302 + # 303 + # shellcheck disable=SC2016 304 + # (Expressions don't expand in single quotes, use double quotes for that.) 258 305 postFixupHooks+=(' 259 306 if [ -z "${dontAutoPatchelf-}" ]; then 260 307 autoPatchelf -- $(for output in $outputs; do