From 1f4014b53a4dc77768f3998a6fd3b67024431fa3 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Tue, 23 Apr 2013 14:17:33 -0700 Subject: [PATCH] Start wrapping OpenGL, and print periodic FPS value to stdout. In addition to the fips binary, we now also compile a libfips.so library and LD_PRELOAD that before executing the program specified on the command- line. The libfips.so library wraps OpenGL calls of interest for purpose of instrumentation. So far, the only call wrapped is glXSwapBuffers and the only instrumentation is to compute and print out a frames-per-second value every 60 frames. --- .gitignore | 2 + Makefile.local | 34 ++++++++--- configure | 96 ++++++++++++++---------------- execute.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++- execute.h | 6 +- fips.c | 2 +- glxwrap.c | 96 ++++++++++++++++++++++++++++++ libfips.sym | 6 ++ 8 files changed, 334 insertions(+), 66 deletions(-) create mode 100644 glxwrap.c create mode 100644 libfips.sym diff --git a/.gitignore b/.gitignore index 5a5d08a..65bec77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .deps .first-build-message Makefile.config +config.h fips +libfips.so *.o *~ diff --git a/Makefile.local b/Makefile.local index 52a02ff..1287f49 100644 --- a/Makefile.local +++ b/Makefile.local @@ -5,16 +5,15 @@ include Makefile.release # Smash together user's values with our extra values FINAL_CFLAGS = -DFIPS_VERSION=$(VERSION) $(CFLAGS) $(WARN_CFLAGS) $(CONFIGURE_CFLAGS) $(extra_cflags) FINAL_FIPS_LDFLAGS = $(LDFLAGS) $(AS_NEEDED_LDFLAGS) $(TALLOC_LDFLAGS) +FINAL_LIBFIPS_LDFLAGS = $(LDFLAGS) -ldl FINAL_FIPS_LINKER = CC ifneq ($(LINKER_RESOLVES_LIBRARY_DEPENDENCIES),1) FINAL_FIPS_LDFLAGS += $(CONFIGURE_LDFLAGS) endif -ifeq ($(LIBDIR_IN_LDCONFIG),0) -FINAL_FIPS_LDFLAGS += $(RPATH_LDFLAGS) -endif .PHONY: all -all: fips +all: fips libfips.so + ifeq ($(MAKECMDGOALS),) ifeq ($(shell cat .first-build-message 2>/dev/null),) @FIPS_FIRST_BUILD=1 $(MAKE) --no-print-directory all @@ -59,6 +58,8 @@ clean: distclean: clean rm -rf $(DISTCLEAN) +# Main program, fips + fips_srcs = \ execute.c \ fips.c @@ -66,20 +67,35 @@ fips_srcs = \ fips_modules = $(fips_srcs:.c=.o) fips: $(fips_modules) - $(call quiet,$(FINAL_FIPS_LINKER) $(CFLAGS)) $^ $(FINAL_FIPS_LDFLAGS) -o $@ + $(call quiet,$(FINAL_FIPS_LINKER) $(CFLAGS)) $(FINAL_CFLAGS) $^ $(FINAL_FIPS_LDFLAGS) -o $@ + +# GL-wrapper library, libfips +LIBRARY_LINK_FLAGS = -shared -Wl,--version-script=libfips.sym,--no-undefined + +extra_cflags += -I$(srcdir) -fPIC + +libfips_srcs = \ + glxwrap.c + +libfips_modules = $(libfips_srcs:.c=.o) + +libfips.so: $(libfips_modules) libfips.sym + $(call quiet,$(FINAL_FIPS_LINKER) $(CFLAGS)) $(FINAL_CFLAGS) $(libfips_modules) $(FINAL_LIBFIPS_LDFLAGS) $(LIBRARY_LINK_FLAGS) -o $@ .PHONY: install install: all - mkdir -p $(DESTDIR)$(prefix)/bin/ - install fips $(DESTDIR)$(prefix)/bin/fips + mkdir -p $(DESTDIR)$(bindir) + install fips $(DESTDIR)$(bindir)/fips + mkdir -p $(DESTDIR)$(libdir)/fips + install -m0644 libfips.so $(DESTDIR)$(libdir)/fips/libfips.so ifeq ($(MAKECMDGOALS), install) @echo "" @echo "Fips is now installed to $(DESTDIR)$(prefix)" @echo "" endif -SRCS := $(SRCS) $(fips_srcs) -CLEAN := $(CLEAN) fips $(fips_modules) +SRCS := $(SRCS) $(fips_srcs) $(libfips_srcs) +CLEAN := $(CLEAN) fips $(fips_modules) $(libfips_modules) DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config diff --git a/configure b/configure index 91797ce..6c8b186 100755 --- a/configure +++ b/configure @@ -42,7 +42,6 @@ LDFLAGS=${LDFLAGS:-} # Set the defaults for values the user can specify with command-line # options. PREFIX=/usr/local -LIBDIR= usage () { @@ -82,8 +81,8 @@ specify an installation prefix other than $PREFIX using Fine tuning of some installation directories is available: + --bindir=DIR Install executables to DIR [PREFIX/bin] --libdir=DIR Install libraries to DIR [PREFIX/lib] - --includedir=DIR Install header files to DIR [PREFIX/include] --mandir=DIR Install man pages to DIR [PREFIX/share/man] --sysconfdir=DIR Read-only single-machine data [PREFIX/etc] @@ -102,6 +101,36 @@ configure-script calling conventions, but don't do anything yet: EOF } +# Given two absolute paths ("from" and "to"), compute a relative path +# from "from" to "to". For example: +# +# relative_path /foo/bar/baz /foo/qux -> ../../qux +relative_path () +{ + if [ $# -ne 2 ] ; then + echo "Internal error: relative_path requires exactly 2 arguments" + exit 1; + fi + + from="$1" + to="$2" + + # Handle trivial case up-front + if [ "$from" = "$to" ] ; then + echo "" + else + shared="$from" + relative="" + + while [ "${to#$shared}" = "$to" ] && [ "$shared" != "." ] ; do + shared="$(dirname $shared)" + relative="..${relative:+/${relative}}" + done + + echo "${relative:-.}${to#$shared}" + fi +} + # Parse command-line options for option; do if [ "${option}" = '--help' ] ; then @@ -109,10 +138,10 @@ for option; do exit 0 elif [ "${option%%=*}" = '--prefix' ] ; then PREFIX="${option#*=}" + elif [ "${option%%=*}" = '--bindir' ] ; then + BINDIR="${option#*=}" elif [ "${option%%=*}" = '--libdir' ] ; then LIBDIR="${option#*=}" - elif [ "${option%%=*}" = '--includedir' ] ; then - INCLUDEDIR="${option#*=}" elif [ "${option%%=*}" = '--mandir' ] ; then MANDIR="${option#*=}" elif [ "${option%%=*}" = '--sysconfdir' ] ; then @@ -142,17 +171,6 @@ for option; do fi done -# We set this value early, (rather than just while printing the -# Makefile.config file later like most values), because we need to -# actually investigate this value compared to the ldconfig_paths value -# below. -if [ -z "$LIBDIR" ] ; then - libdir_expanded="${PREFIX}/lib" -else - # very non-general variable expansion - libdir_expanded=`echo "$LIBDIR" | sed "s|\\${prefix}|${PREFIX}|g; s|\\$prefix/|${PREFIX}/|; s|//*|/|g"` -fi - cat </dev/null | sed -n -e 's,^\(/.*\):\( (.*)\)\?$,\1,p') - # Separate ldconfig_paths only on newline (not on any potential - # embedded space characters in any filenames). Note, we use a - # literal newline in the source here rather than something like: - # - # IFS=$(printf '\n') - # - # because the shell's command substitution deletes any trailing newlines. - IFS=" -" - for path in $ldconfig_paths; do - if [ "$path" = "$libdir_expanded" ]; then - libdir_in_ldconfig=1 - fi - done - IFS=$DEFAULT_IFS - if [ "$libdir_in_ldconfig" = '0' ]; then - printf "No (will set RPATH)\n" - else - printf "Yes\n" - fi else printf "Unknown.\n" cat < config.h < #include +#include +#include + #include #include +#include #include +#include + +#include + +#include "execute.h" + +/* Terminate a string representing a filename at the final '/' to + * eliminate the final filename component, (leaving only the directory + * portions of the original path). + * + * Notes: A path containing no '/' character will not be modified. + * A path consisting only of "/" will not be modified. + */ +static void +chop_trailing_path_component (char *path) +{ + char *slash; + + slash = strrchr (path, '/'); + + if (slash == NULL) + return; + + if (slash == path) + return; + + *slash = '\0'; +} + +/* Find the absolute path of the currently executing binary. + * + * Returns: a string talloc'ed to 'ctx' + */ +static char * +get_bin_name (void *ctx) +{ + const char *link = "/proc/self/exe"; + char *name; + + /* Yes, PATH_MAX is cheesy. I would have preferred to have + * used lstat and read the resulting st_size, but everytime I + * did that with /proc/self/exe I got a value of 0, (whereas + * with a "real" symbolic link I make myself I get the length + * of the filename being linked to). Go figure. */ + int name_len = PATH_MAX + 1; + + name = talloc_size (ctx, name_len - 1); + if (name == NULL) { + fprintf (stderr, "Out of memory.\n"); + exit (1); + } + + name_len = readlink (link, name, name_len); + if (name_len < 0) { + fprintf (stderr, "Failed to readlink %s: %s\n", link, + strerror (errno)); + exit (1); + } + + name[name_len + 1] = '\0'; + + return name; +} + +/* Does path exist? */ static int -fork_exec_and_wait (char * const argv[]) +exists (const char *path) +{ + struct stat st; + int err; + + err = stat (path, &st); + + /* Failed to stat. It either doesn't exist, or might as well not. */ + if (err == -1) + return 0; + + return 1; +} + +/* Given "library" filename resolve it to an absolute path to an + * existing file as follows: + * + * 1. Look in same directory as current executable image + * + * This is to support running from the source directory, without + * having installed anything. + * + * 2. Look in relative path from $(foo)/$(bindir) to + * $(foo)/$(libdir)/fips based on $(foo) from current executable + * image and configured $(bindir) and $(libdir). + * + * We do this rather than looking directly at the configured + * $(libdir) to support cases where the application may have been + * moved after being installed, (in particular, we want to be + * particularly careful not to mix one program with a different + * wrapper---so this "nearest search" should most often be + * correct. + * + * Returns: a string talloc'ed to 'ctx' + */ +static char * +resolve_path (void *ctx, const char *library) +{ + char *bin_path, *lib_path; + + bin_path = get_bin_name (ctx); + + chop_trailing_path_component (bin_path); + + lib_path = talloc_asprintf(ctx, "%s/%s", bin_path, library); + + if (exists (lib_path)) + return lib_path; + + talloc_free (lib_path); + + lib_path = talloc_asprintf(ctx, "%s/" BINDIR_TO_LIBFIPSDIR "/%s", + bin_path, library); + + if (exists (lib_path)) + return lib_path; + + fprintf (stderr, "Error: Failed to find library %s.\n", library); + fprintf (stderr, "Looked in both:\n" + "\t%s\n" + "and\n" + "\t%s/" BINDIR_TO_LIBFIPSDIR "\n", bin_path, bin_path); + exit (1); +} + +/* After forking, set LD_PRELOAD to preload "library" within child + * environment, then exec given arguments. + * + * The "library" argument is the filename (without path) of a shared + * library to load. The complete path will be resolved with + * resolve_library_path above. */ +static int +fork_exec_with_preload_and_wait (char * const argv[], const char *library) { pid_t pid; int i, status; @@ -36,6 +179,15 @@ fork_exec_and_wait (char * const argv[]) /* Child */ if (pid == 0) { + void *ctx = talloc_new (NULL); + char *lib_path; + + lib_path = resolve_path (ctx, library); + + setenv ("LD_PRELOAD", lib_path, 1); + + talloc_free (ctx); + execvp (argv[0], argv); fprintf (stderr, "Failed to execute:"); for (i = 0; argv[i]; i++) { @@ -58,7 +210,7 @@ fork_exec_and_wait (char * const argv[]) } int -execute (int argc, char * const argv[]) +execute_with_preload (int argc, char * const argv[], const char *library) { char **execvp_args; int i; @@ -76,5 +228,5 @@ execute (int argc, char * const argv[]) /* execvp needs final NULL */ execvp_args[i] = NULL; - return fork_exec_and_wait (execvp_args); + return fork_exec_with_preload_and_wait (execvp_args, library); } diff --git a/execute.h b/execute.h index 8b9192f..03af777 100644 --- a/execute.h +++ b/execute.h @@ -22,9 +22,11 @@ #ifndef EXECUTE_H #define EXECUTE_H -/* Execute the program with arguments as specified. +/* Execute the program with arguments as specified, but with the + * library specified in "library" pre-loaded. The library should be + * specified as the compiled filename (such as libfips.so). */ int -execute (int argc, char * const argv[]); +execute_with_preload (int argc, char * const argv[], const char *library); #endif diff --git a/fips.c b/fips.c index dbb8158..98cd9ba 100644 --- a/fips.c +++ b/fips.c @@ -74,7 +74,7 @@ main (int argc, char *argv[]) exit (1); } - ret = execute (argc - optind, &argv[optind]); + ret = execute_with_preload (argc - optind, &argv[optind], "libfips.so"); return ret; } diff --git a/glxwrap.c b/glxwrap.c new file mode 100644 index 0000000..51b5faf --- /dev/null +++ b/glxwrap.c @@ -0,0 +1,96 @@ +/* Copyright © 2013, Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +typedef void (* fips_glXSwapBuffers_t)(Display *dpy, GLXDrawable drawable); + +static void * +lookup (const char *name) +{ + const char *libgl_filename = "libGL.so.1"; + static void *libgl_handle = NULL; + + if (! libgl_handle) { + libgl_handle = dlopen (libgl_filename, RTLD_NOW); + if (! libgl_handle) { + fprintf (stderr, "Error: Failed to dlopen %s\n", + libgl_filename); + exit (1); + } + } + + return dlsym (libgl_handle, name); +} + +static void +call_glXSwapBuffers (Display *dpy, GLXDrawable drawable) +{ + static fips_glXSwapBuffers_t real_glXSwapBuffers = NULL; + const char *name = "glXSwapBuffers"; + + if (! real_glXSwapBuffers) { + real_glXSwapBuffers = (fips_glXSwapBuffers_t) lookup (name); + if (! real_glXSwapBuffers) { + fprintf (stderr, "Error: Failed to find function %s.\n", + name); + return; + } + } + real_glXSwapBuffers (dpy, drawable); +} + +void +glXSwapBuffers (Display *dpy, GLXDrawable drawable) +{ + static int initialized = 0; + static int frames; + static struct timeval tv_start, tv_now; + + if (! initialized) { + frames = 0; + gettimeofday (&tv_start, NULL); + initialized = 1; + } + + call_glXSwapBuffers (dpy, drawable); + + frames++; + if (frames % 60 == 0) { + double fps; + gettimeofday (&tv_now, NULL); + + fps = (double) frames / (tv_now.tv_sec - tv_start.tv_sec + + (tv_now.tv_usec - tv_start.tv_usec) / 1.0e6); + + printf("FPS: %.3f\n", fps); + } +} diff --git a/libfips.sym b/libfips.sym new file mode 100644 index 0000000..28fff84 --- /dev/null +++ b/libfips.sym @@ -0,0 +1,6 @@ +{ +global: + glXSwapBuffers; +local: + *; +}; -- 2.43.0