diff --git a/zircon/public/gn/config/BUILD.gn b/zircon/public/gn/config/BUILD.gn
index f7ccccbb3a8d7524326f3d41fdf5edb3d99dcf16..23665bc2dac386c1cd5d568a1f7c094c501b81d2 100644
--- a/zircon/public/gn/config/BUILD.gn
+++ b/zircon/public/gn/config/BUILD.gn
@@ -520,11 +520,17 @@ config("user_executable") {
     compiler_flags = [ "-fPIE" ]
     asmflags = compiler_flags
     cflags = compiler_flags
-    ldflags = compiler_flags
-    ldflags += [
-      "-Wl,-pie",
-      "-Wl,-dynamic-linker,ld.so.1",
-    ]
+    ldflags = compiler_flags + [ "-Wl,-pie" ]
+  } else {
+    ldflags = []
+  }
+
+  # Specify the dynamic linker if building a variant that uses a separate
+  # set of libraries.  With GCC, the dynamic linker must be explicit even
+  # in the default case because the compiler driver is not inherently
+  # configured for Fuchsia as it is in Clang.
+  if (is_gcc || toolchain.libprefix != "") {
+    ldflags += [ "-Wl,-dynamic-linker,${toolchain.libprefix}ld.so.1" ]
   }
 }
 
diff --git a/zircon/public/gn/config/standard.gni b/zircon/public/gn/config/standard.gni
index dd303222d567fa6b84ddba7fb64083e1c9a4174c..9ea36bf1f7bbefd61c36627f31dde31e17778f57 100644
--- a/zircon/public/gn/config/standard.gni
+++ b/zircon/public/gn/config/standard.gni
@@ -233,9 +233,6 @@ standard_variants = [
           add = [ "$zx/system/core/devmgr:driver_deps.$name" ]
         },
       ]
-      toolchain_vars = {
-        libprefix = "asan/"
-      }
       tags = [
         "instrumented",
         "useronly",
diff --git a/zircon/public/gn/toolchain/environment.gni b/zircon/public/gn/toolchain/environment.gni
index 6acbdf2dfb6ae05197908d5ae30e4c4fcdd666f7..13ee1b9ba2309a9e16b45c582ede40503e455341 100644
--- a/zircon/public/gn/toolchain/environment.gni
+++ b/zircon/public/gn/toolchain/environment.gni
@@ -38,19 +38,21 @@ declare_args() {
   # Selector scope parameters
   #
   #   * variant
-  #     - Required: The variant to use when this selector matches.  If this is a
-  #     string then it must match a fully-defined variant elsewhere in the
-  #     list (or in $default_variants + $standard_variants, which is appended
-  #     implicitly to the $variants list).  If it's a scope then it defines a
-  #     new variant (see details below).
+  #     - Required: The variant to use when this selector matches.  If this
+  #     is a string then it must match a fully-defined variant elsewhere in
+  #     the list (or in $default_variants + $standard_variants, which is
+  #     appended implicitly to the $variants list).  If it's a scope then
+  #     it defines a new variant (see details below).
   #     - Type: string or scope, described below
   #
   #   * cpu
-  #     - Optional: If nonempty, match only when $current_cpu is one in the list.
+  #     - Optional: If nonempty, match only when $current_cpu is one in the
+  #     - list.
   #     - Type: list(string)
   #
   #   * os
-  #     - Optional: If nonempty, match only when $current_os is one in the list.
+  #     - Optional: If nonempty, match only when $current_os is one in the
+  #     - list.
   #     - Type: list(string)
   #
   #   * host
@@ -82,8 +84,9 @@ declare_args() {
   #     - Type: list(string)
   #
   #   * target_type
-  #     - Optional: If nonempty, a list of target types to match.  This is one of
-  #     "executable", "host_tool", "loadable_module", "driver", or "test".
+  #     - Optional: If nonempty, a list of target types to match.  This is
+  #     one of "executable", "host_tool", "loadable_module", "driver", or
+  #     "test".
   #     Note, test_driver() matches as "driver".
   #     - Type: list(string)
   #
@@ -106,9 +109,10 @@ declare_args() {
   #     - Type: list(label_no_toolchain)
   #
   #   * output_name
-  #     - Optional: If nonempty, match only when the `output_name` of the target
-  #     is one in the list.  Note `output_name` defaults to `target_name`,
-  #     and does not include prefixes or suffixes like ".so" or ".exe".
+  #     - Optional: If nonempty, match only when the `output_name` of the
+  #     target is one in the list.  Note `output_name` defaults to
+  #     `target_name`, and does not include prefixes or suffixes like ".so"
+  #     or ".exe".
   #     - Type: list(string)
   #
   # An element with a scope for `.variant` defines a new variant.  Each
@@ -297,14 +301,23 @@ declare_args() {
 #     This is just like $toolchain_args passed to toolchain() in bare GN.
 #
 #   * toolchain_vars
-#     - Optional: A scope imported into the $toolchain scope in these toolchains.
-#     This can store useful toolchain-specific variables that should be
-#     available within the toolchain.  $toolchain automatically contains
-#     `tool_dir`, `tool_prefix`, `cc, and `cxx`, from c_toolchain().
-#     If `variant_suffix` is defined here, terminal targets in the environment
-#     will have "$target_name$variant_suffix" aliases that get the same thing
-#     but in that particular variant installed under the suffixed name.
-#     The default `variant_suffix` is ".$variant" in each variant.
+#     - Optional: A scope imported into the $toolchain scope in these
+#     toolchains.  This can store useful toolchain-specific variables that
+#     should be available within the toolchain.  $toolchain automatically
+#     contains `tool_dir`, `tool_prefix`, `cc, and `cxx`, from
+#     c_toolchain().  If `variant_suffix` is defined here, terminal targets
+#     in the environment will have "$target_name$variant_suffix" aliases
+#     that get the same thing but in that particular variant installed
+#     under the suffixed name.  The default `variant_suffix` is ".$variant"
+#     in each variant.
+#
+#   * variant_libprefix
+#     - Optional: If true, each variant's name is used to form the
+#     ${toolchain.libprefix} value in that variant.  The final libprefix
+#     is "${toolchain_vars.libprefix}${toolchain.variant}/".  By default,
+#     this is true *only* in variants that have the "instrumented" tag.
+#     If explicitly set to true or false, that applies to *all* variants.
+#     - Type: bool
 #
 #   * variant_selectors
 #     - Optional: A list in the schema of the $variants build argument.  This
@@ -324,7 +337,7 @@ declare_args() {
 #     .variant scope.  If any of these strings appears in a .variant scope,
 #     then $variant_selectors elements using that variant will be silently
 #     ignored in this environment's toolchains.  e.g. an environment that is
-#     incompatbile with instrumentation could list the "instrumentation" tag
+#     incompatbile with instrumentation could list the "instrumented" tag
 #     and variants that enable instrumentation will be defined with that tag.
 #     Then simple `variants=["asan"]` user configurations that would apply
 #     ordinarily to all targets don't break the special-case execution
@@ -905,15 +918,21 @@ template("environment") {
           # `variant`, so it can't be used any more in this block.)
           variant = variant.name
 
+          if (!defined(libprefix)) {
+            libprefix = ""
+          }
+          if ((defined(invoker.variant_libprefix) &&
+               invoker.variant_libprefix) ||
+              (!defined(invoker.variant_libprefix) &&
+               tags + [ "instrumented" ] - [ "instrumented" ] != tags)) {
+            libprefix += "$variant/"
+          }
+
           # Plumb through the suffix of this variant and the list of others.
           if (!defined(variant_suffix)) {
             variant_suffix = ".$variant"
           }
 
-          if (!defined(libprefix)) {
-            libprefix = ""
-          }
-
           other_variants = []
           foreach(other_variant, toolchain_variants) {
             if (other_variant.name != variant) {
diff --git a/zircon/third_party/ulib/musl/ldso/BUILD.gn b/zircon/third_party/ulib/musl/ldso/BUILD.gn
index 71b4c4da4c6ce47290fb42cc3e2be85e6f78d361..493979e284092e90e7c19d2b6793dfcf1f893450 100644
--- a/zircon/third_party/ulib/musl/ldso/BUILD.gn
+++ b/zircon/third_party/ulib/musl/ldso/BUILD.gn
@@ -13,4 +13,11 @@ source_set("ldso") {
     "dynlink-sancov.S",
     "dynlink.c",
   ]
+  if (toolchain.libprefix != "") {
+    # The libprefix always ends with a / but that's not part of the
+    # "config" string in the loader-service protocol.
+    ldsvc_config = get_path_info("${toolchain.libprefix}libfoo.so", "dir")
+    assert(ldsvc_config != "" && ldsvc_config != ".")
+    defines = [ "DYNLINK_LDSVC_CONFIG=\"$ldsvc_config\"" ]
+  }
 }
diff --git a/zircon/third_party/ulib/musl/ldso/dynlink.c b/zircon/third_party/ulib/musl/ldso/dynlink.c
index ee8d26fad677071df36a3922c76a3c93c73be54b..bf7248076762aa63200db9bcd158598bc22f9eec 100644
--- a/zircon/third_party/ulib/musl/ldso/dynlink.c
+++ b/zircon/third_party/ulib/musl/ldso/dynlink.c
@@ -2090,6 +2090,11 @@ __NO_SAFESTACK NO_ASAN static dl_start_return_t __dls3(void* start_arg) {
 __NO_SAFESTACK NO_ASAN static void early_init(void) {
 #if __has_feature(address_sanitizer)
     __asan_early_init();
+#endif
+#ifdef DYNLINK_LDSVC_CONFIG
+    // Inform the loader service to look for libraries of the right variant.
+    loader_svc_config(DYNLINK_LDSVC_CONFIG);
+#elif __has_feature(address_sanitizer)
     // Inform the loader service that we prefer ASan-supporting libraries.
     loader_svc_config("asan");
 #endif