Commit 48de57d824

Andrew Kelley <superjoe30@gmail.com>
2018-06-16 23:01:23
add basic std lib code for loading dynamic libraries
this is going to only work for very basic libraries; I plan to slowly add more features over time to support more complicated libraries
1 parent b3a3e20
src/codegen.cpp
@@ -6768,7 +6768,7 @@ static void define_builtin_compile_vars(CodeGen *g) {
     int err;
     Buf *abs_full_path = buf_alloc();
     if ((err = os_path_real(builtin_zig_path, abs_full_path))) {
-        fprintf(stderr, "unable to open '%s': %s", buf_ptr(builtin_zig_path), err_str(err));
+        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(builtin_zig_path), err_str(err));
         exit(1);
     }
 
@@ -6936,11 +6936,11 @@ static ImportTableEntry *add_special_code(CodeGen *g, PackageTableEntry *package
     Buf *abs_full_path = buf_alloc();
     int err;
     if ((err = os_path_real(&path_to_code_src, abs_full_path))) {
-        zig_panic("unable to open '%s': %s", buf_ptr(&path_to_code_src), err_str(err));
+        zig_panic("unable to open '%s': %s\n", buf_ptr(&path_to_code_src), err_str(err));
     }
     Buf *import_code = buf_alloc();
     if ((err = os_fetch_file_path(abs_full_path, import_code, false))) {
-        zig_panic("unable to open '%s': %s", buf_ptr(&path_to_code_src), err_str(err));
+        zig_panic("unable to open '%s': %s\n", buf_ptr(&path_to_code_src), err_str(err));
     }
 
     return add_source_file(g, package, abs_full_path, import_code);
@@ -7024,13 +7024,13 @@ static void gen_root_source(CodeGen *g) {
     Buf *abs_full_path = buf_alloc();
     int err;
     if ((err = os_path_real(rel_full_path, abs_full_path))) {
-        fprintf(stderr, "unable to open '%s': %s", buf_ptr(rel_full_path), err_str(err));
+        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(rel_full_path), err_str(err));
         exit(1);
     }
 
     Buf *source_code = buf_alloc();
     if ((err = os_fetch_file_path(rel_full_path, source_code, true))) {
-        fprintf(stderr, "unable to open '%s': %s", buf_ptr(rel_full_path), err_str(err));
+        fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(rel_full_path), err_str(err));
         exit(1);
     }
 
@@ -7374,7 +7374,7 @@ static void gen_h_file(CodeGen *g) {
 
     FILE *out_h = fopen(buf_ptr(g->out_h_path), "wb");
     if (!out_h)
-        zig_panic("unable to open %s: %s", buf_ptr(g->out_h_path), strerror(errno));
+        zig_panic("unable to open %s: %s\n", buf_ptr(g->out_h_path), strerror(errno));
 
     Buf *export_macro = preprocessor_mangle(buf_sprintf("%s_EXPORT", buf_ptr(g->root_out_name)));
     buf_upcase(export_macro);
src/link.cpp
@@ -208,7 +208,7 @@ static Buf *get_dynamic_linker_path(CodeGen *g) {
 static void construct_linker_job_elf(LinkJob *lj) {
     CodeGen *g = lj->codegen;
 
-    if (lj->link_in_crt) {
+    if (g->libc_link_lib != nullptr) {
         find_libc_lib_path(g);
     }
 
@@ -432,7 +432,7 @@ static bool zig_lld_link(ZigLLVM_ObjectFormatType oformat, const char **args, si
 static void construct_linker_job_coff(LinkJob *lj) {
     CodeGen *g = lj->codegen;
 
-    if (lj->link_in_crt) {
+    if (g->libc_link_lib != nullptr) {
         find_libc_lib_path(g);
     }
 
std/math/index.zig
@@ -536,6 +536,17 @@ test "math.cast" {
     assert(@typeOf(try cast(u8, u32(255))) == u8);
 }
 
+pub const AlignCastError = error{UnalignedMemory};
+
+/// Align cast a pointer but return an error if it's the wrong field
+pub fn alignCast(comptime alignment: u29, ptr: var) AlignCastError!@typeOf(@alignCast(alignment, ptr)) {
+    const addr = @ptrToInt(ptr);
+    if (addr % alignment != 0) {
+        return error.UnalignedMemory;
+    }
+    return @alignCast(alignment, ptr);
+}
+
 pub fn floorPowerOfTwo(comptime T: type, value: T) T {
     var x = value;
 
std/os/file.zig
@@ -265,16 +265,7 @@ pub const File = struct {
 
     pub fn getEndPos(self: *File) !usize {
         if (is_posix) {
-            var stat: posix.Stat = undefined;
-            const err = posix.getErrno(posix.fstat(self.handle, &stat));
-            if (err > 0) {
-                return switch (err) {
-                    posix.EBADF => error.BadFd,
-                    posix.ENOMEM => error.SystemResources,
-                    else => os.unexpectedErrorPosix(err),
-                };
-            }
-
+            const stat = try os.posixFStat(self.handle);
             return usize(stat.size);
         } else if (is_windows) {
             var file_size: windows.LARGE_INTEGER = undefined;
std/os/index.zig
@@ -2697,3 +2697,17 @@ pub fn posixWait(pid: i32) i32 {
         }
     }
 }
+
+pub fn posixFStat(fd: i32) !posix.Stat {
+    var stat: posix.Stat = undefined;
+    const err = posix.getErrno(posix.fstat(fd, &stat));
+    if (err > 0) {
+        return switch (err) {
+            posix.EBADF => error.BadFd,
+            posix.ENOMEM => error.SystemResources,
+            else => os.unexpectedErrorPosix(err),
+        };
+    }
+
+    return stat;
+}
std/dynamic_library.zig
@@ -0,0 +1,161 @@
+const std = @import("index.zig");
+const mem = std.mem;
+const elf = std.elf;
+const cstr = std.cstr;
+const linux = std.os.linux;
+
+pub const DynLib = struct {
+    allocator: *mem.Allocator,
+    elf_lib: ElfLib,
+    fd: i32,
+    map_addr: usize,
+    map_size: usize,
+
+    /// Trusts the file
+    pub fn findAndOpen(allocator: *mem.Allocator, name: []const u8) !DynLib {
+        return open(allocator, name);
+    }
+
+    /// Trusts the file
+    pub fn open(allocator: *mem.Allocator, path: []const u8) !DynLib {
+        const fd = try std.os.posixOpen(allocator, path, 0, linux.O_RDONLY);
+        errdefer std.os.close(fd);
+
+        const size = usize((try std.os.posixFStat(fd)).size);
+
+        const addr = linux.mmap(
+            null,
+            size,
+            linux.PROT_READ | linux.PROT_EXEC,
+            linux.MAP_PRIVATE | linux.MAP_LOCKED,
+            fd,
+            0,
+        );
+        errdefer _ = linux.munmap(addr, size);
+
+        const bytes = @intToPtr([*]align(std.os.page_size) u8, addr)[0..size];
+
+        return DynLib{
+            .allocator = allocator,
+            .elf_lib = try ElfLib.init(bytes),
+            .fd = fd,
+            .map_addr = addr,
+            .map_size = size,
+        };
+    }
+
+    pub fn close(self: *DynLib) void {
+        _ = linux.munmap(self.map_addr, self.map_size);
+        std.os.close(self.fd);
+        self.* = undefined;
+    }
+
+    pub fn lookup(self: *DynLib, name: []const u8) ?usize {
+        return self.elf_lib.lookup("", name);
+    }
+};
+
+pub const ElfLib = struct {
+    strings: [*]u8,
+    syms: [*]elf.Sym,
+    hashtab: [*]linux.Elf_Symndx,
+    versym: ?[*]u16,
+    verdef: ?*elf.Verdef,
+    base: usize,
+
+    // Trusts the memory
+    pub fn init(bytes: []align(@alignOf(elf.Ehdr)) u8) !ElfLib {
+        const eh = @ptrCast(*elf.Ehdr, bytes.ptr);
+        if (!mem.eql(u8, eh.e_ident[0..4], "\x7fELF")) return error.NotElfFile;
+        if (eh.e_type != elf.ET_DYN) return error.NotDynamicLibrary;
+
+        const elf_addr = @ptrToInt(bytes.ptr);
+        var ph_addr: usize = elf_addr + eh.e_phoff;
+
+        var base: usize = @maxValue(usize);
+        var maybe_dynv: ?[*]usize = null;
+        {
+            var i: usize = 0;
+            while (i < eh.e_phnum) : ({
+                i += 1;
+                ph_addr += eh.e_phentsize;
+            }) {
+                const ph = @intToPtr(*elf.Phdr, ph_addr);
+                switch (ph.p_type) {
+                    elf.PT_LOAD => base = elf_addr + ph.p_offset - ph.p_vaddr,
+                    elf.PT_DYNAMIC => maybe_dynv = @intToPtr([*]usize, elf_addr + ph.p_offset),
+                    else => {},
+                }
+            }
+        }
+        const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation;
+        if (base == @maxValue(usize)) return error.BaseNotFound;
+
+        var maybe_strings: ?[*]u8 = null;
+        var maybe_syms: ?[*]elf.Sym = null;
+        var maybe_hashtab: ?[*]linux.Elf_Symndx = null;
+        var maybe_versym: ?[*]u16 = null;
+        var maybe_verdef: ?*elf.Verdef = null;
+
+        {
+            var i: usize = 0;
+            while (dynv[i] != 0) : (i += 2) {
+                const p = base + dynv[i + 1];
+                switch (dynv[i]) {
+                    elf.DT_STRTAB => maybe_strings = @intToPtr([*]u8, p),
+                    elf.DT_SYMTAB => maybe_syms = @intToPtr([*]elf.Sym, p),
+                    elf.DT_HASH => maybe_hashtab = @intToPtr([*]linux.Elf_Symndx, p),
+                    elf.DT_VERSYM => maybe_versym = @intToPtr([*]u16, p),
+                    elf.DT_VERDEF => maybe_verdef = @intToPtr(*elf.Verdef, p),
+                    else => {},
+                }
+            }
+        }
+
+        return ElfLib{
+            .base = base,
+            .strings = maybe_strings orelse return error.ElfStringSectionNotFound,
+            .syms = maybe_syms orelse return error.ElfSymSectionNotFound,
+            .hashtab = maybe_hashtab orelse return error.ElfHashTableNotFound,
+            .versym = maybe_versym,
+            .verdef = maybe_verdef,
+        };
+    }
+
+    /// Returns the address of the symbol
+    pub fn lookup(self: *const ElfLib, vername: []const u8, name: []const u8) ?usize {
+        const maybe_versym = if (self.verdef == null) null else self.versym;
+
+        const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON);
+        const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);
+
+        var i: usize = 0;
+        while (i < self.hashtab[1]) : (i += 1) {
+            if (0 == (u32(1) << u5(self.syms[i].st_info & 0xf) & OK_TYPES)) continue;
+            if (0 == (u32(1) << u5(self.syms[i].st_info >> 4) & OK_BINDS)) continue;
+            if (0 == self.syms[i].st_shndx) continue;
+            if (!mem.eql(u8, name, cstr.toSliceConst(self.strings + self.syms[i].st_name))) continue;
+            if (maybe_versym) |versym| {
+                if (!checkver(self.verdef.?, versym[i], vername, self.strings))
+                    continue;
+            }
+            return self.base + self.syms[i].st_value;
+        }
+
+        return null;
+    }
+};
+
+fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [*]u8) bool {
+    var def = def_arg;
+    const vsym = @bitCast(u32, vsym_arg) & 0x7fff;
+    while (true) {
+        if (0 == (def.vd_flags & elf.VER_FLG_BASE) and (def.vd_ndx & 0x7fff) == vsym)
+            break;
+        if (def.vd_next == 0)
+            return false;
+        def = @intToPtr(*elf.Verdef, @ptrToInt(def) + def.vd_next);
+    }
+    const aux = @intToPtr(*elf.Verdaux, @ptrToInt(def) + def.vd_aux);
+    return mem.eql(u8, vername, cstr.toSliceConst(strings + aux.vda_name));
+}
std/elf.zig
@@ -305,6 +305,21 @@ pub const STT_ARM_16BIT = STT_HIPROC;
 pub const VER_FLG_BASE = 0x1;
 pub const VER_FLG_WEAK = 0x2;
 
+/// An unknown type.
+pub const ET_NONE = 0;
+
+/// A relocatable file.
+pub const ET_REL = 1;
+
+/// An executable file.
+pub const ET_EXEC = 2;
+
+/// A shared object.
+pub const ET_DYN = 3;
+
+/// A core file.
+pub const ET_CORE = 4;
+
 pub const FileType = enum {
     Relocatable,
     Executable,
std/index.zig
@@ -8,6 +8,7 @@ pub const HashMap = @import("hash_map.zig").HashMap;
 pub const LinkedList = @import("linked_list.zig").LinkedList;
 pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;
 pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
+pub const DynLib = @import("dynamic_library.zig").DynLib;
 
 pub const atomic = @import("atomic/index.zig");
 pub const base64 = @import("base64.zig");
std/io.zig
@@ -242,11 +242,16 @@ pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8)
 
 /// On success, caller owns returned buffer.
 pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
+    return readFileAllocAligned(allocator, path, @alignOf(u8));
+}
+
+/// On success, caller owns returned buffer.
+pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
     var file = try File.openRead(allocator, path);
     defer file.close();
 
     const size = try file.getEndPos();
-    const buf = try allocator.alloc(u8, size);
+    const buf = try allocator.alignedAlloc(u8, A, size);
     errdefer allocator.free(buf);
 
     var adapter = FileInStream.init(&file);
test/standalone/load_dynamic_library/add.zig
@@ -0,0 +1,3 @@
+export fn add(a: i32, b: i32) i32 {
+    return a + b;
+}
test/standalone/load_dynamic_library/build.zig
@@ -0,0 +1,22 @@
+const Builder = @import("std").build.Builder;
+
+pub fn build(b: *Builder) void {
+    const opts = b.standardReleaseOptions();
+
+    const lib = b.addSharedLibrary("add", "add.zig", b.version(1, 0, 0));
+    lib.setBuildMode(opts);
+    lib.linkSystemLibrary("c");
+
+    const main = b.addExecutable("main", "main.zig");
+    main.setBuildMode(opts);
+
+    const run = b.addCommand(".", b.env_map, [][]const u8{
+        main.getOutputPath(),
+        lib.getOutputPath(),
+    });
+    run.step.dependOn(&lib.step);
+    run.step.dependOn(&main.step);
+
+    const test_step = b.step("test", "Test the program");
+    test_step.dependOn(&run.step);
+}
test/standalone/load_dynamic_library/main.zig
@@ -0,0 +1,17 @@
+const std = @import("std");
+
+pub fn main() !void {
+    const args = try std.os.argsAlloc(std.debug.global_allocator);
+    defer std.os.argsFree(std.debug.global_allocator, args);
+
+    const dynlib_name = args[1];
+
+    var lib = try std.DynLib.open(std.debug.global_allocator, dynlib_name);
+    defer lib.close();
+
+    const addr = lib.lookup("add") orelse return error.SymbolNotFound;
+    const addFn = @intToPtr(extern fn (i32, i32) i32, addr);
+
+    const result = addFn(12, 34);
+    std.debug.assert(result == 46);
+}
test/build_examples.zig
@@ -18,4 +18,9 @@ pub fn addCases(cases: *tests.BuildExamplesContext) void {
     cases.addBuildFile("test/standalone/pkg_import/build.zig");
     cases.addBuildFile("test/standalone/use_alias/build.zig");
     cases.addBuildFile("test/standalone/brace_expansion/build.zig");
+    if (builtin.os == builtin.Os.linux) {
+        // TODO hook up the DynLib API for windows using LoadLibraryA
+        // TODO figure out how to make this work on darwin - probably libSystem has dlopen/dlsym in it
+        cases.addBuildFile("test/standalone/load_dynamic_library/build.zig");
+    }
 }
CMakeLists.txt
@@ -438,6 +438,7 @@ set(ZIG_STD_FILES
     "debug/failing_allocator.zig"
     "debug/index.zig"
     "dwarf.zig"
+    "dynamic_library.zig"
     "elf.zig"
     "empty.zig"
     "event.zig"