diff --git a/debian/rules b/debian/rules
index 402da7a7612a9ea6b01611a7457e00f93759016c..f419379e51aa90f684cc8a05ff45ca1885276b0f 100755
--- a/debian/rules
+++ b/debian/rules
@@ -40,6 +40,7 @@ override_dh_auto_configure:
 		--libexecdir=/usr/libexec \
 		-Dgtk_doc=$(gtk_doc_has_cflags) \
 		-Dintrospection=false \
+		-Dmultiarch_tuple=$(DEB_HOST_MULTIARCH) \
 		$(NULL)
 
 override_dh_auto_build:
@@ -55,6 +56,10 @@ endif
 
 override_dh_auto_install:
 	DESTDIR=$(CURDIR)/debian/tmp ninja -C builddir install
+	ln -s ../../bin/$(DEB_HOST_MULTIARCH)-vulkaninfo \
+		debian/tmp/usr/libexec/steam-runtime-tools-0/
+	ln -s ../../bin/$(DEB_HOST_MULTIARCH)-wflinfo \
+		debian/tmp/usr/libexec/steam-runtime-tools-0/
 	for x in debian/tmp/usr/libexec/installed-tests/steam-runtime-tools-0/*; do \
 		if [ -f "$$x" ] \
 		&& cmp -s "$$x" \
diff --git a/helpers/meson.build b/helpers/meson.build
index bda568148f81491e91a78b84ac96be838b53b404..3c37a53a09cd5589c26c7e31bc4ac65f7fdf557c 100644
--- a/helpers/meson.build
+++ b/helpers/meson.build
@@ -25,10 +25,7 @@ executable(
   multiarch + '-true',
   'true.c',
   install : true,
-  install_dir : join_paths(
-    get_option('libexecdir'),
-    'steam-runtime-tools-' + api_major,
-  )
+  install_dir : pkglibexecdir,
 )
 
 executable(
@@ -37,10 +34,7 @@ executable(
   include_directories : project_include_dirs,
   dependencies : [json_glib, glib],
   install : true,
-  install_dir : join_paths(
-    get_option('libexecdir'),
-    'steam-runtime-tools-' + api_major,
-  )
+  install_dir : pkglibexecdir,
 )
 
 executable(
@@ -48,10 +42,7 @@ executable(
   'inspect-library.c',
   dependencies : [libdl],
   install : true,
-  install_dir : join_paths(
-    get_option('libexecdir'),
-    'steam-runtime-tools-' + api_major,
-  )
+  install_dir : pkglibexecdir,
 )
 
 executable(
diff --git a/meson.build b/meson.build
index 8f64fa14b85f25192e4b6f82daae9294ae74b70b..046d480fc378a232b9a778e115053032ae19d6df 100644
--- a/meson.build
+++ b/meson.build
@@ -150,7 +150,14 @@ libdl = c_compiler.find_library('dl', required : false)
 
 project_include_dirs = include_directories('.')
 
-if host_machine.cpu_family() == 'x86_64'
+pkglibexecdir = join_paths(
+  get_option('libexecdir'),
+  'steam-runtime-tools-' + api_major,
+)
+
+if get_option('multiarch_tuple') != ''
+  multiarch = get_option('multiarch_tuple')
+elif host_machine.cpu_family() == 'x86_64'
   multiarch = 'x86_64-linux-gnu'
 elif host_machine.cpu_family() == 'x86'
   multiarch = 'i386-linux-gnu'
diff --git a/meson_options.txt b/meson_options.txt
index 9e52f2064503d274bab92928bfccfaaf2005341f..e4e20172277ad0b8b17a92d9bb4e307fa2cdf348 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -25,3 +25,10 @@ option(
   value : true,
   description : 'enable man pages',
 )
+
+option(
+  'multiarch_tuple',
+  type : 'string',
+  value : '',
+  description : 'Debian-style multiarch tuple',
+)
diff --git a/steam-runtime-tools/architecture.c b/steam-runtime-tools/architecture.c
index 00f2d6720cff49d76b293aa8accabad3108b870d..72bdda2d1224567228f512dcbc8317e0d6a8bb41 100644
--- a/steam-runtime-tools/architecture.c
+++ b/steam-runtime-tools/architecture.c
@@ -26,6 +26,7 @@
 #include "steam-runtime-tools/architecture.h"
 #include "steam-runtime-tools/architecture-internal.h"
 
+#include "steam-runtime-tools/glib-compat.h"
 #include "steam-runtime-tools/utils.h"
 #include "steam-runtime-tools/utils-internal.h"
 
@@ -47,7 +48,7 @@ _srt_architecture_can_run (const char *helpers_path,
                            const char *multiarch)
 {
   gchar *helper = NULL;
-  const gchar *argv[] = { "true", NULL };
+  GPtrArray *argv = NULL;
   int exit_status = -1;
   GError *error = NULL;
   gboolean ret = FALSE;
@@ -58,12 +59,18 @@ _srt_architecture_can_run (const char *helpers_path,
   g_return_val_if_fail (multiarch != NULL, FALSE);
   g_return_val_if_fail (_srt_check_not_setuid (), FALSE);
 
-  if (helpers_path == NULL)
-    helpers_path = _srt_get_helpers_path ();
+  argv = _srt_get_helper (helpers_path, multiarch, "true",
+                          SRT_HELPER_FLAGS_NONE, &error);
+  if (argv == NULL)
+    {
+      g_debug ("%s", error->message);
+      goto out;
+    }
+
+  g_ptr_array_add (argv, NULL);
 
-  helper = g_strdup_printf ("%s/%s-true", helpers_path, multiarch);
-  argv[0] = helper;
-  g_debug ("Testing architecture %s with %s", multiarch, helper);
+  g_debug ("Testing architecture %s with %s",
+           multiarch, (const char *) g_ptr_array_index (argv, 0));
 
   my_environ = g_get_environ ();
   ld_preload = g_environ_getenv (my_environ, "LD_PRELOAD");
@@ -74,7 +81,7 @@ _srt_architecture_can_run (const char *helpers_path,
     }
 
   if (!g_spawn_sync (NULL,       /* working directory */
-                     (gchar **) argv,
+                     (gchar **) argv->pdata,
                      my_environ, /* envp */
                      0,          /* flags */
                      NULL,       /* child setup */
@@ -101,6 +108,7 @@ out:
   g_free (helper);
   g_free (filtered_preload);
   g_clear_error (&error);
+  g_clear_pointer (&argv, g_ptr_array_unref);
   return ret;
 }
 
diff --git a/steam-runtime-tools/graphics.c b/steam-runtime-tools/graphics.c
index 558c53e38a4ea41914ad8ff2b6ab556854cc33eb..e861db43c78b2427bf066baef88aa14ff140a3a7 100644
--- a/steam-runtime-tools/graphics.c
+++ b/steam-runtime-tools/graphics.c
@@ -401,13 +401,16 @@ _argv_for_graphics_test (const char *helpers_path,
                          SrtTestFlags test_flags,
                          const char *multiarch_tuple,
                          SrtWindowSystem window_system,
-                         SrtRenderingInterface rendering_interface)
+                         SrtRenderingInterface rendering_interface,
+                         GError **error)
 {
   GPtrArray *argv = NULL;
-
   gchar *platformstring = NULL;
+  SrtHelperFlags flags = (SRT_HELPER_FLAGS_TIME_OUT
+                          | SRT_HELPER_FLAGS_SEARCH_PATH);
 
-  argv = g_ptr_array_new_with_free_func (g_free);
+  if (test_flags & SRT_TEST_FLAGS_TIME_OUT_SOONER)
+    flags |= SRT_HELPER_FLAGS_TIME_OUT_SOONER;
 
   if (window_system == SRT_WINDOW_SYSTEM_GLX)
     {
@@ -469,62 +472,34 @@ _argv_for_graphics_test (const char *helpers_path,
       g_return_val_if_reached (NULL);
     }
 
-  // Use timeout command to limit how long the helper can run
-  g_ptr_array_add (argv, g_strdup ("timeout"));
-  g_ptr_array_add (argv, g_strdup ("--signal=TERM"));
-
-  if (test_flags & SRT_TEST_FLAGS_TIME_OUT_SOONER)
-    {
-      // Speed up the failing case in automated testing
-      g_ptr_array_add (argv, g_strdup ("--kill-after=1"));
-      g_ptr_array_add (argv, g_strdup ("1"));
-    }
-  else
+  if (rendering_interface == SRT_RENDERING_INTERFACE_GL
+      || rendering_interface == SRT_RENDERING_INTERFACE_GLESV2)
     {
-      // Kill the helper (if still running) 3 seconds after the TERM signal
-      g_ptr_array_add (argv, g_strdup ("--kill-after=3"));
-      // Send TERM signal after 10 seconds
-      g_ptr_array_add (argv, g_strdup ("10"));
-    }
+      const char *api;
 
-  if (rendering_interface == SRT_RENDERING_INTERFACE_GL)
-    {
-      if (helpers_path != NULL)
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s/%s-wflinfo", helpers_path, multiarch_tuple));
-        }
-      else
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s-wflinfo", multiarch_tuple));
-        }
-      g_ptr_array_add (argv, g_strdup_printf ("--platform=%s", platformstring));
-      g_ptr_array_add (argv, g_strdup ("--api=gl"));
-      g_ptr_array_add (argv, g_strdup ("--format=json"));
-    }
-  else if (rendering_interface == SRT_RENDERING_INTERFACE_GLESV2)
-    {
-      if (helpers_path != NULL)
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s/%s-wflinfo", helpers_path, multiarch_tuple));
-        }
+      argv = _srt_get_helper (helpers_path, multiarch_tuple, "wflinfo", flags,
+                              error);
+
+      if (argv == NULL)
+        goto out;
+
+      if (rendering_interface == SRT_RENDERING_INTERFACE_GLESV2)
+        api = "gles2";
       else
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s-wflinfo", multiarch_tuple));
-        }
+        api = "gl";
+
       g_ptr_array_add (argv, g_strdup_printf ("--platform=%s", platformstring));
-      g_ptr_array_add (argv, g_strdup ("--api=gles2"));
+      g_ptr_array_add (argv, g_strdup_printf ("--api=%s", api));
       g_ptr_array_add (argv, g_strdup ("--format=json"));
     }
   else if (rendering_interface == SRT_RENDERING_INTERFACE_VULKAN)
     {
-      if (helpers_path != NULL)
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s/%s-vulkaninfo", helpers_path, multiarch_tuple));
-        }
-      else
-        {
-          g_ptr_array_add (argv, g_strdup_printf ("%s-vulkaninfo", multiarch_tuple));
-        }
+      argv = _srt_get_helper (helpers_path, multiarch_tuple, "vulkaninfo",
+                              flags, error);
+
+      if (argv == NULL)
+        goto out;
+
       g_ptr_array_add (argv, g_strdup ("-j"));
     }
   else
@@ -536,8 +511,8 @@ _argv_for_graphics_test (const char *helpers_path,
 
   g_ptr_array_add (argv, NULL);
 
+out:
   g_free (platformstring);
-
   return argv;
 }
 
@@ -545,34 +520,22 @@ _argv_for_graphics_test (const char *helpers_path,
 static GPtrArray *
 _argv_for_check_vulkan (const char *helpers_path,
                         SrtTestFlags test_flags,
-                        const char *multiarch_tuple)
+                        const char *multiarch_tuple,
+                        GError **error)
 {
-  GPtrArray *argv = g_ptr_array_new_with_free_func (g_free);
-
-  // Use timeout command to limit how long the helper can run
-  g_ptr_array_add (argv, g_strdup ("timeout"));
-  g_ptr_array_add (argv, g_strdup ("--signal=TERM"));
+  GPtrArray *argv;
+  SrtHelperFlags flags = SRT_HELPER_FLAGS_TIME_OUT;
 
   if (test_flags & SRT_TEST_FLAGS_TIME_OUT_SOONER)
-    {
-      // Speed up the failing case in automated testing
-      g_ptr_array_add (argv, g_strdup ("--kill-after=1"));
-      g_ptr_array_add (argv, g_strdup ("1"));
-    }
-  else
-    {
-      // Kill the helper (if still running) 3 seconds after the TERM signal
-      g_ptr_array_add (argv, g_strdup ("--kill-after=3"));
-      // Send TERM signal after 10 seconds
-      g_ptr_array_add (argv, g_strdup ("10"));
-    }
+    flags |= SRT_HELPER_FLAGS_TIME_OUT_SOONER;
 
-  if (helpers_path == NULL)
-    helpers_path = _srt_get_helpers_path ();
+  argv = _srt_get_helper (helpers_path, multiarch_tuple, "check-vulkan",
+                          flags, error);
 
-  g_ptr_array_add (argv, g_strdup_printf ("%s/%s-check-vulkan", helpers_path, multiarch_tuple));
-  g_ptr_array_add (argv, NULL);
+  if (argv == NULL)
+    return NULL;
 
+  g_ptr_array_add (argv, NULL);
   return argv;
 }
 
@@ -624,10 +587,16 @@ _srt_check_graphics (const char *helpers_path,
                                              test_flags,
                                              multiarch_tuple,
                                              window_system,
-                                             rendering_interface);
+                                             rendering_interface,
+                                             &error);
 
   if (argv == NULL)
-    return SRT_GRAPHICS_ISSUES_INTERNAL_ERROR;
+    {
+      issues |= SRT_GRAPHICS_ISSUES_CANNOT_LOAD;
+      /* Put the error message in the 'messages' */
+      child_stderr = g_strdup (error->message);
+      goto out;
+    }
 
   my_environ = g_get_environ ();
   ld_preload = g_environ_getenv (my_environ, "LD_PRELOAD");
@@ -698,9 +667,16 @@ _srt_check_graphics (const char *helpers_path,
 
       argv = _argv_for_check_vulkan (helpers_path,
                                      test_flags,
-                                     multiarch_tuple);
+                                     multiarch_tuple,
+                                     &error);
 
-      g_return_val_if_fail (argv != NULL, SRT_GRAPHICS_ISSUES_INTERNAL_ERROR);
+      if (argv == NULL)
+        {
+          issues |= SRT_GRAPHICS_ISSUES_CANNOT_DRAW;
+          /* Put the error message in the 'messages' */
+          child_stderr2 = g_strdup (error->message);
+          goto out;
+        }
 
       /* Now run and report exit code/messages if failure */
       if (!g_spawn_sync (NULL,    /* working directory */
@@ -715,19 +691,10 @@ _srt_check_graphics (const char *helpers_path,
                          &error))
         {
           g_debug ("An error occurred calling the helper: %s", error->message);
-          issues |= SRT_GRAPHICS_ISSUES_CANNOT_LOAD;
+          issues |= SRT_GRAPHICS_ISSUES_CANNOT_DRAW;
           goto out;
         }
 
-      if (child_stderr2 != NULL && child_stderr2[0] != '\0')
-        {
-          gchar *tmp = g_strconcat (child_stderr, child_stderr2, NULL);
-
-          g_free (child_stderr);
-          child_stderr = tmp;
-        }
-      g_free (child_stderr2);
-
       if (exit_status != 0)
         {
           g_debug ("... wait status %d", exit_status);
@@ -743,6 +710,16 @@ _srt_check_graphics (const char *helpers_path,
     }
 
 out:
+  /* If we have stderr (or error messages) from both vulkaninfo and
+   * check-vulkan, combine them */
+  if (child_stderr2 != NULL && child_stderr2[0] != '\0')
+    {
+      gchar *tmp = g_strconcat (child_stderr, child_stderr2, NULL);
+
+      g_free (child_stderr);
+      child_stderr = tmp;
+    }
+
   if (details_out != NULL)
     *details_out = _srt_graphics_new (multiarch_tuple,
                                       window_system,
@@ -756,9 +733,10 @@ out:
     g_object_unref (parser);
 
   g_free (new_version_string);
-  g_ptr_array_unref (argv);
+  g_clear_pointer (&argv, g_ptr_array_unref);
   g_free (output);
   g_free (child_stderr);
+  g_free (child_stderr2);
   g_clear_error (&error);
   g_free (filtered_preload);
   g_strfreev (my_environ);
diff --git a/steam-runtime-tools/library.c b/steam-runtime-tools/library.c
index 19789f77c8e43631bcbd25cd0837305022c11a7b..681d4bb98980d5d035d12aa7dfc6f69cf564ebf1 100644
--- a/steam-runtime-tools/library.c
+++ b/steam-runtime-tools/library.c
@@ -27,6 +27,7 @@
 
 #include "steam-runtime-tools/architecture.h"
 #include "steam-runtime-tools/enums.h"
+#include "steam-runtime-tools/glib-compat.h"
 #include "steam-runtime-tools/library-internal.h"
 #include "steam-runtime-tools/utils.h"
 #include "steam-runtime-tools/utils-internal.h"
@@ -506,7 +507,6 @@ _srt_check_library_presence (const char *helpers_path,
   GStrv my_environ = NULL;
   const gchar *ld_preload;
   gchar *filtered_preload = NULL;
-  argv = g_ptr_array_new_with_free_func (g_free);
 
   g_return_val_if_fail (soname != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
   g_return_val_if_fail (multiarch != NULL, SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
@@ -514,10 +514,21 @@ _srt_check_library_presence (const char *helpers_path,
                         SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
   g_return_val_if_fail (_srt_check_not_setuid (), SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
 
-  if (helpers_path == NULL)
-    helpers_path = _srt_get_helpers_path ();
+  if (symbols_path == NULL)
+    issues |= SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS;
+
+  argv = _srt_get_helper (helpers_path, multiarch, "inspect-library",
+                          SRT_HELPER_FLAGS_NONE, &error);
+
+  if (argv == NULL)
+    {
+      issues |= SRT_LIBRARY_ISSUES_CANNOT_LOAD;
+      /* Use the error message as though the child had printed it on stderr -
+       * either way, it's a useful diagnostic */
+      child_stderr = g_strdup (error->message);
+      goto out;
+    }
 
-  g_ptr_array_add (argv, g_strdup_printf ("%s/%s-inspect-library", helpers_path, multiarch));
   g_debug ("Checking library %s integrity with %s", soname, (gchar *)g_ptr_array_index (argv, 0));
 
   switch (symbols_format)
@@ -534,7 +545,7 @@ _srt_check_library_presence (const char *helpers_path,
         break;
 
       default:
-        g_return_val_if_reached (SRT_LIBRARY_ISSUES_CANNOT_LOAD);
+        g_return_val_if_reached (SRT_LIBRARY_ISSUES_INTERNAL_ERROR);
     }
 
   for (gsize i = 0; hidden_deps != NULL && hidden_deps[i] != NULL; i++)
@@ -546,9 +557,6 @@ _srt_check_library_presence (const char *helpers_path,
   /* NULL terminate the array */
   g_ptr_array_add (argv, NULL);
 
-  if (symbols_path == NULL)
-    issues |= SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS;
-
   my_environ = g_get_environ ();
   ld_preload = g_environ_getenv (my_environ, "LD_PRELOAD");
   if (ld_preload != NULL)
@@ -665,7 +673,7 @@ out:
   g_strfreev (missing_symbols);
   g_strfreev (misversioned_symbols);
   g_strfreev (dependencies);
-  g_ptr_array_free (argv, TRUE);
+  g_clear_pointer (&argv, g_ptr_array_unref);
   g_free (absolute_path);
   g_free (child_stderr);
   g_free (output);
diff --git a/steam-runtime-tools/locale.c b/steam-runtime-tools/locale.c
index e04c14a4163bee8e0fac13b31a460f6881583b7f..a59e08d8568b675892f4bd9f18bbb071373fde3c 100644
--- a/steam-runtime-tools/locale.c
+++ b/steam-runtime-tools/locale.c
@@ -225,6 +225,15 @@ _srt_check_locale (const char *helpers_path,
   g_return_val_if_fail (requested_name != NULL, NULL);
   g_return_val_if_fail (_srt_check_not_setuid (), NULL);
 
+  if (multiarch_tuple == NULL)
+    multiarch_tuple = _SRT_MULTIARCH;
+
+  argv = _srt_get_helper (helpers_path, multiarch_tuple, "check-locale",
+                          SRT_HELPER_FLAGS_NONE, error);
+
+  if (argv == NULL)
+    goto out;
+
   my_environ = g_get_environ ();
   ld_preload = g_environ_getenv (my_environ, "LD_PRELOAD");
   if (ld_preload != NULL)
@@ -234,16 +243,6 @@ _srt_check_locale (const char *helpers_path,
                                      filtered_preload, TRUE);
     }
 
-  argv = g_ptr_array_new_with_free_func (g_free);
-
-  if (helpers_path == NULL)
-    helpers_path = _srt_get_helpers_path ();
-
-  if (multiarch_tuple == NULL)
-    multiarch_tuple = _SRT_MULTIARCH;
-
-  g_ptr_array_add (argv, g_strdup_printf ("%s/%s-check-locale",
-                                          helpers_path, multiarch_tuple));
   g_ptr_array_add (argv, g_strdup (requested_name));
 
   g_ptr_array_add (argv, NULL);
@@ -350,7 +349,7 @@ out:
     }
 
   g_clear_object (&parser);
-  g_ptr_array_unref (argv);
+  g_clear_pointer (&argv, g_ptr_array_unref);
   g_free (output);
   g_free (filtered_preload);
   g_strfreev (my_environ);
diff --git a/steam-runtime-tools/utils-internal.h b/steam-runtime-tools/utils-internal.h
index 8fc5b0ecaed50282164de870a22a29e1674ae463..8f6b15e2d08cdb263a1686781c82e4e5de797142 100644
--- a/steam-runtime-tools/utils-internal.h
+++ b/steam-runtime-tools/utils-internal.h
@@ -28,5 +28,17 @@
 
 #include <glib.h>
 
-G_GNUC_INTERNAL const char *_srt_get_helpers_path (void);
+typedef enum
+{
+  SRT_HELPER_FLAGS_SEARCH_PATH = (1 << 0),
+  SRT_HELPER_FLAGS_TIME_OUT = (1 << 1),
+  SRT_HELPER_FLAGS_TIME_OUT_SOONER = (1 << 2),
+  SRT_HELPER_FLAGS_NONE = 0
+} SrtHelperFlags;
+
+G_GNUC_INTERNAL GPtrArray *_srt_get_helper (const char *helpers_path,
+                                            const char *multiarch,
+                                            const char *base,
+                                            SrtHelperFlags flags,
+                                            GError **error);
 gchar *_srt_filter_gameoverlayrenderer (const gchar *input);
diff --git a/steam-runtime-tools/utils.c b/steam-runtime-tools/utils.c
index c8a06acdd708bba569ddf5a9128d09d49a27fb0a..00d7346326f0f5d9045b778bccc3e612ebf91d25 100644
--- a/steam-runtime-tools/utils.c
+++ b/steam-runtime-tools/utils.c
@@ -39,6 +39,8 @@
 #endif
 
 #include <glib-object.h>
+#include <gio/gio.h>
+#include "steam-runtime-tools/glib-compat.h"
 
 #ifdef HAVE_GETAUXVAL
 #define getauxval_AT_SECURE() getauxval (AT_SECURE)
@@ -144,19 +146,20 @@ _srt_check_not_setuid (void)
   return !is_setuid;
 }
 
-static gchar *helpers_path = NULL;
+static gchar *global_helpers_path = NULL;
 
-G_GNUC_INTERNAL const char *
-_srt_get_helpers_path (void)
+static const char *
+_srt_get_helpers_path (GError **error)
 {
   const char *path;
   Dl_info ignored;
   struct link_map *map = NULL;
   gchar *dir;
 
-  g_return_val_if_fail (_srt_check_not_setuid (), "/");
+  g_return_val_if_fail (_srt_check_not_setuid (), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-  path = helpers_path;
+  path = global_helpers_path;
 
   if (path != NULL)
     goto out;
@@ -170,8 +173,9 @@ _srt_get_helpers_path (void)
                RTLD_DL_LINKMAP) == 0 ||
       map == NULL)
     {
-      g_warning ("Unable to locate shared library containing "
-                 "_srt_get_helpers_path()");
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Unable to locate shared library containing "
+                   "_srt_get_helpers_path()");
       goto out;
     }
 
@@ -195,22 +199,120 @@ _srt_get_helpers_path (void)
     }
 
   /* deliberate one-per-process leak */
-  helpers_path = g_build_filename (
+  global_helpers_path = g_build_filename (
       dir, "libexec", "steam-runtime-tools-" _SRT_API_MAJOR,
       NULL);
-  path = helpers_path;
+  path = global_helpers_path;
 
   g_free (dir);
 
 out:
-  /* We have to return *something* non-NULL */
-  if (path == NULL)
+  return path;
+}
+
+/*
+ * _srt_get_helper:
+ * @helpers_path: (nullable): Directory to search for helper executables,
+ *  or %NULL for default behaviour
+ * @multiarch: (nullable): A multiarch tuple like %SRT_ABI_I386 to prefix
+ *  to the executable name, or %NULL
+ * @base: (not nullable): Base name of the executable
+ * @flags: Flags affecting how we set up the helper
+ * @error: Used to raise an error if %NULL is returned
+ *
+ * Find a helper executable. We return an array of arguments so that the
+ * helper can be wrapped by an "adverb" like `env`, `timeout` or a
+ * specific `ld.so` implementation if required.
+ *
+ * Returns: (nullable) (element-type filename) (transfer container): The
+ *  initial `argv` for the helper, with g_free() set as the free-function, and
+ *  no %NULL terminator. Free with g_ptr_array_unref() or g_ptr_array_free().
+ */
+G_GNUC_INTERNAL GPtrArray *
+_srt_get_helper (const char *helpers_path,
+                 const char *multiarch,
+                 const char *base,
+                 SrtHelperFlags flags,
+                 GError **error)
+{
+  GPtrArray *argv = NULL;
+  gchar *path;
+  gchar *prefixed;
+
+  g_return_val_if_fail (base != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  argv = g_ptr_array_new_with_free_func (g_free);
+
+  if (flags & SRT_HELPER_FLAGS_TIME_OUT)
+    {
+      g_ptr_array_add (argv, g_strdup ("timeout"));
+      g_ptr_array_add (argv, g_strdup ("--signal=TERM"));
+
+      if (flags & SRT_HELPER_FLAGS_TIME_OUT_SOONER)
+        {
+          /* Speed up the failing case in automated testing */
+          g_ptr_array_add (argv, g_strdup ("--kill-after=1"));
+          g_ptr_array_add (argv, g_strdup ("1"));
+        }
+      else
+        {
+          /* Kill the helper (if still running) 3 seconds after the TERM
+           * signal */
+          g_ptr_array_add (argv, g_strdup ("--kill-after=3"));
+          /* Send TERM signal after 10 seconds */
+          g_ptr_array_add (argv, g_strdup ("10"));
+        }
+    }
+
+  if (helpers_path == NULL)
     {
-      g_warning ("Unable to determine path to helpers");
-      path = "/";
+      helpers_path = _srt_get_helpers_path (error);
+
+      if (helpers_path == NULL)
+        {
+          g_ptr_array_unref (argv);
+          return NULL;
+        }
     }
 
-  return path;
+  /* Prefer a helper from ${SRT_HELPERS_PATH} or
+   * ${libexecdir}/steam-runtime-tools-${_SRT_API_MAJOR}
+   * if it exists */
+  path = g_strdup_printf ("%s/%s%s%s",
+                          helpers_path,
+                          multiarch == NULL ? "" : multiarch,
+                          multiarch == NULL ? "" : "-",
+                          base);
+
+  g_debug ("Looking for %s", path);
+
+  if (g_file_test (path, G_FILE_TEST_IS_EXECUTABLE))
+    {
+      g_ptr_array_add (argv, g_steal_pointer (&path));
+      return argv;
+    }
+
+  if ((flags & SRT_HELPER_FLAGS_SEARCH_PATH) == 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                   "%s not found", path);
+      g_free (path);
+      g_ptr_array_unref (argv);
+      return NULL;
+    }
+
+  /* For helpers that are not part of steam-runtime-tools, such as
+   * *-wflinfo and *-vulkaninfo, we fall back to searching $PATH */
+  g_free (path);
+
+  if (multiarch == NULL)
+    prefixed = g_strdup (base);
+  else
+    prefixed = g_strdup_printf ("%s-%s", multiarch, base);
+
+  g_ptr_array_add (argv, g_steal_pointer (&prefixed));
+  return argv;
 }
 
 /**