Commit 2f9c5c0644

Andrew Kelley <andrew@ziglang.org>
2020-02-17 21:23:59
self-host dynamic linker detection
1 parent c784c52
lib/std/os.zig
@@ -2974,18 +2974,26 @@ pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
 }
 
 pub fn dl_iterate_phdr(
-    comptime T: type,
-    callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*T) i32,
-    data: ?*T,
-) isize {
+    context: var,
+    comptime Error: type,
+    comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void,
+) Error!void {
+    const Context = @TypeOf(context);
+
     if (builtin.object_format != .elf)
         @compileError("dl_iterate_phdr is not available for this target");
 
     if (builtin.link_libc) {
-        return system.dl_iterate_phdr(
-            @ptrCast(std.c.dl_iterate_phdr_callback, callback),
-            @ptrCast(?*c_void, data),
-        );
+        switch (system.dl_iterate_phdr(struct {
+            fn callbackC(info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int {
+                const context_ptr = @ptrCast(*const Context, @alignCast(@alignOf(*const Context), data));
+                callback(info, size, context_ptr.*) catch |err| return @errorToInt(err);
+                return 0;
+            }
+        }.callbackC, @intToPtr(?*c_void, @ptrToInt(&context)))) {
+            0 => return,
+            else => |err| return @errSetCast(Error, @intToError(@intCast(u16, err))), // TODO don't hardcode u16
+        }
     }
 
     const elf_base = std.process.getBaseAddress();
@@ -3007,11 +3015,10 @@ pub fn dl_iterate_phdr(
             .dlpi_phnum = ehdr.e_phnum,
         };
 
-        return callback(&info, @sizeOf(dl_phdr_info), data);
+        return callback(&info, @sizeOf(dl_phdr_info), context);
     }
 
     // Last return value from the callback function
-    var last_r: isize = 0;
     while (it.next()) |entry| {
         var dlpi_phdr: [*]elf.Phdr = undefined;
         var dlpi_phnum: u16 = undefined;
@@ -3033,11 +3040,8 @@ pub fn dl_iterate_phdr(
             .dlpi_phnum = dlpi_phnum,
         };
 
-        last_r = callback(&info, @sizeOf(dl_phdr_info), data);
-        if (last_r != 0) break;
+        try callback(&info, @sizeOf(dl_phdr_info), context);
     }
-
-    return last_r;
 }
 
 pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
lib/std/process.zig
@@ -613,3 +613,59 @@ pub fn getBaseAddress() usize {
         else => @compileError("Unsupported OS"),
     }
 }
+
+/// Caller owns the result value and each inner slice.
+pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0]u8 {
+    switch (builtin.link_mode) {
+        .Static => return &[_][:0]u8{},
+        .Dynamic => {},
+    }
+    const List = std.ArrayList([:0]u8);
+    switch (builtin.os) {
+        .linux,
+        .freebsd,
+        .netbsd,
+        .dragonfly,
+        => {
+            var paths = List.init(allocator);
+            errdefer {
+                const slice = paths.toOwnedSlice();
+                for (slice) |item| {
+                    allocator.free(item);
+                }
+                allocator.free(slice);
+            }
+            try os.dl_iterate_phdr(&paths, error{OutOfMemory}, struct {
+                fn callback(info: *os.dl_phdr_info, size: usize, list: *List) !void {
+                    const name = info.dlpi_name orelse return;
+                    if (name[0] == '/') {
+                        const item = try mem.dupeZ(list.allocator, u8, mem.toSliceConst(u8, name));
+                        errdefer list.allocator.free(item);
+                        try list.append(item);
+                    }
+                }
+            }.callback);
+            return paths.toOwnedSlice();
+        },
+        .macosx, .ios, .watchos, .tvos => {
+            var paths = List.init(allocator);
+            errdefer {
+                const slice = paths.toOwnedSlice();
+                for (slice) |item| {
+                    allocator.free(item);
+                }
+                allocator.free(slice);
+            }
+            const img_count = std.c._dyld_image_count();
+            var i: u32 = 0;
+            while (i < img_count) : (i += 1) {
+                const name = std.c._dyld_get_image_name(i);
+                const item = try mem.dupeZ(allocator, u8, mem.toSliceConst(u8, name));
+                errdefer allocator.free(item);
+                try paths.append(item);
+            }
+            return paths.toOwnedSlice();
+        },
+        else => return error.UnimplementedSelfExeSharedPaths,
+    }
+}
lib/std/target.zig
@@ -1037,6 +1037,13 @@ pub const Target = union(enum) {
         };
     }
 
+    pub fn isAndroid(self: Target) bool {
+        return switch (self.getAbi()) {
+            .android => true,
+            else => false,
+        };
+    }
+
     pub fn isDragonFlyBSD(self: Target) bool {
         return switch (self.getOs()) {
             .dragonfly => true,
@@ -1196,6 +1203,136 @@ pub const Target = union(enum) {
 
         return .unavailable;
     }
+
+    pub const FloatAbi = enum {
+        hard,
+        soft,
+        soft_fp,
+    };
+
+    pub fn getFloatAbi(self: Target) FloatAbi {
+        return switch (self.getAbi()) {
+            .gnueabihf,
+            .eabihf,
+            .musleabihf,
+            => .hard,
+            else => .soft,
+        };
+    }
+
+    /// Caller owns returned memory.
+    pub fn getStandardDynamicLinkerPath(
+        self: Target,
+        allocator: *mem.Allocator,
+    ) error{
+        OutOfMemory,
+        UnknownDynamicLinkerPath,
+    }![:0]u8 {
+        const a = allocator;
+        if (self.isAndroid()) {
+            return mem.dupeZ(a, u8, if (self.getArchPtrBitWidth() == 64)
+                "/system/bin/linker64"
+            else
+                "/system/bin/linker");
+        }
+
+        if (self.isMusl()) {
+            var result = try std.Buffer.init(allocator, "/lib/ld-musl-");
+            defer result.deinit();
+
+            var is_arm = false;
+            switch (self.getArch()) {
+                .arm, .thumb => {
+                    try result.append("arm");
+                    is_arm = true;
+                },
+                .armeb, .thumbeb => {
+                    try result.append("armeb");
+                    is_arm = true;
+                },
+                else => |arch| try result.append(@tagName(arch)),
+            }
+            if (is_arm and self.getFloatAbi() == .hard) {
+                try result.append("hf");
+            }
+            try result.append(".so.1");
+            return result.toOwnedSlice();
+        }
+
+        switch (self.getOs()) {
+            .freebsd => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.1"),
+            .netbsd => return mem.dupeZ(a, u8, "/libexec/ld.elf_so"),
+            .dragonfly => return mem.dupeZ(a, u8, "/libexec/ld-elf.so.2"),
+            .linux => switch (self.getArch()) {
+                .i386,
+                .sparc,
+                .sparcel,
+                => return mem.dupeZ(a, u8, "/lib/ld-linux.so.2"),
+
+                .aarch64 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64.so.1"),
+                .aarch64_be => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_be.so.1"),
+                .aarch64_32 => return mem.dupeZ(a, u8, "/lib/ld-linux-aarch64_32.so.1"),
+
+                .arm,
+                .armeb,
+                .thumb,
+                .thumbeb,
+                => return mem.dupeZ(a, u8, switch (self.getFloatAbi()) {
+                    .hard => "/lib/ld-linux-armhf.so.3",
+                    else => "/lib/ld-linux.so.3",
+                }),
+
+                .mips,
+                .mipsel,
+                .mips64,
+                .mips64el,
+                => return error.UnknownDynamicLinkerPath,
+
+                .powerpc => return mem.dupeZ(a, u8, "/lib/ld.so.1"),
+                .powerpc64, .powerpc64le => return mem.dupeZ(a, u8, "/lib64/ld64.so.2"),
+                .s390x => return mem.dupeZ(a, u8, "/lib64/ld64.so.1"),
+                .sparcv9 => return mem.dupeZ(a, u8, "/lib64/ld-linux.so.2"),
+                .x86_64 => return mem.dupeZ(a, u8, switch (self.getAbi()) {
+                    .gnux32 => "/libx32/ld-linux-x32.so.2",
+                    else => "/lib64/ld-linux-x86-64.so.2",
+                }),
+
+                .riscv32 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv32-ilp32.so.1"),
+                .riscv64 => return mem.dupeZ(a, u8, "/lib/ld-linux-riscv64-lp64.so.1"),
+
+                .arc,
+                .avr,
+                .bpfel,
+                .bpfeb,
+                .hexagon,
+                .msp430,
+                .r600,
+                .amdgcn,
+                .tce,
+                .tcele,
+                .xcore,
+                .nvptx,
+                .nvptx64,
+                .le32,
+                .le64,
+                .amdil,
+                .amdil64,
+                .hsail,
+                .hsail64,
+                .spir,
+                .spir64,
+                .kalimba,
+                .shave,
+                .lanai,
+                .wasm32,
+                .wasm64,
+                .renderscript32,
+                .renderscript64,
+                => return error.UnknownDynamicLinkerPath,
+            },
+            else => return error.UnknownDynamicLinkerPath,
+        }
+    }
 };
 
 test "parseCpuFeatureSet" {
src/codegen.cpp
@@ -8624,7 +8624,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             break;
     }
     buf_appendf(contents, "pub const output_mode = OutputMode.%s;\n", out_type);
-    const char *link_type = g->is_dynamic ? "Dynamic" : "Static";
+    const char *link_type = g->have_dynamic_link ? "Dynamic" : "Static";
     buf_appendf(contents, "pub const link_mode = LinkMode.%s;\n", link_type);
     buf_appendf(contents, "pub const is_test = %s;\n", bool_to_str(g->is_test_build));
     buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded));
@@ -8731,7 +8731,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
     cache_int(&cache_hash, g->build_mode);
     cache_bool(&cache_hash, g->strip_debug_symbols);
     cache_int(&cache_hash, g->out_type);
-    cache_bool(&cache_hash, g->is_dynamic);
+    cache_bool(&cache_hash, detect_dynamic_link(g));
     cache_bool(&cache_hash, g->is_test_build);
     cache_bool(&cache_hash, g->is_single_threaded);
     cache_bool(&cache_hash, g->test_is_evented);
@@ -8957,6 +8957,8 @@ static void init(CodeGen *g) {
 }
 
 static void detect_dynamic_linker(CodeGen *g) {
+    Error err;
+
     if (g->dynamic_linker_path != nullptr)
         return;
     if (!g->have_dynamic_link)
@@ -8964,45 +8966,15 @@ static void detect_dynamic_linker(CodeGen *g) {
     if (g->out_type == OutTypeObj || (g->out_type == OutTypeLib && !g->is_dynamic))
         return;
 
-    const char *standard_ld_path = target_dynamic_linker(g->zig_target);
-    if (standard_ld_path == nullptr)
-        return;
-
-    if (g->zig_target->is_native) {
-        // target_dynamic_linker is usually correct. However on some systems, such as NixOS
-        // it will be incorrect. See if we can do better by looking at what zig's own
-        // dynamic linker path is.
-        g->dynamic_linker_path = get_self_dynamic_linker_path();
-        if (g->dynamic_linker_path != nullptr)
-            return;
-
-        // If Zig is statically linked, such as via distributed binary static builds, the above
-        // trick won't work. What are we left with? Try to run the system C compiler and get
-        // it to tell us the dynamic linker path
-#if defined(ZIG_OS_LINUX)
-        {
-            Error err;
-            for (size_t i = 0; possible_ld_names[i] != NULL; i += 1) {
-                const char *lib_name = possible_ld_names[i];
-                char *result_ptr;
-                size_t result_len;
-                if ((err = stage2_libc_cc_print_file_name(&result_ptr, &result_len, lib_name, false))) {
-                    if (err != ErrorCCompilerCannotFindFile && err != ErrorNoCCompilerInstalled) {
-                        fprintf(stderr, "Unable to detect native dynamic linker: %s\n", err_str(err));
-                        exit(1);
-                    }
-                    continue;
-                }
-                g->dynamic_linker_path = buf_create_from_mem(result_ptr, result_len);
-                // Skips heap::c_allocator because the memory is allocated by stage2 library.
-                free(result_ptr);
-                return;
-            }
-        }
-#endif
+    char *dynamic_linker_ptr;
+    size_t dynamic_linker_len;
+    if ((err = stage2_detect_dynamic_linker(g->zig_target, &dynamic_linker_ptr, &dynamic_linker_len))) {
+        fprintf(stderr, "Unable to detect dynamic linker: %s\n", err_str(err));
+        exit(1);
     }
-
-    g->dynamic_linker_path = buf_create_from_str(standard_ld_path);
+    g->dynamic_linker_path = buf_create_from_mem(dynamic_linker_ptr, dynamic_linker_len);
+    // Skips heap::c_allocator because the memory is allocated by stage2 library.
+    free(dynamic_linker_ptr);
 }
 
 static void detect_libc(CodeGen *g) {
src/compiler.cpp
@@ -4,20 +4,6 @@
 
 #include <stdio.h>
 
-static Buf saved_dynamic_linker_path = BUF_INIT;
-static bool searched_for_dyn_linker = false;
-
-static void detect_dynamic_linker(Buf *lib_path) {
-#if defined(ZIG_OS_LINUX)
-    for (size_t i = 0; possible_ld_names[i] != NULL; i += 1) {
-        if (buf_ends_with_str(lib_path, possible_ld_names[i])) {
-            buf_init_from_buf(&saved_dynamic_linker_path, lib_path);
-            break;
-        }
-    }
-#endif
-}
-
 Buf *get_self_libc_path(void) {
     static Buf saved_libc_path = BUF_INIT;
     static bool searched_for_libc = false;
@@ -43,25 +29,6 @@ Buf *get_self_libc_path(void) {
     }
 }
 
-Buf *get_self_dynamic_linker_path(void) {
-    for (;;) {
-        if (saved_dynamic_linker_path.list.length != 0) {
-            return &saved_dynamic_linker_path;
-        }
-        if (searched_for_dyn_linker)
-            return nullptr;
-        ZigList<Buf *> lib_paths = {};
-        Error err;
-        if ((err = os_self_exe_shared_libs(lib_paths)))
-            return nullptr;
-        for (size_t i = 0; i < lib_paths.length; i += 1) {
-            Buf *lib_path = lib_paths.at(i);
-            detect_dynamic_linker(lib_path);
-        }
-        searched_for_dyn_linker = true;
-    }
-}
-
 Error get_compiler_id(Buf **result) {
     static Buf saved_compiler_id = BUF_INIT;
 
@@ -98,7 +65,6 @@ Error get_compiler_id(Buf **result) {
         return err;
     for (size_t i = 0; i < lib_paths.length; i += 1) {
         Buf *lib_path = lib_paths.at(i);
-        detect_dynamic_linker(lib_path);
         if ((err = cache_add_file(ch, lib_path)))
             return err;
     }
src/compiler.hpp
@@ -12,7 +12,6 @@
 #include "error.hpp"
 
 Error get_compiler_id(Buf **result);
-Buf *get_self_dynamic_linker_path(void);
 Buf *get_self_libc_path(void);
 
 Buf *get_zig_lib_dir(void);
src/error.cpp
@@ -80,6 +80,7 @@ const char *err_str(Error err) {
         case ErrorLibCKernel32LibNotFound: return "kernel32 library not found";
         case ErrorUnsupportedArchitecture: return "unsupported architecture";
         case ErrorWindowsSdkNotFound: return "Windows SDK not found";
+        case ErrorUnknownDynamicLinkerPath: return "Windows SDK not found";
     }
     return "(invalid error)";
 }
src/os.cpp
@@ -2129,21 +2129,3 @@ void os_file_close(OsFile *file) {
     *file = -1;
 #endif
 }
-
-#ifdef ZIG_OS_LINUX
-const char *possible_ld_names[] = {
-#if defined(ZIG_ARCH_X86_64)
-    "ld-linux-x86-64.so.2",
-    "ld-musl-x86_64.so.1",
-#elif defined(ZIG_ARCH_ARM64)
-    "ld-linux-aarch64.so.1",
-    "ld-musl-aarch64.so.1",
-#elif defined(ZIG_ARCH_ARM)
-    "ld-linux-armhf.so.3",
-    "ld-musl-armhf.so.1",
-    "ld-linux.so.3",
-    "ld-musl-arm.so.1",
-#endif
-    NULL,
-};
-#endif
src/os.hpp
@@ -43,10 +43,6 @@
 #define ZIG_ARCH_UNKNOWN
 #endif
 
-#ifdef ZIG_OS_LINUX
-extern const char *possible_ld_names[];
-#endif
-
 #if defined(ZIG_OS_WINDOWS)
 #define ZIG_PRI_usize "I64u"
 #define ZIG_PRI_i64 "I64d"
src/stage2.cpp
@@ -171,9 +171,7 @@ enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc) {
     stage2_panic(msg, strlen(msg));
 }
 
-enum Error stage2_libc_cc_print_file_name(char **out_ptr, size_t *out_len,
-        const char *o_file, bool want_dirname)
-{
-    const char *msg = "stage0 called stage2_libc_cc_print_file_name";
+enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target, char **out_ptr, size_t *out_len) {
+    const char *msg = "stage0 called stage2_detect_dynamic_linker";
     stage2_panic(msg, strlen(msg));
 }
src/stage2.h
@@ -12,6 +12,8 @@
 #include <stdint.h>
 #include <stdio.h>
 
+#include "zig_llvm.h"
+
 #ifdef __cplusplus
 #define ZIG_EXTERN_C extern "C"
 #else
@@ -100,6 +102,7 @@ enum Error {
     ErrorLibCKernel32LibNotFound,
     ErrorUnsupportedArchitecture,
     ErrorWindowsSdkNotFound,
+    ErrorUnknownDynamicLinkerPath,
 };
 
 // ABI warning
@@ -242,8 +245,70 @@ ZIG_EXTERN_C enum Error stage2_libc_parse(struct Stage2LibCInstallation *libc, c
 ZIG_EXTERN_C enum Error stage2_libc_render(struct Stage2LibCInstallation *self, FILE *file);
 // ABI warning
 ZIG_EXTERN_C enum Error stage2_libc_find_native(struct Stage2LibCInstallation *libc);
+
+// ABI warning
+// Synchronize with target.cpp::os_list
+enum Os {
+    OsFreestanding,
+    OsAnanas,
+    OsCloudABI,
+    OsDragonFly,
+    OsFreeBSD,
+    OsFuchsia,
+    OsIOS,
+    OsKFreeBSD,
+    OsLinux,
+    OsLv2,        // PS3
+    OsMacOSX,
+    OsNetBSD,
+    OsOpenBSD,
+    OsSolaris,
+    OsWindows,
+    OsHaiku,
+    OsMinix,
+    OsRTEMS,
+    OsNaCl,       // Native Client
+    OsCNK,        // BG/P Compute-Node Kernel
+    OsAIX,
+    OsCUDA,       // NVIDIA CUDA
+    OsNVCL,       // NVIDIA OpenCL
+    OsAMDHSA,     // AMD HSA Runtime
+    OsPS4,
+    OsELFIAMCU,
+    OsTvOS,       // Apple tvOS
+    OsWatchOS,    // Apple watchOS
+    OsMesa3D,
+    OsContiki,
+    OsAMDPAL,
+    OsHermitCore,
+    OsHurd,
+    OsWASI,
+    OsEmscripten,
+    OsUefi,
+    OsOther,
+};
+
+// ABI warning
+struct ZigGLibCVersion {
+    uint32_t major; // always 2
+    uint32_t minor;
+    uint32_t patch;
+};
+
+// ABI warning
+struct ZigTarget {
+    enum ZigLLVM_ArchType arch;
+    enum ZigLLVM_SubArchType sub_arch;
+    enum ZigLLVM_VendorType vendor;
+    Os os;
+    enum ZigLLVM_EnvironmentType abi;
+    struct ZigGLibCVersion *glibc_version; // null means default
+    struct Stage2CpuFeatures *cpu_features;
+    bool is_native;
+};
+
 // ABI warning
-ZIG_EXTERN_C enum Error stage2_libc_cc_print_file_name(char **out_ptr, size_t *out_len,
-        const char *o_file, bool want_dirname);
+ZIG_EXTERN_C enum Error stage2_detect_dynamic_linker(const struct ZigTarget *target,
+        char **out_ptr, size_t *out_len);
 
 #endif
src/target.cpp
@@ -1204,213 +1204,10 @@ const char *target_lib_file_ext(const ZigTarget *target, bool is_static,
     }
 }
 
-enum FloatAbi {
-    FloatAbiHard,
-    FloatAbiSoft,
-    FloatAbiSoftFp,
-};
-
-static FloatAbi get_float_abi(const ZigTarget *target) {
-    const ZigLLVM_EnvironmentType env = target->abi;
-    if (env == ZigLLVM_GNUEABIHF ||
-        env == ZigLLVM_EABIHF ||
-        env == ZigLLVM_MuslEABIHF)
-    {
-        return FloatAbiHard;
-    } else {
-        return FloatAbiSoft;
-    }
-}
-
-static bool is_64_bit(ZigLLVM_ArchType arch) {
-    return target_arch_pointer_bit_width(arch) == 64;
-}
-
 bool target_is_android(const ZigTarget *target) {
     return target->abi == ZigLLVM_Android;
 }
 
-const char *target_dynamic_linker(const ZigTarget *target) {
-    if (target_is_android(target)) {
-        return is_64_bit(target->arch) ? "/system/bin/linker64" : "/system/bin/linker";
-    }
-
-    if (target_is_musl(target)) {
-        Buf buf = BUF_INIT;
-        buf_init_from_str(&buf, "/lib/ld-musl-");
-        bool is_arm = false;
-        switch (target->arch) {
-            case ZigLLVM_arm:
-            case ZigLLVM_thumb:
-                buf_append_str(&buf, "arm");
-                is_arm = true;
-                break;
-            case ZigLLVM_armeb:
-            case ZigLLVM_thumbeb:
-                buf_append_str(&buf, "armeb");
-                is_arm = true;
-                break;
-            default:
-                buf_append_str(&buf, target_arch_name(target->arch));
-        }
-        if (is_arm && get_float_abi(target) == FloatAbiHard) {
-            buf_append_str(&buf, "hf");
-        }
-        buf_append_str(&buf, ".so.1");
-        return buf_ptr(&buf);
-    }
-
-    switch (target->os) {
-        case OsFreeBSD:
-            return "/libexec/ld-elf.so.1";
-        case OsNetBSD:
-            return "/libexec/ld.elf_so";
-        case OsDragonFly:
-            return "/libexec/ld-elf.so.2";
-        case OsLinux: {
-            const ZigLLVM_EnvironmentType abi = target->abi;
-            switch (target->arch) {
-                case ZigLLVM_UnknownArch:
-                    zig_unreachable();
-                case ZigLLVM_x86:
-                case ZigLLVM_sparc:
-                case ZigLLVM_sparcel:
-                    return "/lib/ld-linux.so.2";
-
-                case ZigLLVM_aarch64:
-                    return "/lib/ld-linux-aarch64.so.1";
-
-                case ZigLLVM_aarch64_be:
-                    return "/lib/ld-linux-aarch64_be.so.1";
-
-                case ZigLLVM_aarch64_32:
-                    return "/lib/ld-linux-aarch64_32.so.1";
-
-                case ZigLLVM_arm:
-                case ZigLLVM_thumb:
-                    if (get_float_abi(target) == FloatAbiHard) {
-                        return "/lib/ld-linux-armhf.so.3";
-                    } else {
-                        return "/lib/ld-linux.so.3";
-                    }
-
-                case ZigLLVM_armeb:
-                case ZigLLVM_thumbeb:
-                    if (get_float_abi(target) == FloatAbiHard) {
-                        return "/lib/ld-linux-armhf.so.3";
-                    } else {
-                        return "/lib/ld-linux.so.3";
-                    }
-
-                case ZigLLVM_mips:
-                case ZigLLVM_mipsel:
-                case ZigLLVM_mips64:
-                case ZigLLVM_mips64el:
-                    zig_panic("TODO implement target_dynamic_linker for mips");
-
-                case ZigLLVM_ppc:
-                    return "/lib/ld.so.1";
-
-                case ZigLLVM_ppc64:
-                    return "/lib64/ld64.so.2";
-
-                case ZigLLVM_ppc64le:
-                    return "/lib64/ld64.so.2";
-
-                case ZigLLVM_systemz:
-                    return "/lib64/ld64.so.1";
-
-                case ZigLLVM_sparcv9:
-                    return "/lib64/ld-linux.so.2";
-
-                case ZigLLVM_x86_64:
-                    if (abi == ZigLLVM_GNUX32) {
-                        return "/libx32/ld-linux-x32.so.2";
-                    }
-                    if (abi == ZigLLVM_Musl || abi == ZigLLVM_MuslEABI || abi == ZigLLVM_MuslEABIHF) {
-                        return "/lib/ld-musl-x86_64.so.1";
-                    }
-                    return "/lib64/ld-linux-x86-64.so.2";
-
-                case ZigLLVM_wasm32:
-                case ZigLLVM_wasm64:
-                    return nullptr;
-
-                case ZigLLVM_riscv32:
-                    return "/lib/ld-linux-riscv32-ilp32.so.1";
-                case ZigLLVM_riscv64:
-                    return "/lib/ld-linux-riscv64-lp64.so.1";
-
-                case ZigLLVM_arc:
-                case ZigLLVM_avr:
-                case ZigLLVM_bpfel:
-                case ZigLLVM_bpfeb:
-                case ZigLLVM_hexagon:
-                case ZigLLVM_msp430:
-                case ZigLLVM_r600:
-                case ZigLLVM_amdgcn:
-                case ZigLLVM_tce:
-                case ZigLLVM_tcele:
-                case ZigLLVM_xcore:
-                case ZigLLVM_nvptx:
-                case ZigLLVM_nvptx64:
-                case ZigLLVM_le32:
-                case ZigLLVM_le64:
-                case ZigLLVM_amdil:
-                case ZigLLVM_amdil64:
-                case ZigLLVM_hsail:
-                case ZigLLVM_hsail64:
-                case ZigLLVM_spir:
-                case ZigLLVM_spir64:
-                case ZigLLVM_kalimba:
-                case ZigLLVM_shave:
-                case ZigLLVM_lanai:
-                case ZigLLVM_renderscript32:
-                case ZigLLVM_renderscript64:
-                    zig_panic("TODO implement target_dynamic_linker for this arch");
-            }
-            zig_unreachable();
-        }
-        case OsFreestanding:
-        case OsIOS:
-        case OsTvOS:
-        case OsWatchOS:
-        case OsMacOSX:
-        case OsUefi:
-        case OsWindows:
-        case OsEmscripten:
-        case OsOther:
-            return nullptr;
-
-        case OsAnanas:
-        case OsCloudABI:
-        case OsFuchsia:
-        case OsKFreeBSD:
-        case OsLv2:
-        case OsOpenBSD:
-        case OsSolaris:
-        case OsHaiku:
-        case OsMinix:
-        case OsRTEMS:
-        case OsNaCl:
-        case OsCNK:
-        case OsAIX:
-        case OsCUDA:
-        case OsNVCL:
-        case OsAMDHSA:
-        case OsPS4:
-        case OsELFIAMCU:
-        case OsMesa3D:
-        case OsContiki:
-        case OsAMDPAL:
-        case OsHermitCore:
-        case OsHurd:
-        case OsWASI:
-            zig_panic("TODO implement target_dynamic_linker for this OS");
-    }
-    zig_unreachable();
-}
-
 bool target_can_exec(const ZigTarget *host_target, const ZigTarget *guest_target) {
     assert(host_target != nullptr);
 
src/target.hpp
@@ -8,51 +8,10 @@
 #ifndef ZIG_TARGET_HPP
 #define ZIG_TARGET_HPP
 
-#include <zig_llvm.h>
+#include "stage2.h"
 
 struct Buf;
 
-// Synchronize with target.cpp::os_list
-enum Os {
-    OsFreestanding,
-    OsAnanas,
-    OsCloudABI,
-    OsDragonFly,
-    OsFreeBSD,
-    OsFuchsia,
-    OsIOS,
-    OsKFreeBSD,
-    OsLinux,
-    OsLv2,        // PS3
-    OsMacOSX,
-    OsNetBSD,
-    OsOpenBSD,
-    OsSolaris,
-    OsWindows,
-    OsHaiku,
-    OsMinix,
-    OsRTEMS,
-    OsNaCl,       // Native Client
-    OsCNK,        // BG/P Compute-Node Kernel
-    OsAIX,
-    OsCUDA,       // NVIDIA CUDA
-    OsNVCL,       // NVIDIA OpenCL
-    OsAMDHSA,     // AMD HSA Runtime
-    OsPS4,
-    OsELFIAMCU,
-    OsTvOS,       // Apple tvOS
-    OsWatchOS,    // Apple watchOS
-    OsMesa3D,
-    OsContiki,
-    OsAMDPAL,
-    OsHermitCore,
-    OsHurd,
-    OsWASI,
-    OsEmscripten,
-    OsUefi,
-    OsOther,
-};
-
 // Synchronize with target.cpp::subarch_list_list
 enum SubArchList {
     SubArchListNone,
@@ -78,23 +37,6 @@ enum TargetSubsystem {
     TargetSubsystemAuto
 };
 
-struct ZigGLibCVersion {
-    uint32_t major; // always 2
-    uint32_t minor;
-    uint32_t patch;
-};
-
-struct ZigTarget {
-    ZigLLVM_ArchType arch;
-    ZigLLVM_SubArchType sub_arch;
-    ZigLLVM_VendorType vendor;
-    Os os;
-    ZigLLVM_EnvironmentType abi;
-    ZigGLibCVersion *glibc_version; // null means default
-    Stage2CpuFeatures *cpu_features;
-    bool is_native;
-};
-
 enum CIntType {
     CIntTypeShort,
     CIntTypeUShort,
@@ -168,8 +110,6 @@ const char *target_lib_file_prefix(const ZigTarget *target);
 const char *target_lib_file_ext(const ZigTarget *target, bool is_static,
         size_t version_major, size_t version_minor, size_t version_patch);
 
-const char *target_dynamic_linker(const ZigTarget *target);
-
 bool target_can_exec(const ZigTarget *host_target, const ZigTarget *guest_target);
 ZigLLVM_OSType get_llvm_os_type(Os os_type);
 
src-self-hosted/introspect.zig
@@ -6,6 +6,14 @@ const fs = std.fs;
 
 const warn = std.debug.warn;
 
+pub fn detectDynamicLinker(allocator: *mem.Allocator, target: std.Target) ![:0]u8 {
+    if (target == .Native) {
+        return @import("libc_installation.zig").detectNativeDynamicLinker(allocator);
+    } else {
+        return target.getStandardDynamicLinkerPath(allocator);
+    }
+}
+
 /// Caller must free result
 pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 {
     const test_zig_dir = try fs.path.join(allocator, &[_][]const u8{ test_path, "lib", "zig" });
src-self-hosted/libc_installation.zig
@@ -492,7 +492,7 @@ pub const LibCInstallation = struct {
 const default_cc_exe = if (is_windows) "cc.exe" else "cc";
 
 /// caller owns returned memory
-pub fn ccPrintFileName(
+fn ccPrintFileName(
     allocator: *Allocator,
     o_file: []const u8,
     want_dirname: enum { full_path, only_dir },
@@ -535,6 +535,43 @@ pub fn ccPrintFileName(
     }
 }
 
+/// Caller owns returned memory.
+pub fn detectNativeDynamicLinker(allocator: *Allocator) ![:0]u8 {
+    const standard_ld_path = try std.Target.current.getStandardDynamicLinkerPath(allocator);
+    var standard_ld_path_resource: ?[:0]u8 = standard_ld_path; // Set to null to avoid freeing it.
+    defer if (standard_ld_path_resource) |s| allocator.free(s);
+
+    const standard_ld_basename = fs.path.basename(standard_ld_path);
+
+    {
+        // Best case scenario: the current executable is dynamically linked, and we can iterate
+        // over our own shared objects and find a dynamic linker.
+        const lib_paths = try std.process.getSelfExeSharedLibPaths(allocator);
+        defer allocator.free(lib_paths);
+
+        for (lib_paths) |lib_path| {
+            if (std.mem.endsWith(u8, lib_path, standard_ld_basename)) {
+                return std.mem.dupeZ(allocator, u8, lib_path);
+            }
+        }
+    }
+
+    // If Zig is statically linked, such as via distributed binary static builds, the above
+    // trick won't work. What are we left with? Try to run the system C compiler and get
+    // it to tell us the dynamic linker path.
+    return ccPrintFileName(allocator, standard_ld_basename, .full_path) catch |err| switch (err) {
+        error.OutOfMemory => return error.OutOfMemory,
+        error.LibCRuntimeNotFound,
+        error.CCompilerExitCode,
+        error.CCompilerCrashed,
+        error.UnableToSpawnCCompiler,
+        => {
+            standard_ld_path_resource = null; // Prevent freeing standard_ld_path.
+            return standard_ld_path;
+        },
+    };
+}
+
 const Search = struct {
     path: []const u8,
     version: []const u8,
src-self-hosted/stage2.zig
@@ -109,6 +109,7 @@ const Error = extern enum {
     LibCKernel32LibNotFound,
     UnsupportedArchitecture,
     WindowsSdkNotFound,
+    UnknownDynamicLinkerPath,
 };
 
 const FILE = std.c.FILE;
@@ -1012,24 +1013,100 @@ export fn stage2_libc_render(stage1_libc: *Stage2LibCInstallation, output_file:
 }
 
 // ABI warning
-export fn stage2_libc_cc_print_file_name(
-    out_ptr: *[*:0]u8,
-    out_len: *usize,
-    o_file: [*:0]const u8,
-    want_dirname: bool,
-) Error {
-    const result = @import("libc_installation.zig").ccPrintFileName(
+const Stage2Target = extern struct {
+    arch: c_int,
+    sub_arch: c_int,
+    vendor: c_int,
+    os: c_int,
+    abi: c_int,
+    glibc_version: ?*Stage2GLibCVersion, // null means default
+    cpu_features: *Stage2CpuFeatures,
+    is_native: bool,
+};
+
+// ABI warning
+const Stage2GLibCVersion = extern struct {
+    major: u32,
+    minor: u32,
+    patch: u32,
+};
+
+// ABI warning
+export fn stage2_detect_dynamic_linker(in_target: *const Stage2Target, out_ptr: *[*:0]u8, out_len: *usize) Error {
+    const target: Target = if (in_target.is_native) .Native else .{
+        .Cross = .{
+            .arch = switch (enumInt(@TagType(Target.Arch), in_target.arch)) {
+                .arm => .{ .arm = enumInt(Target.Arch.Arm32, in_target.sub_arch) },
+                .armeb => .{ .armeb = enumInt(Target.Arch.Arm32, in_target.sub_arch) },
+                .thumb => .{ .thumb = enumInt(Target.Arch.Arm32, in_target.sub_arch) },
+                .thumbeb => .{ .thumbeb = enumInt(Target.Arch.Arm32, in_target.sub_arch) },
+
+                .aarch64 => .{ .aarch64 = enumInt(Target.Arch.Arm64, in_target.sub_arch) },
+                .aarch64_be => .{ .aarch64_be = enumInt(Target.Arch.Arm64, in_target.sub_arch) },
+                .aarch64_32 => .{ .aarch64_32 = enumInt(Target.Arch.Arm64, in_target.sub_arch) },
+
+                .kalimba => .{ .kalimba = enumInt(Target.Arch.Kalimba, in_target.sub_arch) },
+
+                .arc => .arc,
+                .avr => .avr,
+                .bpfel => .bpfel,
+                .bpfeb => .bpfeb,
+                .hexagon => .hexagon,
+                .mips => .mips,
+                .mipsel => .mipsel,
+                .mips64 => .mips64,
+                .mips64el => .mips64el,
+                .msp430 => .msp430,
+                .powerpc => .powerpc,
+                .powerpc64 => .powerpc64,
+                .powerpc64le => .powerpc64le,
+                .r600 => .r600,
+                .amdgcn => .amdgcn,
+                .riscv32 => .riscv32,
+                .riscv64 => .riscv64,
+                .sparc => .sparc,
+                .sparcv9 => .sparcv9,
+                .sparcel => .sparcel,
+                .s390x => .s390x,
+                .tce => .tce,
+                .tcele => .tcele,
+                .i386 => .i386,
+                .x86_64 => .x86_64,
+                .xcore => .xcore,
+                .nvptx => .nvptx,
+                .nvptx64 => .nvptx64,
+                .le32 => .le32,
+                .le64 => .le64,
+                .amdil => .amdil,
+                .amdil64 => .amdil64,
+                .hsail => .hsail,
+                .hsail64 => .hsail64,
+                .spir => .spir,
+                .spir64 => .spir64,
+                .shave => .shave,
+                .lanai => .lanai,
+                .wasm32 => .wasm32,
+                .wasm64 => .wasm64,
+                .renderscript32 => .renderscript32,
+                .renderscript64 => .renderscript64,
+            },
+            .os = enumInt(Target.Os, in_target.os),
+            .abi = enumInt(Target.Abi, in_target.abi),
+            .cpu_features = in_target.cpu_features.cpu_features,
+        },
+    };
+    const result = @import("introspect.zig").detectDynamicLinker(
         std.heap.c_allocator,
-        mem.toSliceConst(u8, o_file),
-        if (want_dirname) .only_dir else .full_path,
+        target,
     ) catch |err| switch (err) {
         error.OutOfMemory => return .OutOfMemory,
-        error.LibCRuntimeNotFound => return .FileNotFound,
-        error.CCompilerExitCode => return .CCompilerExitCode,
-        error.CCompilerCrashed => return .CCompilerCrashed,
-        error.UnableToSpawnCCompiler => return .UnableToSpawnCCompiler,
+        error.UnknownDynamicLinkerPath => return .UnknownDynamicLinkerPath,
     };
     out_ptr.* = result.ptr;
     out_len.* = result.len;
     return .None;
 }
+
+fn enumInt(comptime Enum: type, int: c_int) Enum {
+    return @intToEnum(Enum, @intCast(@TagType(Enum), int));
+}
src-self-hosted/util.zig
@@ -2,143 +2,6 @@ const std = @import("std");
 const Target = std.Target;
 const llvm = @import("llvm.zig");
 
-pub const FloatAbi = enum {
-    Hard,
-    Soft,
-    SoftFp,
-};
-
-/// TODO expose the arch and subarch separately
-pub fn isArmOrThumb(self: Target) bool {
-    return switch (self.getArch()) {
-        .arm,
-        .armeb,
-        .aarch64,
-        .aarch64_be,
-        .thumb,
-        .thumbeb,
-        => true,
-        else => false,
-    };
-}
-
-pub fn getFloatAbi(self: Target) FloatAbi {
-    return switch (self.getAbi()) {
-        .gnueabihf,
-        .eabihf,
-        .musleabihf,
-        => .Hard,
-        else => .Soft,
-    };
-}
-
-pub fn getDynamicLinkerPath(self: Target) ?[]const u8 {
-    const env = self.getAbi();
-    const arch = self.getArch();
-    const os = self.getOs();
-    switch (os) {
-        .freebsd => {
-            return "/libexec/ld-elf.so.1";
-        },
-        .linux => {
-            switch (env) {
-                .android => {
-                    if (self.getArchPtrBitWidth() == 64) {
-                        return "/system/bin/linker64";
-                    } else {
-                        return "/system/bin/linker";
-                    }
-                },
-                .gnux32 => {
-                    if (arch == .x86_64) {
-                        return "/libx32/ld-linux-x32.so.2";
-                    }
-                },
-                .musl,
-                .musleabi,
-                .musleabihf,
-                => {
-                    if (arch == .x86_64) {
-                        return "/lib/ld-musl-x86_64.so.1";
-                    }
-                },
-                else => {},
-            }
-            switch (arch) {
-                .i386,
-                .sparc,
-                .sparcel,
-                => return "/lib/ld-linux.so.2",
-
-                .aarch64 => return "/lib/ld-linux-aarch64.so.1",
-
-                .aarch64_be => return "/lib/ld-linux-aarch64_be.so.1",
-
-                .arm,
-                .thumb,
-                => return switch (getFloatAbi(self)) {
-                    .Hard => return "/lib/ld-linux-armhf.so.3",
-                    else => return "/lib/ld-linux.so.3",
-                },
-
-                .armeb,
-                .thumbeb,
-                => return switch (getFloatAbi(self)) {
-                    .Hard => return "/lib/ld-linux-armhf.so.3",
-                    else => return "/lib/ld-linux.so.3",
-                },
-
-                .mips,
-                .mipsel,
-                .mips64,
-                .mips64el,
-                => return null,
-
-                .powerpc => return "/lib/ld.so.1",
-                .powerpc64 => return "/lib64/ld64.so.2",
-                .powerpc64le => return "/lib64/ld64.so.2",
-                .s390x => return "/lib64/ld64.so.1",
-                .sparcv9 => return "/lib64/ld-linux.so.2",
-                .x86_64 => return "/lib64/ld-linux-x86-64.so.2",
-
-                .arc,
-                .avr,
-                .bpfel,
-                .bpfeb,
-                .hexagon,
-                .msp430,
-                .r600,
-                .amdgcn,
-                .riscv32,
-                .riscv64,
-                .tce,
-                .tcele,
-                .xcore,
-                .nvptx,
-                .nvptx64,
-                .le32,
-                .le64,
-                .amdil,
-                .amdil64,
-                .hsail,
-                .hsail64,
-                .spir,
-                .spir64,
-                .kalimba,
-                .shave,
-                .lanai,
-                .wasm32,
-                .wasm64,
-                .renderscript32,
-                .renderscript64,
-                .aarch64_32,
-                => return null,
-            }
-        },
-        else => return null,
-    }
-}
-
 pub fn getDarwinArchString(self: Target) [:0]const u8 {
     const arch = self.getArch();
     switch (arch) {