Skip to content

Commit

Permalink
add basic std lib code for loading dynamic libraries
Browse files Browse the repository at this point in the history
this is going to only work for very basic libraries;
I plan to slowly add more features over time to support more
complicated libraries
  • Loading branch information
andrewrk committed Jun 16, 2018
1 parent b3a3e20 commit 48de57d
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 19 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Expand Up @@ -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"
Expand Down
12 changes: 6 additions & 6 deletions src/codegen.cpp
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/link.cpp
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
161 changes: 161 additions & 0 deletions 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));
}
15 changes: 15 additions & 0 deletions std/elf.zig
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions std/index.zig
Expand Up @@ -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");
Expand Down
7 changes: 6 additions & 1 deletion std/io.zig
Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions std/math/index.zig
Expand Up @@ -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;

Expand Down
11 changes: 1 addition & 10 deletions std/os/file.zig
Expand Up @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions std/os/index.zig
Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions test/build_examples.zig
Expand Up @@ -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");
}
}
3 changes: 3 additions & 0 deletions test/standalone/load_dynamic_library/add.zig
@@ -0,0 +1,3 @@
export fn add(a: i32, b: i32) i32 {
return a + b;
}
22 changes: 22 additions & 0 deletions 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);
}
17 changes: 17 additions & 0 deletions 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);
}

0 comments on commit 48de57d

Please sign in to comment.