Skip to content

Commit

Permalink
Merge pull request #8 from kubkon/handle-args
Browse files Browse the repository at this point in the history
Implement different zld drivers via symlinks and refactor usage/options per driver
  • Loading branch information
kubkon authored Jul 26, 2022
2 parents 9dcaee1 + ad30320 commit 3bd20cd
Show file tree
Hide file tree
Showing 20 changed files with 1,162 additions and 622 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ You will need latest Zig in your path. You can get nightly binaries from [here](
$ zig build
```

This will create the `zld` binary in `zig-out/bin/zld`. You can then use it like you'd use a standard linker.
This will create the `ld.zld` (Elf), `ld64.zld` (MachO) and `link-zld` (Coff) binaries in `zig-out/bin/`.
You can then use it like you'd use a standard linker.

```
$ cat <<EOF > hello.c
Expand All @@ -30,8 +31,11 @@ $ clang -c hello.c
# Or, create .o using zig cc
$ zig cc -c hello.c
# Link away!
$ ./zig-out/bin/zld hello.o -o hello
# On macOS
$ ./zig-out/bin/ld64.zld hello.o -o hello
# On Linux
$ ./zig-out/bin/ld.zld hello.o -o hello
# Run!
$ ./hello
Expand Down
76 changes: 59 additions & 17 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const Builder = @import("std").build.Builder;
const std = @import("std");
const fs = std.fs;
const log = std.log;

const Allocator = std.mem.Allocator;
const Builder = std.build.Builder;
const FileSource = std.build.FileSource;
const LibExeObjStep = std.build.LibExeObjStep;
const InstallDir = std.build.InstallDir;
const Step = std.build.Step;

pub fn build(b: *Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();

const enable_logging = b.option(bool, "log", "Whether to enable logging") orelse false;
Expand All @@ -27,17 +29,13 @@ pub fn build(b: *Builder) void {
const exe_opts = b.addOptions();
exe.addOptions("build_options", exe_opts);
exe_opts.addOption(bool, "enable_logging", enable_logging);

exe.install();

const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const elf_symlink = symlink(exe, "ld.zld");
elf_symlink.step.dependOn(&exe.step);

const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const macho_symlink = symlink(exe, "ld64.zld");
macho_symlink.step.dependOn(&exe.step);

const tests = b.addTest("src/test.zig");
tests.setBuildMode(mode);
Expand All @@ -47,8 +45,52 @@ pub fn build(b: *Builder) void {
const test_opts = b.addOptions();
tests.addOptions("build_options", test_opts);
test_opts.addOption(bool, "enable_qemu", is_qemu_enabled);
test_opts.addOption(bool, "enable_logging", enable_logging);

const test_step = b.step("test", "Run library and end-to-end tests");
test_step.dependOn(&exe.step);
test_step.dependOn(&tests.step);
}

fn symlink(exe: *LibExeObjStep, name: []const u8) *CreateSymlinkStep {
const step = CreateSymlinkStep.create(exe.builder, exe.getOutputSource(), name);
exe.builder.getInstallStep().dependOn(&step.step);
return step;
}

const CreateSymlinkStep = struct {
pub const base_id = .custom;

step: Step,
builder: *Builder,
source: FileSource,
target: []const u8,

pub fn create(
builder: *Builder,
source: FileSource,
target: []const u8,
) *CreateSymlinkStep {
const self = builder.allocator.create(CreateSymlinkStep) catch unreachable;
self.* = CreateSymlinkStep{
.builder = builder,
.step = Step.init(.log, builder.fmt("symlink {s} -> {s}", .{
source.getDisplayName(),
target,
}), builder.allocator, make),
.source = source,
.target = builder.dupe(target),
};
return self;
}

fn make(step: *Step) anyerror!void {
const self = @fieldParentPtr(CreateSymlinkStep, "step", step);
const rel_source = fs.path.basename(self.source.getPath(self.builder));
const source_path = self.builder.getInstallPath(.bin, rel_source);
const target_path = self.builder.getInstallPath(.bin, self.target);
fs.atomicSymLink(self.builder.allocator, source_path, target_path) catch |err| {
log.err("Unable to symlink {s} -> {s}", .{ source_path, target_path });
return err;
};
}
};
18 changes: 9 additions & 9 deletions src/Coff.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ const mem = std.mem;

const Allocator = mem.Allocator;
const Object = @import("Coff/Object.zig");
pub const Options = @import("Coff/Options.zig");
const Zld = @import("Zld.zig");

pub const base_tag = Zld.Tag.coff;

base: Zld,
options: Options,

objects: std.ArrayListUnmanaged(Object) = .{},

pub fn openPath(allocator: Allocator, options: Zld.Options) !*Coff {
pub fn openPath(allocator: Allocator, options: Options) !*Coff {
const file = try options.emit.directory.createFile(options.emit.sub_path, .{
.truncate = true,
.read = true,
Expand All @@ -34,32 +36,30 @@ pub fn openPath(allocator: Allocator, options: Zld.Options) !*Coff {
return self;
}

fn createEmpty(gpa: Allocator, options: Zld.Options) !*Coff {
fn createEmpty(gpa: Allocator, options: Options) !*Coff {
const self = try gpa.create(Coff);

self.* = .{
.base = .{
.tag = .coff,
.options = options,
.allocator = gpa,
.file = undefined,
},
.options = options,
};

return self;
}

pub fn deinit(self: *Coff) void {
self.closeFiles();

for (self.objects.items) |*object| {
object.deinit(self.base.allocator);
}

self.objects.deinit(self.base.allocator);
}

pub fn closeFiles(self: Coff) void {
pub fn closeFiles(self: *const Coff) void {
for (self.objects.items) |object| {
object.file.close();
}
Expand All @@ -70,9 +70,9 @@ pub fn flush(self: *Coff) !void {

var positionals = std.ArrayList([]const u8).init(gpa);
defer positionals.deinit();
try positionals.ensureTotalCapacity(self.base.options.positionals.len);
try positionals.ensureTotalCapacity(self.options.positionals.len);

for (self.base.options.positionals) |obj| {
for (self.options.positionals) |obj| {
positionals.appendAssumeCapacity(obj.path);
}

Expand Down Expand Up @@ -110,7 +110,7 @@ fn parseObject(self: *Coff, path: []const u8) !bool {
.file = file,
};

object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
object.parse(self.base.allocator, self.options.target.cpu_arch.?) catch |err| switch (err) {
error.EndOfStream => {
object.deinit(self.base.allocator);
return false;
Expand Down
14 changes: 7 additions & 7 deletions src/Coff/Object.zig
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ pub const IMAGE_SYM_CLASS_SECTION = 104;
pub const IMAGE_SYM_CLASS_WEAK_EXTERNAL = 105;
pub const IMAGE_SYM_CLASS_CLR_TOKEN = 107;

// comptime {
// assert(@sizeOf(Symbol) == 18);
// assert(@sizeOf(CoffHeader) == 20);
// }
comptime {
assert(@sizeOf(Symbol) == 18);
assert(@sizeOf(CoffHeader) == 20);
}

pub fn deinit(self: *Object, allocator: Allocator) void {
self.symtab.deinit(allocator);
Expand All @@ -108,7 +108,7 @@ pub fn deinit(self: *Object, allocator: Allocator) void {
allocator.free(self.name);
}

pub fn parse(self: *Object, allocator: Allocator, target: std.Target) !void {
pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void {
const reader = self.file.reader();
const header = try reader.readStruct(CoffHeader);

Expand All @@ -117,10 +117,10 @@ pub fn parse(self: *Object, allocator: Allocator, target: std.Target) !void {
return error.NotObject;
}

if (header.machine != @enumToInt(target.cpu.arch.toCoffMachine())) {
if (header.machine != @enumToInt(cpu_arch.toCoffMachine())) {
log.debug("Invalid architecture {any}, expected {any}", .{
header.machine,
target.cpu.arch.toCoffMachine(),
cpu_arch.toCoffMachine(),
});
return error.InvalidCpuArch;
}
Expand Down
91 changes: 91 additions & 0 deletions src/Coff/Options.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const Options = @This();

const std = @import("std");
const builtin = @import("builtin");
const io = std.io;
const mem = std.mem;
const process = std.process;

const Allocator = mem.Allocator;
const CrossTarget = std.zig.CrossTarget;
const Coff = @import("../Coff.zig");
const Zld = @import("../Zld.zig");

const usage =
\\Usage: link-zld [files...]
\\
\\General Options:
\\-l[name] Specify library to link against
\\-L[path] Specify library search dir
\\-o [path] Specify output path for the final artifact
\\-h, --help Print this help and exit
\\--debug-log [scope] Turn on debugging logs for [scope] (requires zld compiled with -Dlog)
;

emit: Zld.Emit,
output_mode: Zld.OutputMode,
target: CrossTarget,
positionals: []const Zld.LinkObject,
libs: std.StringArrayHashMap(Zld.SystemLib),
lib_dirs: []const []const u8,

pub fn parseArgs(arena: Allocator, ctx: Zld.MainCtx) !Options {
if (ctx.args.len == 0) {
ctx.printSuccess("{s}", .{usage});
}

var positionals = std.ArrayList(Zld.LinkObject).init(arena);
var libs = std.StringArrayHashMap(Zld.SystemLib).init(arena);
var lib_dirs = std.ArrayList([]const u8).init(arena);
var out_path: ?[]const u8 = null;

const Iterator = struct {
args: []const []const u8,
i: usize = 0,
fn next(it: *@This()) ?[]const u8 {
if (it.i >= it.args.len) {
return null;
}
defer it.i += 1;
return it.args[it.i];
}
};
var args_iter = Iterator{ .args = ctx.args };

while (args_iter.next()) |arg| {
if (mem.eql(u8, arg, "--help") or mem.eql(u8, arg, "-h")) {
ctx.printSuccess("{s}", .{usage});
} else if (mem.eql(u8, arg, "--debug-log")) {
const scope = args_iter.next() orelse ctx.printFailure("Expected log scope after {s}", .{arg});
try ctx.log_scopes.append(scope);
} else if (mem.startsWith(u8, arg, "-l")) {
try libs.put(arg[2..], .{});
} else if (mem.startsWith(u8, arg, "-L")) {
try lib_dirs.append(arg[2..]);
} else if (mem.eql(u8, arg, "-o")) {
out_path = args_iter.next() orelse
ctx.printFailure("Expected output path after {s}", .{arg});
} else {
try positionals.append(.{
.path = arg,
.must_link = true,
});
}
}

if (positionals.items.len == 0) {
ctx.printFailure("Expected at least one input .o file", .{});
}

return Options{
.emit = .{
.directory = std.fs.cwd(),
.sub_path = out_path orelse "a.out",
},
.target = CrossTarget.fromTarget(builtin.target),
.output_mode = .exe,
.positionals = positionals.items,
.libs = libs,
.lib_dirs = lib_dirs.items,
};
}
Loading

0 comments on commit 3bd20cd

Please sign in to comment.