Commit e05ecbf165

Andrew Kelley <andrew@ziglang.org>
2020-09-10 07:24:17
stage2: progress towards LLD linking
* add `zig libc` command * add `--libc` CLI and integrate it with Module and linker code * implement libc detection and paths resolution * port LLD ELF linker line construction to stage2 * integrate dynamic linker option into Module and linker code * implement default link_mode detection and error handling if user requests static when it cannot be fulfilled * integrate more linker options * implement detection of .so.X.Y.Z file extension as a shared object file. nice try, you can't fool me. * correct usage text for -dynamic and -static
1 parent 5746a86
src-self-hosted/link/Elf.zig
@@ -18,6 +18,7 @@ const link = @import("../link.zig");
 const File = link.File;
 const Elf = @This();
 const build_options = @import("build_options");
+const target_util = @import("../target.zig");
 
 const default_entry_addr = 0x8000000;
 
@@ -709,12 +710,7 @@ pub const abbrev_parameter = 6;
 
 pub fn flush(self: *Elf, module: *Module) !void {
     if (build_options.have_llvm and self.base.options.use_lld) {
-        // If there is no Zig code to compile, then we should skip flushing the output file because it
-        // will not be part of the linker line anyway.
-        if (module.root_pkg != null) {
-            try self.flushInner(module);
-        }
-        std.debug.print("TODO create an LLD command line and invoke it\n", .{});
+        return self.linkWithLLD(module);
     } else {
         switch (self.base.options.effectiveOutputMode()) {
             .Exe, .Obj => {},
@@ -1202,6 +1198,275 @@ fn flushInner(self: *Elf, module: *Module) !void {
     assert(!self.debug_strtab_dirty);
 }
 
+fn linkWithLLD(self: *Elf, module: *Module) !void {
+    // If there is no Zig code to compile, then we should skip flushing the output file because it
+    // will not be part of the linker line anyway.
+    if (module.root_pkg != null) {
+        try self.flushInner(module);
+    }
+    var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator);
+    defer arena_allocator.deinit();
+    const arena = &arena_allocator.allocator;
+
+    const target = self.base.options.target;
+    const is_obj = self.base.options.output_mode == .Obj;
+
+    // Create an LLD command line and invoke it.
+    var argv = std.ArrayList([]const u8).init(self.base.allocator);
+    defer argv.deinit();
+    // Even though we're calling LLD as a library it thinks the first argument is its own exe name.
+    try argv.append("lld");
+    if (is_obj) {
+        try argv.append("-r");
+    }
+    if (self.base.options.output_mode == .Lib and
+        self.base.options.link_mode == .Static and
+        !target.isWasm())
+    {
+        // TODO port the code from link.cpp
+        return error.TODOMakeArchive;
+    }
+    const link_in_crt = self.base.options.link_libc and self.base.options.output_mode == .Exe;
+
+    try argv.append("-error-limit=0");
+
+    if (self.base.options.output_mode == .Exe) {
+        try argv.append("-z");
+        const stack_size = self.base.options.stack_size_override orelse 16777216;
+        const arg = try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size});
+        try argv.append(arg);
+    }
+
+    if (self.base.options.linker_script) |linker_script| {
+        try argv.append("-T");
+        try argv.append(linker_script);
+    }
+
+    const gc_sections = self.base.options.gc_sections orelse !is_obj;
+    if (gc_sections) {
+        try argv.append("--gc-sections");
+    }
+
+    if (self.base.options.eh_frame_hdr) {
+        try argv.append("--eh-frame-hdr");
+    }
+
+    if (self.base.options.rdynamic) {
+        try argv.append("--export-dynamic");
+    }
+
+    try argv.appendSlice(self.base.options.extra_lld_args);
+
+    if (self.base.options.z_nodelete) {
+        try argv.append("-z");
+        try argv.append("nodelete");
+    }
+    if (self.base.options.z_defs) {
+        try argv.append("-z");
+        try argv.append("defs");
+    }
+
+    if (getLDMOption(target)) |ldm| {
+        // Any target ELF will use the freebsd osabi if suffixed with "_fbsd".
+        const arg = if (target.os.tag == .freebsd)
+            try std.fmt.allocPrint(arena, "{}_fbsd", .{ldm})
+        else
+            ldm;
+        try argv.append("-m");
+        try argv.append(arg);
+    }
+
+    const is_lib = self.base.options.output_mode == .Lib;
+    const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
+    if (self.base.options.link_mode == .Static) {
+        if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) {
+            try argv.append("-Bstatic");
+        } else {
+            try argv.append("-static");
+        }
+    } else if (is_dyn_lib) {
+        try argv.append("-shared");
+    }
+
+    if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) {
+        try argv.append("-pie");
+    }
+
+    const full_out_path = if (self.base.options.dir_path) |dir_path|
+        try std.fs.path.join(arena, &[_][]const u8{dir_path, self.base.options.sub_path})
+    else 
+        self.base.options.sub_path;
+    try argv.append("-o");
+    try argv.append(full_out_path);
+
+    if (link_in_crt) {
+        const crt1o: []const u8 = o: {
+            if (target.os.tag == .netbsd) {
+                break :o "crt0.o";
+            } else if (target.isAndroid()) {
+                if (self.base.options.link_mode == .Dynamic) {
+                    break :o "crtbegin_dynamic.o";
+                } else {
+                    break :o "crtbegin_static.o";
+                }
+            } else if (self.base.options.link_mode == .Static) {
+                break :o "crt1.o";
+            } else {
+                break :o "Scrt1.o";
+            }
+        };
+        try argv.append(try module.get_libc_crt_file(arena, crt1o));
+        if (target_util.libc_needs_crti_crtn(target)) {
+            try argv.append(try module.get_libc_crt_file(arena, "crti.o"));
+        }
+    }
+
+    // TODO rpaths
+    //for (size_t i = 0; i < g->rpath_list.length; i += 1) {
+    //    Buf *rpath = g->rpath_list.at(i);
+    //    add_rpath(lj, rpath);
+    //}
+    //if (g->each_lib_rpath) {
+    //    for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
+    //        const char *lib_dir = g->lib_dirs.at(i);
+    //        for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
+    //            LinkLib *link_lib = g->link_libs_list.at(i);
+    //            if (buf_eql_str(link_lib->name, "c")) {
+    //                continue;
+    //            }
+    //            bool does_exist;
+    //            Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name));
+    //            if (os_file_exists(test_path, &does_exist) != ErrorNone) {
+    //                zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path));
+    //            }
+    //            if (does_exist) {
+    //                add_rpath(lj, buf_create_from_str(lib_dir));
+    //                break;
+    //            }
+    //        }
+    //    }
+    //}
+
+    for (self.base.options.lib_dirs) |lib_dir| {
+        try argv.append("-L");
+        try argv.append(lib_dir);
+    }
+
+    if (self.base.options.link_libc) {
+        if (self.base.options.libc_installation) |libc_installation| {
+            try argv.append("-L");
+            try argv.append(libc_installation.crt_dir.?);
+        }
+
+        if (self.base.options.link_mode == .Dynamic and (is_dyn_lib or self.base.options.output_mode == .Exe)) {
+            if (self.base.options.dynamic_linker) |dynamic_linker| {
+                try argv.append("-dynamic-linker");
+                try argv.append(dynamic_linker);
+            }
+        }
+    }
+
+    if (is_dyn_lib) {
+        const soname = self.base.options.override_soname orelse
+            try std.fmt.allocPrint(arena, "lib{}.so.{}", .{self.base.options.root_name,
+                self.base.options.version.major,});
+        try argv.append("-soname");
+        try argv.append(soname);
+
+        if (self.base.options.version_script) |version_script| {
+            try argv.append("-version-script");
+            try argv.append(version_script);
+        }
+    }
+
+    // Positional arguments to the linker such as object files.
+    try argv.appendSlice(self.base.options.objects);
+
+    // TODO compiler-rt and libc
+    //if (!g->is_dummy_so && (g->out_type == OutTypeExe || is_dyn_lib)) {
+    //    if (g->libc_link_lib == nullptr) {
+    //        Buf *libc_a_path = build_c(g, OutTypeLib, lj->build_dep_prog_node);
+    //        try argv.append(buf_ptr(libc_a_path));
+    //    }
+
+    //    Buf *compiler_rt_o_path = build_compiler_rt(g, OutTypeLib, lj->build_dep_prog_node);
+    //    try argv.append(buf_ptr(compiler_rt_o_path));
+    //}
+
+    // Shared libraries.
+    try argv.ensureCapacity(argv.items.len + self.base.options.system_libs.len);
+    for (self.base.options.system_libs) |link_lib| {
+        // By this time, we depend on these libs being dynamically linked libraries and not static libraries
+        // (the check for that needs to be earlier), but they could be full paths to .so files, in which
+        // case we want to avoid prepending "-l".
+        const ext = Module.classifyFileExt(link_lib);
+        const arg = if (ext == .so) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib});
+        argv.appendAssumeCapacity(arg);
+    }
+
+    if (!is_obj) {
+        // libc++ dep
+        if (self.base.options.link_libcpp) {
+            try argv.append(module.libcxxabi_static_lib.?);
+            try argv.append(module.libcxx_static_lib.?);
+        }
+
+        // libc dep
+        if (self.base.options.link_libc) {
+            if (self.base.options.libc_installation != null) {
+                if (self.base.options.link_mode == .Static) {
+                    try argv.append("--start-group");
+                    try argv.append("-lc");
+                    try argv.append("-lm");
+                    try argv.append("--end-group");
+                } else {
+                    try argv.append("-lc");
+                    try argv.append("-lm");
+                }
+
+                if (target.os.tag == .freebsd or target.os.tag == .netbsd) {
+                    try argv.append("-lpthread");
+                }
+            } else if (target.isGnuLibC()) {
+                try argv.append(module.libunwind_static_lib.?);
+                // TODO here we need to iterate over the glibc libs and add the .so files to the linker line.
+                std.log.warn("TODO port add_glibc_libs to stage2", .{});
+                try argv.append(try module.get_libc_crt_file(arena, "libc_nonshared.a"));
+            } else if (target.isMusl()) {
+                try argv.append(module.libunwind_static_lib.?);
+                try argv.append(module.libc_static_lib.?);
+            } else if (self.base.options.link_libcpp) {
+                try argv.append(module.libunwind_static_lib.?);
+            } else {
+                unreachable; // Compiler was supposed to emit an error for not being able to provide libc.
+            }
+        }
+    }
+
+    // crt end
+    if (link_in_crt) {
+        if (target.isAndroid()) {
+            try argv.append(try module.get_libc_crt_file(arena, "crtend_android.o"));
+        } else if (target_util.libc_needs_crti_crtn(target)) {
+            try argv.append(try module.get_libc_crt_file(arena, "crtn.o"));
+        }
+    }
+
+    const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
+    if (allow_shlib_undefined) {
+        try argv.append("--allow-shlib-undefined");
+    }
+
+    if (self.base.options.bind_global_refs_locally) {
+        try argv.append("-Bsymbolic");
+    }
+
+    for (argv.items) |arg| {
+        std.debug.print("{} ", .{arg});
+    }
+    @panic("invoke LLD");
+}
+
 fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void {
     const target_endian = self.base.options.target.cpu.arch.endian();
     switch (self.ptr_width) {
@@ -2616,3 +2881,36 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
         .sh_entsize = @intCast(u32, shdr.sh_entsize),
     };
 }
+
+fn getLDMOption(target: std.Target) ?[]const u8 {
+    switch (target.cpu.arch) {
+        .i386 => return "elf_i386",
+        .aarch64 => return "aarch64linux",
+        .aarch64_be => return "aarch64_be_linux",
+        .arm, .thumb => return "armelf_linux_eabi",
+        .armeb, .thumbeb => return "armebelf_linux_eabi",
+        .powerpc => return "elf32ppclinux",
+        .powerpc64 => return "elf64ppc",
+        .powerpc64le => return "elf64lppc",
+        .sparc, .sparcel => return "elf32_sparc",
+        .sparcv9 => return "elf64_sparc",
+        .mips => return "elf32btsmip",
+        .mipsel => return "elf32ltsmip",
+        .mips64 => return "elf64btsmip",
+        .mips64el => return "elf64ltsmip",
+        .s390x => return "elf64_s390",
+        .x86_64 => {
+            if (target.abi == .gnux32) {
+                return "elf32_x86_64";
+            }
+            // Any target elf will use the freebsd osabi if suffixed with "_fbsd".
+            if (target.os.tag == .freebsd) {
+                return "elf_x86_64_fbsd";
+            }
+            return "elf_x86_64";
+        },
+        .riscv32 => return "elf32lriscv",
+        .riscv64 => return "elf64lriscv",
+        else => return null,
+    }
+}
src-self-hosted/libc_installation.zig
@@ -11,6 +11,8 @@ const is_gnu = Target.current.isGnu();
 
 usingnamespace @import("windows_sdk.zig");
 
+// TODO Rework this abstraction to use std.log instead of taking a stderr stream.
+
 /// See the render function implementation for documentation of the fields.
 pub const LibCInstallation = struct {
     include_dir: ?[]const u8 = null,
src-self-hosted/link.zig
@@ -6,6 +6,7 @@ const trace = @import("tracy.zig").trace;
 const Package = @import("Package.zig");
 const Type = @import("type.zig").Type;
 const build_options = @import("build_options");
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 
@@ -23,6 +24,7 @@ pub const Options = struct {
     optimize_mode: std.builtin.Mode,
     root_name: []const u8,
     root_pkg: ?*const Package,
+    dynamic_linker: ?[]const u8 = null,
     /// Used for calculating how much space to reserve for symbols in case the binary file
     /// does not already have a symbol table.
     symbol_count_hint: u64 = 32,
@@ -30,6 +32,7 @@ pub const Options = struct {
     /// the binary file does not already have such a section.
     program_code_size_hint: u64 = 256 * 1024,
     entry_addr: ?u64 = null,
+    stack_size_override: ?u64 = null,
     /// Set to `true` to omit debug info.
     strip: bool = false,
     /// If this is true then this link code is responsible for outputting an object
@@ -44,6 +47,19 @@ pub const Options = struct {
     link_libc: bool = false,
     link_libcpp: bool = false,
     function_sections: bool = false,
+    eh_frame_hdr: bool = false,
+    rdynamic: bool = false,
+    z_nodelete: bool = false,
+    z_defs: bool = false,
+    bind_global_refs_locally: bool,
+    is_native_os: bool,
+    gc_sections: ?bool = null,
+    allow_shlib_undefined: ?bool = null,
+    linker_script: ?[]const u8 = null,
+    version_script: ?[]const u8 = null,
+    override_soname: ?[]const u8 = null,
+    /// Extra args passed directly to LLD. Ignored when not linking with LLD.
+    extra_lld_args: []const []const u8 = &[0][]const u8,
 
     objects: []const []const u8 = &[0][]const u8{},
     framework_dirs: []const []const u8 = &[0][]const u8{},
@@ -52,6 +68,9 @@ pub const Options = struct {
     lib_dirs: []const []const u8 = &[0][]const u8{},
     rpath_list: []const []const u8 = &[0][]const u8{},
 
+    version: std.builtin.Version,
+    libc_installation: ?*const LibCInstallation,
+
     pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
         return if (options.use_lld) .Obj else options.output_mode;
     }
src-self-hosted/main.zig
@@ -14,6 +14,7 @@ const zir = @import("zir.zig");
 const build_options = @import("build_options");
 const warn = std.log.warn;
 const introspect = @import("introspect.zig");
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 fn fatal(comptime format: []const u8, args: anytype) noreturn {
     std.log.emerg(format, args);
@@ -33,18 +34,22 @@ const usage =
     \\
     \\Commands:
     \\
-    \\  build-exe   [source]     Create executable from source or object files
-    \\  build-lib   [source]     Create library from source or object files
-    \\  build-obj   [source]     Create object from source or assembly
-    \\  cc                       Use Zig as a drop-in C compiler
-    \\  c++                      Use Zig as a drop-in C++ compiler
-    \\  env                      Print lib path, std path, compiler id and version
-    \\  fmt         [source]     Parse file and render in canonical zig format
-    \\  translate-c [source]     Convert C code to Zig code
-    \\  targets                  List available compilation targets
-    \\  version                  Print version number and exit
-    \\  zen                      Print zen of zig and exit
+    \\  build-exe        Create executable from source or object files
+    \\  build-lib        Create library from source or object files
+    \\  build-obj        Create object from source or assembly
+    \\  cc               Use Zig as a drop-in C compiler
+    \\  c++              Use Zig as a drop-in C++ compiler
+    \\  env              Print lib path, std path, compiler id and version
+    \\  fmt              Parse file and render in canonical zig format
+    \\  libc             Display native libc paths file or validate one
+    \\  translate-c      Convert C code to Zig code
+    \\  targets          List available compilation targets
+    \\  version          Print version number and exit
+    \\  zen              Print zen of zig and exit
     \\
+    \\General Options:
+    \\
+    \\  --help           Print command-specific usage
     \\
 ;
 
@@ -126,6 +131,8 @@ pub fn main() !void {
         return punt_to_clang(arena, args);
     } else if (mem.eql(u8, cmd, "fmt")) {
         return cmdFmt(gpa, cmd_args);
+    } else if (mem.eql(u8, cmd, "libc")) {
+        return cmdLibC(gpa, cmd_args);
     } else if (mem.eql(u8, cmd, "targets")) {
         const info = try std.zig.system.NativeTargetInfo.detect(arena, .{});
         const stdout = io.getStdOut().outStream();
@@ -184,7 +191,6 @@ const usage_build_generic =
     \\    ReleaseSmall            Optimize for small binary, safety off
     \\  -fPIC                     Force-enable Position Independent Code
     \\  -fno-PIC                  Force-disable Position Independent Code
-    \\  --dynamic                 Force output to be dynamically linked
     \\  --strip                   Exclude debug symbols
     \\  -ofmt=[mode]              Override target object format
     \\    elf                     Executable and Linking Format
@@ -199,6 +205,7 @@ const usage_build_generic =
     \\  -isystem  [dir]           Add directory to SYSTEM include search path
     \\  -I[dir]                   Add directory to include search path
     \\  -D[macro]=[value]         Define C [macro] to [value] (1 if [value] omitted)
+    \\  --libc [file]             Provide a file which specifies libc paths
     \\
     \\Link Options:
     \\  -l[lib], --library [lib]  Link against system library
@@ -208,6 +215,9 @@ const usage_build_generic =
     \\  --version [ver]           Dynamic library semver
     \\  -rdynamic                 Add all symbols to the dynamic symbol table
     \\  -rpath [path]             Add directory to the runtime library search path
+    \\  --eh-frame-hdr            Enable C++ exception handling by passing --eh-frame-hdr to linker
+    \\  -dynamic                  Force output to be dynamically linked
+    \\  -static                   Force output to be statically linked
     \\
     \\Debug Options (Zig Compiler Development):
     \\  -ftime-report             Print timing diagnostics
@@ -220,6 +230,14 @@ const usage_build_generic =
     \\
 ;
 
+const repl_help =
+    \\Commands:
+    \\  update   Detect changes to source files and update output files.
+    \\    help   Print this text
+    \\    exit   Quit this repl
+    \\
+;
+
 const Emit = union(enum) {
     no,
     yes_default_path,
@@ -275,16 +293,17 @@ pub fn buildOutputType(
     var version_script: ?[]const u8 = null;
     var disable_c_depfile = false;
     var override_soname: ?[]const u8 = null;
-    var linker_optimization: ?[]const u8 = null;
     var linker_gc_sections: ?bool = null;
     var linker_allow_shlib_undefined: ?bool = null;
     var linker_bind_global_refs_locally: ?bool = null;
     var linker_z_nodelete = false;
     var linker_z_defs = false;
-    var stack_size_override: u64 = 0;
+    var stack_size_override: ?u64 = null;
     var use_llvm: ?bool = null;
     var use_lld: ?bool = null;
     var use_clang: ?bool = null;
+    var link_eh_frame_hdr = false;
+    var libc_paths_file: ?[]const u8 = null;
 
     var system_libs = std.ArrayList([]const u8).init(gpa);
     defer system_libs.deinit();
@@ -292,6 +311,9 @@ pub fn buildOutputType(
     var clang_argv = std.ArrayList([]const u8).init(gpa);
     defer clang_argv.deinit();
 
+    var lld_argv = std.ArrayList([]const u8).init(gpa);
+    defer lld_argv.deinit();
+
     var lib_dirs = std.ArrayList([]const u8).init(gpa);
     defer lib_dirs.deinit();
 
@@ -414,15 +436,11 @@ pub fn buildOutputType(
                         fatal("unable to parse --version '{}': {}", .{ args[i], @errorName(err) });
                     };
                 } else if (mem.eql(u8, arg, "-target")) {
-                    if (i + 1 >= args.len) {
-                        fatal("expected parameter after -target", .{});
-                    }
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
                     i += 1;
                     target_arch_os_abi = args[i];
                 } else if (mem.eql(u8, arg, "-mcpu")) {
-                    if (i + 1 >= args.len) {
-                        fatal("expected parameter after -mcpu", .{});
-                    }
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
                     i += 1;
                     target_mcpu = args[i];
                 } else if (mem.startsWith(u8, arg, "-ofmt=")) {
@@ -430,11 +448,13 @@ pub fn buildOutputType(
                 } else if (mem.startsWith(u8, arg, "-mcpu=")) {
                     target_mcpu = arg["-mcpu=".len..];
                 } else if (mem.eql(u8, arg, "--dynamic-linker")) {
-                    if (i + 1 >= args.len) {
-                        fatal("expected parameter after --dynamic-linker", .{});
-                    }
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
                     i += 1;
                     target_dynamic_linker = args[i];
+                } else if (mem.eql(u8, arg, "--libc")) {
+                    if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg});
+                    i += 1;
+                    libc_paths_file = args[i];
                 } else if (mem.eql(u8, arg, "--watch")) {
                     watch = true;
                 } else if (mem.eql(u8, arg, "-ftime-report")) {
@@ -481,6 +501,8 @@ pub fn buildOutputType(
                     link_mode = .Static;
                 } else if (mem.eql(u8, arg, "--strip")) {
                     strip = true;
+                } else if (mem.eql(u8, arg, "--eh-frame-hdr")) {
+                    link_eh_frame_hdr = true;
                 } else if (mem.eql(u8, arg, "-Bsymbolic")) {
                     linker_bind_global_refs_locally = true;
                 } else if (mem.eql(u8, arg, "--debug-tokenize")) {
@@ -565,7 +587,7 @@ pub fn buildOutputType(
                     const file_ext = Module.classifyFileExt(mem.spanZ(it.only_arg));
                     switch (file_ext) {
                         .assembly, .c, .cpp, .ll, .bc, .h => try c_source_files.append(it.only_arg),
-                        .unknown => try link_objects.append(it.only_arg),
+                        .unknown, .so => try link_objects.append(it.only_arg),
                     }
                 },
                 .l => {
@@ -716,7 +738,7 @@ pub fn buildOutputType(
                 }
                 version_script = linker_args.items[i];
             } else if (mem.startsWith(u8, arg, "-O")) {
-                linker_optimization = arg;
+                try lld_argv.append(arg);
             } else if (mem.eql(u8, arg, "--gc-sections")) {
                 linker_gc_sections = true;
             } else if (mem.eql(u8, arg, "--no-gc-sections")) {
@@ -994,10 +1016,21 @@ pub fn buildOutputType(
     };
     var default_prng = std.rand.DefaultPrng.init(random_seed);
 
+    var libc_installation: ?LibCInstallation = null;
+    defer if (libc_installation) |*l| l.deinit(gpa);
+
+    if (libc_paths_file) |paths_file| {
+        libc_installation = LibCInstallation.parse(gpa, paths_file, io.getStdErr().writer()) catch |err| {
+            fatal("unable to parse libc paths file: {}", .{@errorName(err)});
+        };
+    }
+
     const module = Module.create(gpa, .{
         .zig_lib_dir = zig_lib_dir,
         .root_name = root_name,
         .target = target_info.target,
+        .is_native_os = cross_target.isNativeOs(),
+        .dynamic_linker = target_info.dynamic_linker.get(),
         .output_mode = output_mode,
         .root_pkg = root_pkg,
         .bin_file_dir_path = null,
@@ -1008,6 +1041,7 @@ pub fn buildOutputType(
         .optimize_mode = build_mode,
         .keep_source_files_loaded = zir_out_path != null,
         .clang_argv = clang_argv.items,
+        .lld_argv = lld_argv.items,
         .lib_dirs = lib_dirs.items,
         .rpath_list = rpath_list.items,
         .c_source_files = c_source_files.items,
@@ -1028,17 +1062,19 @@ pub fn buildOutputType(
         .version_script = version_script,
         .disable_c_depfile = disable_c_depfile,
         .override_soname = override_soname,
-        .linker_optimization = linker_optimization,
         .linker_gc_sections = linker_gc_sections,
         .linker_allow_shlib_undefined = linker_allow_shlib_undefined,
         .linker_bind_global_refs_locally = linker_bind_global_refs_locally,
         .linker_z_nodelete = linker_z_nodelete,
         .linker_z_defs = linker_z_defs,
+        .link_eh_frame_hdr = link_eh_frame_hdr,
         .stack_size_override = stack_size_override,
         .strip = strip,
         .self_exe_path = self_exe_path,
         .rand = &default_prng.random,
         .clang_passthrough_mode = arg_mode != .build,
+        .version = version,
+        .libc_installation = if (libc_installation) |*lci| lci else null,
     }) catch |err| {
         fatal("unable to create module: {}", .{@errorName(err)});
     };
@@ -1116,16 +1152,64 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo
     }
 }
 
-const repl_help =
-    \\Commands:
-    \\  update   Detect changes to source files and update output files.
-    \\    help   Print this text
-    \\    exit   Quit this repl
+pub const usage_libc =
+    \\Usage: zig libc
+    \\
+    \\    Detect the native libc installation and print the resulting
+    \\    paths to stdout. You can save this into a file and then edit
+    \\    the paths to create a cross compilation libc kit. Then you
+    \\    can pass `--libc [file]` for Zig to use it.
+    \\
+    \\Usage: zig libc [paths_file]
+    \\
+    \\    Parse a libc installation text file and validate it.
     \\
 ;
 
+pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void {
+    var input_file: ?[]const u8 = null;
+    {
+        var i: usize = 0;
+        while (i < args.len) : (i += 1) {
+            const arg = args[i];
+            if (mem.startsWith(u8, arg, "-")) {
+                if (mem.eql(u8, arg, "--help")) {
+                    const stdout = io.getStdOut().writer();
+                    try stdout.writeAll(usage_libc);
+                    process.exit(0);
+                } else {
+                    fatal("unrecognized parameter: '{}'", .{arg});
+                }
+            } else if (input_file != null) {
+                fatal("unexpected extra parameter: '{}'", .{arg});
+            } else {
+                input_file = arg;
+            }
+        }
+    }
+    if (input_file) |libc_file| {
+        const stderr = std.io.getStdErr().writer();
+        var libc = LibCInstallation.parse(gpa, libc_file, stderr) catch |err| {
+            fatal("unable to parse libc file: {}", .{@errorName(err)});
+        };
+        defer libc.deinit(gpa);
+    } else {
+        var libc = LibCInstallation.findNative(.{
+            .allocator = gpa,
+            .verbose = true,
+        }) catch |err| {
+            fatal("unable to detect native libc: {}", .{@errorName(err)});
+        };
+        defer libc.deinit(gpa);
+
+        var bos = io.bufferedOutStream(io.getStdOut().writer());
+        try libc.render(bos.writer());
+        try bos.flush();
+    }
+}
+
 pub const usage_fmt =
-    \\usage: zig fmt [file]...
+    \\Usage: zig fmt [file]...
     \\
     \\   Formats the input files and modifies them in-place.
     \\   Arguments can be files or directories, which are searched
src-self-hosted/Module.zig
@@ -24,6 +24,7 @@ const liveness = @import("liveness.zig");
 const astgen = @import("astgen.zig");
 const zir_sema = @import("zir_sema.zig");
 const build_options = @import("build_options");
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
 
 /// General-purpose allocator. Used for both temporary and long-term storage.
 gpa: *Allocator,
@@ -82,8 +83,6 @@ next_anon_name_index: usize = 0,
 /// contains Decls that need to be deleted if they end up having no references to them.
 deletion_set: std.ArrayListUnmanaged(*Decl) = .{},
 
-/// Owned by Module.
-root_name: []u8,
 keep_source_files_loaded: bool,
 use_clang: bool,
 sanitize_c: bool,
@@ -106,6 +105,19 @@ zig_cache_dir_path: []const u8,
 libc_include_dir_list: []const []const u8,
 rand: *std.rand.Random,
 
+/// Populated when we build libc++.a. A WorkItem to build this is placed in the queue
+/// and resolved before calling linker.flush().
+libcxx_static_lib: ?[]const u8 = null,
+/// Populated when we build libc++abi.a. A WorkItem to build this is placed in the queue
+/// and resolved before calling linker.flush().
+libcxxabi_static_lib: ?[]const u8 = null,
+/// Populated when we build libunwind.a. A WorkItem to build this is placed in the queue
+/// and resolved before calling linker.flush().
+libunwind_static_lib: ?[]const u8 = null,
+/// Populated when we build c.a. A WorkItem to build this is placed in the queue
+/// and resolved before calling linker.flush().
+libc_static_lib: ?[]const u8 = null,
+
 pub const InnerError = error{ OutOfMemory, AnalysisFail };
 
 const WorkItem = union(enum) {
@@ -932,6 +944,7 @@ pub const InitOptions = struct {
     root_pkg: ?*Package,
     output_mode: std.builtin.OutputMode,
     rand: *std.rand.Random,
+    dynamic_linker: ?[]const u8 = null,
     bin_file_dir_path: ?[]const u8 = null,
     bin_file_dir: ?std.fs.Dir = null,
     bin_file_path: []const u8,
@@ -941,6 +954,7 @@ pub const InitOptions = struct {
     optimize_mode: std.builtin.Mode = .Debug,
     keep_source_files_loaded: bool = false,
     clang_argv: []const []const u8 = &[0][]const u8{},
+    lld_argv: []const []const u8 = &[0][]const u8{},
     lib_dirs: []const []const u8 = &[0][]const u8{},
     rpath_list: []const []const u8 = &[0][]const u8{},
     c_source_files: []const []const u8 = &[0][]const u8{},
@@ -957,10 +971,11 @@ pub const InitOptions = struct {
     use_clang: ?bool = null,
     rdynamic: bool = false,
     strip: bool = false,
+    is_native_os: bool,
+    link_eh_frame_hdr: bool = false,
     linker_script: ?[]const u8 = null,
     version_script: ?[]const u8 = null,
     override_soname: ?[]const u8 = null,
-    linker_optimization: ?[]const u8 = null,
     linker_gc_sections: ?bool = null,
     function_sections: ?bool = null,
     linker_allow_shlib_undefined: ?bool = null,
@@ -969,8 +984,10 @@ pub const InitOptions = struct {
     linker_z_nodelete: bool = false,
     linker_z_defs: bool = false,
     clang_passthrough_mode: bool = false,
-    stack_size_override: u64 = 0,
+    stack_size_override: ?u64 = null,
     self_exe_path: ?[]const u8 = null,
+    version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 },
+    libc_installation: ?*const LibCInstallation = null,
 };
 
 pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
@@ -1002,6 +1019,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
                 options.frameworks.len != 0 or
                 options.system_libs.len != 0 or
                 options.link_libc or options.link_libcpp or
+                options.link_eh_frame_hdr or
                 options.linker_script != null or options.version_script != null)
             {
                 break :blk true;
@@ -1017,6 +1035,35 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             break :blk false;
         };
 
+        const must_dynamic_link = dl: {
+            if (target_util.cannotDynamicLink(options.target))
+                break :dl false;
+            if (target_util.osRequiresLibC(options.target))
+                break :dl true;
+            if (options.link_libc and options.target.isGnuLibC())
+                break :dl true;
+            if (options.system_libs.len != 0)
+                break :dl true;
+
+            break :dl false;
+        };
+        const default_link_mode: std.builtin.LinkMode = if (must_dynamic_link) .Dynamic else .Static;
+        const link_mode: std.builtin.LinkMode = if (options.link_mode) |lm| blk: {
+            if (lm == .Static and must_dynamic_link) {
+                return error.UnableToStaticLink;
+            }
+            break :blk lm;
+        } else default_link_mode;
+
+        const libc_dirs = try detectLibCIncludeDirs(
+            arena,
+            options.zig_lib_dir,
+            options.target,
+            options.is_native_os,
+            options.link_libc,
+            options.libc_installation,
+        );
+
         const bin_file = try link.File.openPath(gpa, .{
             .dir = options.bin_file_dir orelse std.fs.cwd(),
             .dir_path = options.bin_file_dir_path,
@@ -1024,8 +1071,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .root_name = root_name,
             .root_pkg = options.root_pkg,
             .target = options.target,
+            .dynamic_linker = options.dynamic_linker,
             .output_mode = options.output_mode,
-            .link_mode = options.link_mode orelse .Static,
+            .link_mode = link_mode,
             .object_format = ofmt,
             .optimize_mode = options.optimize_mode,
             .use_lld = use_lld,
@@ -1039,7 +1087,22 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .lib_dirs = options.lib_dirs,
             .rpath_list = options.rpath_list,
             .strip = options.strip,
+            .is_native_os = options.is_native_os,
             .function_sections = options.function_sections orelse false,
+            .allow_shlib_undefined = options.linker_allow_shlib_undefined,
+            .bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false,
+            .z_nodelete = options.linker_z_nodelete,
+            .z_defs = options.linker_z_defs,
+            .stack_size_override = options.stack_size_override,
+            .linker_script = options.linker_script,
+            .version_script = options.version_script,
+            .gc_sections = options.linker_gc_sections,
+            .eh_frame_hdr = options.link_eh_frame_hdr,
+            .rdynamic = options.rdynamic,
+            .extra_lld_args = options.lld_argv,
+            .override_soname = options.override_soname,
+            .version = options.version,
+            .libc_installation = libc_dirs.libc_installation,
         });
         errdefer bin_file.destroy();
 
@@ -1146,13 +1209,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             break :blk true;
         };
 
-        const libc_include_dir_list = try detectLibCIncludeDirs(
-            arena,
-            options.zig_lib_dir,
-            options.target,
-            options.link_libc,
-        );
-
         const sanitize_c: bool = options.want_sanitize_c orelse switch (options.optimize_mode) {
             .Debug, .ReleaseSafe => true,
             .ReleaseSmall, .ReleaseFast => false,
@@ -1163,7 +1219,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .arena_state = arena_allocator.state,
             .zig_lib_dir = options.zig_lib_dir,
             .zig_cache_dir_path = zig_cache_dir_path,
-            .root_name = root_name,
             .root_pkg = options.root_pkg,
             .root_scope = root_scope,
             .bin_file = bin_file,
@@ -1174,7 +1229,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
             .c_source_files = options.c_source_files,
             .cache = cache,
             .self_exe_path = options.self_exe_path,
-            .libc_include_dir_list = libc_include_dir_list,
+            .libc_include_dir_list = libc_dirs.libc_include_dir_list,
             .sanitize_c = sanitize_c,
             .rand = options.rand,
             .clang_passthrough_mode = options.clang_passthrough_mode,
@@ -1544,7 +1599,10 @@ fn buildCObject(mod: *Module, c_object: *CObject) !void {
     // directly to the output file.
     const direct_o = mod.c_source_files.len == 1 and mod.root_pkg == null and
         mod.bin_file.options.output_mode == .Obj and mod.bin_file.options.objects.len == 0;
-    const o_basename_noext = if (direct_o) mod.root_name else mem.split(c_source_basename, ".").next().?;
+    const o_basename_noext = if (direct_o)
+        mod.bin_file.options.root_name
+    else
+        mem.split(c_source_basename, ".").next().?;
     const o_basename = try std.fmt.allocPrint(arena, "{}{}", .{ o_basename_noext, mod.getTarget().oFileExt() });
 
     // We can't know the digest until we do the C compiler invocation, so we need a temporary filename.
@@ -1749,7 +1807,7 @@ fn addCCArgs(
                 try argv.append(p);
             }
         },
-        .assembly, .ll, .bc, .unknown => {},
+        .so, .assembly, .ll, .bc, .unknown => {},
     }
     // TODO CLI args for cpu features when compiling assembly
     //for (size_t i = 0; i < g->zig_target->llvm_cpu_features_asm_len; i += 1) {
@@ -4259,6 +4317,7 @@ pub const FileExt = enum {
     ll,
     bc,
     assembly,
+    so,
     unknown,
 };
 
@@ -4290,10 +4349,36 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
         return .assembly;
     } else if (mem.endsWith(u8, filename, ".h")) {
         return .h;
-    } else {
-        // TODO look for .so, .so.X, .so.X.Y, .so.X.Y.Z
-        return .unknown;
+    } else if (mem.endsWith(u8, filename, ".so")) {
+        return .so;
+    }
+    // Look for .so.X, .so.X.Y, .so.X.Y.Z
+    var it = mem.split(filename, ".");
+    _ = it.next().?;
+    var so_txt = it.next() orelse return .unknown;
+    while (!mem.eql(u8, so_txt, "so")) {
+        so_txt = it.next() orelse return .unknown;
     }
+    const n1 = it.next() orelse return .unknown;
+    const n2 = it.next();
+    const n3 = it.next();
+
+    _ = std.fmt.parseInt(u32, n1, 10) catch return .unknown;
+    if (n2) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
+    if (n3) |x| _ = std.fmt.parseInt(u32, x, 10) catch return .unknown;
+    if (it.next() != null) return .unknown;
+
+    return .so;
+}
+
+test "classifyFileExt" {
+    std.testing.expectEqual(FileExt.cpp, classifyFileExt("foo.cc"));
+    std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.nim"));
+    std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so"));
+    std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1"));
+    std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2"));
+    std.testing.expectEqual(FileExt.so, classifyFileExt("foo.so.1.2.3"));
+    std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.so.1.2.3~"));
 }
 
 fn haveFramePointer(mod: *Module) bool {
@@ -4303,16 +4388,29 @@ fn haveFramePointer(mod: *Module) bool {
     };
 }
 
+const LibCDirs = struct {
+    libc_include_dir_list: []const []const u8,
+    libc_installation: ?*const LibCInstallation,
+};
+
 fn detectLibCIncludeDirs(
     arena: *Allocator,
     zig_lib_dir: []const u8,
     target: Target,
+    is_native_os: bool,
     link_libc: bool,
-) ![]const []const u8 {
-    if (!link_libc) return &[0][]u8{};
+    libc_installation: ?*const LibCInstallation,
+) !LibCDirs {
+    if (!link_libc) {
+        return LibCDirs{
+            .libc_include_dir_list = &[0][]u8{},
+            .libc_installation = null,
+        };
+    }
 
-    // TODO Support --libc file explicitly providing libc paths. Or not? Maybe we are better off
-    // deleting that feature.
+    if (libc_installation) |lci| {
+        return detectLibCFromLibCInstallation(arena, target, lci);
+    }
 
     if (target_util.canBuildLibC(target)) {
         const generic_name = target_util.libCGenericName(target);
@@ -4348,9 +4446,52 @@ fn detectLibCIncludeDirs(
         list[1] = generic_include_dir;
         list[2] = arch_os_include_dir;
         list[3] = generic_os_include_dir;
-        return list;
+        return LibCDirs{
+            .libc_include_dir_list = list,
+            .libc_installation = null,
+        };
+    }
+
+    if (is_native_os) {
+        const libc = try arena.create(LibCInstallation);
+        libc.* = try LibCInstallation.findNative(.{ .allocator = arena });
+        return detectLibCFromLibCInstallation(arena, target, libc);
+    }
+
+    return LibCDirs{
+        .libc_include_dir_list = &[0][]u8{},
+        .libc_installation = null,
+    };
+}
+
+fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs {
+    var list = std.ArrayList([]const u8).init(arena);
+    try list.ensureCapacity(4);
+
+    list.appendAssumeCapacity(lci.include_dir.?);
+
+    const is_redundant = mem.eql(u8, lci.sys_include_dir.?, lci.include_dir.?);
+    if (!is_redundant) list.appendAssumeCapacity(lci.sys_include_dir.?);
+
+    if (target.os.tag == .windows) {
+        if (std.fs.path.dirname(lci.include_dir.?)) |include_dir_parent| {
+            const um_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "um" });
+            list.appendAssumeCapacity(um_dir);
+
+            const shared_dir = try std.fs.path.join(arena, &[_][]const u8{ include_dir_parent, "shared" });
+            list.appendAssumeCapacity(shared_dir);
+        }
     }
+    return LibCDirs{
+        .libc_include_dir_list = list.items,
+        .libc_installation = lci,
+    };
+}
 
-    // TODO finish porting detect_libc from codegen.cpp
-    return error.LibCDetectionUnimplemented;
+pub fn get_libc_crt_file(mod: *Module, arena: *Allocator, basename: []const u8) ![]const u8 {
+    // TODO port support for building crt files from stage1
+    const lci = mod.bin_file.options.libc_installation orelse return error.LibCInstallationNotAvailable;
+    const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCRTDir;
+    const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename });
+    return full_path;
 }
src-self-hosted/target.zig
@@ -109,3 +109,28 @@ pub fn canBuildLibC(target: std.Target) bool {
     }
     return false;
 }
+
+pub fn cannotDynamicLink(target: std.Target) bool {
+    return switch (target.os.tag) {
+        .freestanding, .other => true,
+        else => false,
+    };
+}
+
+pub fn osRequiresLibC(target: std.Target) bool {
+    // On Darwin, we always link libSystem which contains libc.
+    // Similarly on FreeBSD and NetBSD we always link system libc
+    // since this is the stable syscall interface.
+    return switch (target.os.tag) {
+        .freebsd, .netbsd, .dragonfly, .macosx, .ios, .watchos, .tvos => true,
+        else => false,
+    };
+}
+
+pub fn requiresPIE(target: std.Target) bool {
+    return target.isAndroid();
+}
+
+pub fn libc_needs_crti_crtn(target: std.Target) bool {
+    return !(target.cpu.arch.isRISCV() or target.isAndroid());
+}
src-self-hosted/test.zig
@@ -472,6 +472,7 @@ pub const TestContext = struct {
             .root_pkg = root_pkg,
             .keep_source_files_loaded = true,
             .object_format = ofmt,
+            .is_native_os = case.target.isNativeOs(),
         });
         defer module.destroy();