opengl: Do VirtualGL preload conditioning on VGL_DISPLAY

Preloading is done by doing a binary patch to the library to add it
as a dependency. The 2.3 series of glibc will let this be done with
just a wrapper that calls ld.so and with the --preload option.
This commit is contained in:
Tyson Whitehead
2019-04-12 11:23:38 -04:00
parent 3825783ee5
commit 7736b409f4
2 changed files with 152 additions and 40 deletions

View File

@@ -3,7 +3,7 @@
# Add a setup hook to the mesa_noglu package that automatically adds
# a libvglfaker.so dependency to executables that depend on libGL.so.
{ super, lib, buildEnv, makeSetupHook, file, bash }:
{ super, lib, buildEnv, makeSetupHook, file, bash, xorg }:
let
autoVirtualGLHook =
@@ -27,7 +27,7 @@ let
makeSetupHook {
name = "insert-virtualgl";
deps = [ file virtualglLib ];
substitutions = { inherit virtualglLib; };
substitutions = { inherit virtualglLib bash; inherit (xorg) libXext; };
} ./insert-virtualgl.sh;
in {

View File

@@ -1,3 +1,10 @@
# Quote the given arguement
#
VGL_quote() {
printf '%s' "${*@Q}"
}
# Stream producers
#
# These produce a stream of escaped new-line separated items
@@ -12,16 +19,22 @@ VGL_sourceS() {
# Stream the results of find (correctly escapes all filenames)
#
VGL_findS() {
declare -a options=("$@") file
declare options=("$@") file
find "${options[@]}" -print0 |
while read -d '' -r file; do
VGL_sourceS "$file"
done
}
# Stream the closure of the list libraries from an elf file
# Stream the list of directly required libraries from an elf file
#
VGL_elfLibsS() {
patchelf --print-needed "$1"
}
# Stream the resolved libraries from an elf file
#
VGL_elfLibsResolvedS() {
declare file=$1 line
ldd "$file" |
while read line; do
@@ -42,6 +55,16 @@ VGL_elfLibsS() {
done
}
# Stream the list of rpaths from an elf file
#
VGL_elfRPathS() {
declare file=$1 entry
patchelf --print-rpath "$file" |
while read -d ':' -r entry; do
VGL_sourceS "$entry"
done
}
# Stream transformers
@@ -49,30 +72,41 @@ VGL_elfLibsS() {
# Take in a set of options and transform a stream of new-line separated items
#
# Group all items onto a single line separated by given deliminator (or space)
#
VGL_joinS() {
declare deliminator=${1- } next
if read next; then
printf '%q' "$next"
while read next; do
printf '%s%q' "$deliminator" "$next"
done
printf '\n'
fi
}
# Remove all items for which the given command returns false
#
VGL_filterS() {
declare command=("$@")
VGL_testKeepS() {
declare command=$1 next
while read next; do
if eval $(printf '%q ' "${command[@]}" "$next"); then
set -- "$next"
if (eval "$command"); then
VGL_sourceS "$next"
fi
done
}
# Remove all items which match one of the given values
#
VGL_valuesRemoveS() {
declare -A values
declare value
for value in "$@"; do
values[$value]=
done
while read value; do
[[ ${values[$value]+yes} ]] \
|| VGL_sourceS "$value"
done
}
# Add addition to end unless test succeeds on at least one
#
VGL_testSuffixNoneS() {
declare command=$1 addition=$2
eval "declare items=($(cat))"
VGL_sourceS "${items[@]}"
VGL_sourceS "${items[@]}" | VGL_isTestAnyS "$command" \
|| VGL_sourceS "$addition"
}
# Stream consumers
@@ -82,10 +116,11 @@ VGL_filterS() {
# Return true iff given command returns true for some item
#
VGL_anyS() {
declare command=("$@")
VGL_isTestAnyS() {
declare command="$1" next
while read next; do
if eval $(printf '%q ' "${command[@]}" "$next"); then
set -- "$next"
if (eval "$command"); then
return 0
fi
done
@@ -93,6 +128,19 @@ VGL_anyS() {
}
# Return true iff given command returns true for all item
#
VGL_isTestAllS() {
declare command="$1" next
while read next; do
set -- "$next"
if ! (eval "$command"); then
return 1
fi
done
return 0
}
# Tests for the filter transformer
#
@@ -123,33 +171,97 @@ VGL_isLibGL() {
# Update elf files
#
# Run given stream element on rpath stream and write result to file if differs from original
#
VGL_elfFilterRPathS() {
declare file=$1 command=$2
eval "declare pathsOld=($(VGL_elfRPathS "$file"))"
eval "declare pathsNew=($(VGL_sourceS "${pathsOld[@]}" | eval "$command"))"
[[ ${pathsNew[@]@Q} = ${pathsOld[@]@Q} ]] \
|| if (( ${#pathsNew[@]} > 0 ));
then ( IFS=:; set -x; patchelf --set-rpath "${pathsNew[*]}" "$file" )
else ( set -x; patchelf --remove-rpath "$file" )
fi
}
# Run given stream element on directly required libraries stream and write result to file if differs from original
#
VGL_elfFilterLibsS() {
declare file=$1 command=$2
eval "declare libsOld=($(VGL_elfLibsS "$file"))"
eval "declare libsNew=($(VGL_sourceS "${libsOld[@]}" | eval "$command"))"
eval "declare libs=($(VGL_sourceS "${libsOld[@]}" | VGL_valuesRemoveS "${libsNew[@]}"))"
(( ${#libs[@]} < 1 )) \
|| ( eval "set -x; patchelf$(printf ' --remove-needed %q' "${libs[@]}") ${file@Q}")
eval "declare libs=($(VGL_sourceS "${libsNew[@]}" | VGL_valuesRemoveS "${libsOld[@]}"))"
(( ${#libs[@]} < 1 )) \
|| ( eval "set -x; patchelf$(printf ' --add-needed %q' "${libs[@]}") ${file@Q}")
}
# Patch elf file to be able to use OpenGL properly (not a stream function so can be called in normal way)
#
VGL_patch() {
declare files=("$@") file
for file in "${files[@]}"; do
# glibc 2.3 ld.so supports a --preload option that would make this just a simple wrapper
#
# mv "$file" "${file%/*}/.${file##*/}.vgl"
# cat > "$file" <<EOF
#! @bash@/bin/bash
# exec -a "\$0" "$(patchelf --print-interpreter "${file%/*}/.${file##*/}.vgl")" \${VGL_DISPLAY:+--preload "@virtualglLib@/lib/libvglfaker.so"} "${file%/*}/.${file##*/}.vgl" "\$@"
# EOF
# chmod +x "$file"
# VGL version may need fixing up to ensure libglfaker.so gets loaded at the start
printf 'Wrapping and patching for VirtualGL: %q\n' "$file" >&2
cp -a "$file" "${file%/*}/.${file##*/}.vgl-yes"
VGL_elfFilterLibsS "${file%/*}/.${file##*/}.vgl-yes" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 =~ ^(.*/)?libvglfaker.so ]]')"' @virtualglLib@/lib/libvglfaker.so'
# patchelf --add-needed @virtualglLib@/lib/libvglfaker.so "${file%/*}/.${file##*/}.vgl-yes"
# Non-VGL versions may need fixing to load libXext so NVIDIA binary libs are happy
#
# Ugly, but putting this here instead of with the NVIDIA libs, allows us not to to fix one version for all
mv "$file" "${file%/*}/.${file##*/}.vgl-no"
VGL_elfFilterLibsS "${file%/*}/.${file##*/}.vgl-no" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 =~ ^(.*/)?libXext.so ]]')"' libXext.so.6'
VGL_elfFilterRPathS "${file%/*}/.${file##*/}.vgl-no" 'VGL_testSuffixNoneS '"$(VGL_quote '[[ $1 = @libXext@/lib ]]')"' @libXext@/lib'
# Replace with chooser script based on whether VGL_DISPLAY is set or not
cat > "$file" <<EOF
#! @bash@/bin/bash
if [ -z "\$VGL_DISPLAY" ]; then
exec -a "\$0" "${file%/*}/.${file##*/}.vgl-no" "\$@"
else
exec -a "\$0" "${file%/*}/.${file##*/}.vgl-yes" "\$@"
fi
EOF
chmod +x "$file"
done
}
# Setup hook
#
# Find all executables that depend on libGL and add a libvglfaker.so dependency
# Find all executables that depend on libGL and replace with a wrapper script that either executes the original
# executable or a copy with an added libvglfaker.so dependency depending on whether VGL_DISPLAY is set or not.
#
VGL_autoAddVGL() {
declare opts=$(shopt -p) output
set +o pipefail
echo "Inserting VirtualGL into OpenGL executables in $prefix..." >&2
set -x
for output in $outputs; do
VGL_findS "${!output}" -type f | VGL_filterS VGL_isElfBin | {
declare file
while read file; do
VGL_elfLibsS "$file" |
if VGL_anyS VGL_isLibGL; then
VGL_sourceS "$file"
fi
done
} | {
declare file
while read file; do
printf 'patchelf --add-needed @virtualglLib@/lib/libvglfaker.so %q\n' "$file" >&2
patchelf --add-needed @virtualglLib@/lib/libvglfaker.so "$file"
done
}
VGL_findS "${!output}" -type f \
| VGL_testKeepS 'VGL_isElfBin "$1"' \
| VGL_testKeepS 'VGL_elfLibsResolvedS "$1" | VGL_isTestAnyS '"$(VGL_quote 'VGL_isLibGL "$1"')" \
| VGL_isTestAllS 'VGL_patch "$1"'
done
eval "$opts"