authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-19 07:39:03+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-10-19 07:39:03+02:00
log470d887d801a982e4cd7ecfc5b0e736d92e8b414
tree97872e7cfafde2059c6cb68209f0306fc7ffa9ee
parent1bbe521074727e0ac474413aa23fa1f6121c577c
parent52e0ca1312cac5fad5e941528d6c44f34985f537
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #17593 from ziglang/elf-native-libc

elf: link against libc installation

6 files changed, 695 insertions(+), 10 deletions(-)

CMakeLists.txt+1
...@@ -589,6 +589,7 @@ set(ZIG_STAGE2_SOURCES...@@ -589,6 +589,7 @@ set(ZIG_STAGE2_SOURCES
589 "${CMAKE_SOURCE_DIR}/src/link/Elf.zig"589 "${CMAKE_SOURCE_DIR}/src/link/Elf.zig"
590 "${CMAKE_SOURCE_DIR}/src/link/Elf/Archive.zig"590 "${CMAKE_SOURCE_DIR}/src/link/Elf/Archive.zig"
591 "${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig"591 "${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig"
592 "${CMAKE_SOURCE_DIR}/src/link/Elf/LdScript.zig"
592 "${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig"593 "${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig"
593 "${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig"594 "${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig"
594 "${CMAKE_SOURCE_DIR}/src/link/Elf/SharedObject.zig"595 "${CMAKE_SOURCE_DIR}/src/link/Elf/SharedObject.zig"
src/link/Elf.zig+156-9
...@@ -1383,8 +1383,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node...@@ -1383,8 +1383,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
1383 // libc dep1383 // libc dep
1384 self.error_flags.missing_libc = false;1384 self.error_flags.missing_libc = false;
1385 if (self.base.options.link_libc) {1385 if (self.base.options.link_libc) {
1386 if (self.base.options.libc_installation != null) {1386 if (self.base.options.libc_installation) |lc| {
1387 @panic("TODO explicit libc_installation");1387 const flags = target_util.libcFullLinkFlags(target);
1388 try system_libs.ensureUnusedCapacity(flags.len);
1389 for (flags) |flag| {
1390 const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so", .{
1391 lc.crt_dir.?, fs.path.sep, flag["-l".len..],
1392 });
1393 system_libs.appendAssumeCapacity(.{ .path = lib_path });
1394 }
1388 } else if (target.isGnuLibC()) {1395 } else if (target.isGnuLibC()) {
1389 try system_libs.ensureUnusedCapacity(glibc.libs.len + 1);1396 try system_libs.ensureUnusedCapacity(glibc.libs.len + 1);
1390 for (glibc.libs) |lib| {1397 for (glibc.libs) |lib| {
...@@ -1649,7 +1656,7 @@ const ParseError = error{...@@ -1649,7 +1656,7 @@ const ParseError = error{
1649 FileSystem,1656 FileSystem,
1650 NotSupported,1657 NotSupported,
1651 InvalidCharacter,1658 InvalidCharacter,
1652} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError;1659} || LdScript.Error || std.os.AccessError || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError;
16531660
1654fn parsePositional(1661fn parsePositional(
1655 self: *Elf,1662 self: *Elf,
...@@ -1682,7 +1689,13 @@ fn parseLibrary(...@@ -1682,7 +1689,13 @@ fn parseLibrary(
1682 try self.parseArchive(in_file, lib.path, must_link, ctx);1689 try self.parseArchive(in_file, lib.path, must_link, ctx);
1683 } else if (SharedObject.isSharedObject(in_file)) {1690 } else if (SharedObject.isSharedObject(in_file)) {
1684 try self.parseSharedObject(in_file, lib, ctx);1691 try self.parseSharedObject(in_file, lib, ctx);
1685 } else return error.UnknownFileType;1692 } else {
1693 // TODO if the script has a top-level comment identifying it as GNU ld script,
1694 // then report parse errors. Otherwise return UnknownFileType.
1695 self.parseLdScript(in_file, lib, ctx) catch |err| switch (err) {
1696 else => return error.UnknownFileType,
1697 };
1698 }
1686}1699}
16871700
1688fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseErrorCtx) ParseError!void {1701fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseErrorCtx) ParseError!void {
...@@ -1693,7 +1706,7 @@ fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseEr...@@ -1693,7 +1706,7 @@ fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseEr
1693 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));1706 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
1694 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));1707 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1695 self.files.set(index, .{ .object = .{1708 self.files.set(index, .{ .object = .{
1696 .path = path,1709 .path = try gpa.dupe(u8, path),
1697 .data = data,1710 .data = data,
1698 .index = index,1711 .index = index,
1699 } });1712 } });
...@@ -1718,11 +1731,14 @@ fn parseArchive(...@@ -1718,11 +1731,14 @@ fn parseArchive(
17181731
1719 const gpa = self.base.allocator;1732 const gpa = self.base.allocator;
1720 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));1733 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
1721 var archive = Archive{ .path = path, .data = data };1734 var archive = Archive{ .path = try gpa.dupe(u8, path), .data = data };
1722 defer archive.deinit(gpa);1735 defer archive.deinit(gpa);
1723 try archive.parse(self);1736 try archive.parse(self);
17241737
1725 for (archive.objects.items) |extracted| {1738 const objects = try archive.objects.toOwnedSlice(gpa);
1739 defer gpa.free(objects);
1740
1741 for (objects) |extracted| {
1726 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));1742 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1727 self.files.set(index, .{ .object = extracted });1743 self.files.set(index, .{ .object = extracted });
1728 const object = &self.files.items(.data)[index].object;1744 const object = &self.files.items(.data)[index].object;
...@@ -1749,7 +1765,7 @@ fn parseSharedObject(...@@ -1749,7 +1765,7 @@ fn parseSharedObject(
1749 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));1765 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
1750 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));1766 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
1751 self.files.set(index, .{ .shared_object = .{1767 self.files.set(index, .{ .shared_object = .{
1752 .path = lib.path,1768 .path = try gpa.dupe(u8, lib.path),
1753 .data = data,1769 .data = data,
1754 .index = index,1770 .index = index,
1755 .needed = lib.needed,1771 .needed = lib.needed,
...@@ -1764,6 +1780,123 @@ fn parseSharedObject(...@@ -1764,6 +1780,123 @@ fn parseSharedObject(
1764 if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch;1780 if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch;
1765}1781}
17661782
1783fn parseLdScript(self: *Elf, in_file: std.fs.File, lib: SystemLib, ctx: *ParseErrorCtx) ParseError!void {
1784 const tracy = trace(@src());
1785 defer tracy.end();
1786
1787 const gpa = self.base.allocator;
1788 const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
1789 defer gpa.free(data);
1790
1791 var script = LdScript{};
1792 defer script.deinit(gpa);
1793 try script.parse(data, self);
1794
1795 if (script.cpu_arch) |cpu_arch| {
1796 ctx.detected_cpu_arch = cpu_arch;
1797 if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch;
1798 }
1799
1800 const lib_dirs = self.base.options.lib_dirs;
1801
1802 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
1803 defer arena_allocator.deinit();
1804 const arena = arena_allocator.allocator();
1805
1806 var test_path = std.ArrayList(u8).init(arena);
1807 var checked_paths = std.ArrayList([]const u8).init(arena);
1808
1809 for (script.args.items) |scr_obj| {
1810 checked_paths.clearRetainingCapacity();
1811
1812 success: {
1813 if (mem.startsWith(u8, scr_obj.path, "-l")) {
1814 const lib_name = scr_obj.path["-l".len..];
1815
1816 // TODO I think technically we should re-use the mechanism used by the frontend here.
1817 // Maybe we should hoist search-strategy all the way here?
1818 for (lib_dirs) |lib_dir| {
1819 if (!self.isStatic()) {
1820 if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, lib_name, .Dynamic))
1821 break :success;
1822 }
1823 if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, lib_name, .Static))
1824 break :success;
1825 }
1826
1827 try self.reportMissingLibraryError(
1828 checked_paths.items,
1829 "missing library dependency: GNU ld script '{s}' requires '{s}', but file not found",
1830 .{
1831 lib.path,
1832 scr_obj.path,
1833 },
1834 );
1835 } else {
1836 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
1837 if (fs.realpath(scr_obj.path, &buffer)) |path| {
1838 test_path.clearRetainingCapacity();
1839 try test_path.writer().writeAll(path);
1840 break :success;
1841 } else |_| {}
1842
1843 try checked_paths.append(try gpa.dupe(u8, scr_obj.path));
1844 for (lib_dirs) |lib_dir| {
1845 if (try self.accessLibPath(&test_path, &checked_paths, lib_dir, scr_obj.path, null))
1846 break :success;
1847 }
1848
1849 try self.reportMissingLibraryError(
1850 checked_paths.items,
1851 "missing library dependency: GNU ld script '{s}' requires '{s}', but file not found",
1852 .{
1853 lib.path,
1854 scr_obj.path,
1855 },
1856 );
1857 }
1858 }
1859
1860 const full_path = test_path.items;
1861 const scr_file = try std.fs.cwd().openFile(full_path, .{});
1862 defer scr_file.close();
1863
1864 var scr_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined };
1865 self.parseLibrary(scr_file, .{
1866 .needed = scr_obj.needed,
1867 .path = full_path,
1868 }, false, &scr_ctx) catch |err| try self.handleAndReportParseError(full_path, err, &scr_ctx);
1869 }
1870}
1871
1872fn accessLibPath(
1873 self: *Elf,
1874 test_path: *std.ArrayList(u8),
1875 checked_paths: *std.ArrayList([]const u8),
1876 lib_dir_path: []const u8,
1877 lib_name: []const u8,
1878 link_mode: ?std.builtin.LinkMode,
1879) !bool {
1880 const sep = fs.path.sep_str;
1881 const target = self.base.options.target;
1882 test_path.clearRetainingCapacity();
1883 try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
1884 lib_dir_path,
1885 target.libPrefix(),
1886 lib_name,
1887 if (link_mode) |mode| switch (mode) {
1888 .Static => target.staticLibSuffix(),
1889 .Dynamic => target.dynamicLibSuffix(),
1890 } else "",
1891 });
1892 try checked_paths.append(try self.base.allocator.dupe(u8, test_path.items));
1893 fs.cwd().access(test_path.items, .{}) catch |err| switch (err) {
1894 error.FileNotFound => return false,
1895 else => |e| return e,
1896 };
1897 return true;
1898}
1899
1767/// When resolving symbols, we approach the problem similarly to `mold`.1900/// When resolving symbols, we approach the problem similarly to `mold`.
1768/// 1. Resolve symbols across all objects (including those preemptively extracted archives).1901/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
1769/// 2. Resolve symbols across all shared objects.1902/// 2. Resolve symbols across all shared objects.
...@@ -5886,6 +6019,19 @@ fn reportUndefined(self: *Elf, undefs: anytype) !void {...@@ -5886,6 +6019,19 @@ fn reportUndefined(self: *Elf, undefs: anytype) !void {
5886 }6019 }
5887}6020}
58886021
6022fn reportMissingLibraryError(
6023 self: *Elf,
6024 checked_paths: []const []const u8,
6025 comptime format: []const u8,
6026 args: anytype,
6027) error{OutOfMemory}!void {
6028 var err = try self.addErrorWithNotes(checked_paths.len);
6029 try err.addMsg(self, format, args);
6030 for (checked_paths) |path| {
6031 try err.addNote(self, "tried {s}", .{path});
6032 }
6033}
6034
5889const ParseErrorCtx = struct {6035const ParseErrorCtx = struct {
5890 detected_cpu_arch: std.Target.Cpu.Arch,6036 detected_cpu_arch: std.Target.Cpu.Arch,
5891};6037};
...@@ -6182,7 +6328,7 @@ pub const null_shdr = elf.Elf64_Shdr{...@@ -6182,7 +6328,7 @@ pub const null_shdr = elf.Elf64_Shdr{
6182 .sh_entsize = 0,6328 .sh_entsize = 0,
6183};6329};
61846330
6185const SystemLib = struct {6331pub const SystemLib = struct {
6186 needed: bool = false,6332 needed: bool = false,
6187 path: []const u8,6333 path: []const u8,
6188};6334};
...@@ -6228,6 +6374,7 @@ const GnuHashSection = synthetic_sections.GnuHashSection;...@@ -6228,6 +6374,7 @@ const GnuHashSection = synthetic_sections.GnuHashSection;
6228const GotSection = synthetic_sections.GotSection;6374const GotSection = synthetic_sections.GotSection;
6229const GotPltSection = synthetic_sections.GotPltSection;6375const GotPltSection = synthetic_sections.GotPltSection;
6230const HashSection = synthetic_sections.HashSection;6376const HashSection = synthetic_sections.HashSection;
6377const LdScript = @import("Elf/LdScript.zig");
6231const LinkerDefined = @import("Elf/LinkerDefined.zig");6378const LinkerDefined = @import("Elf/LinkerDefined.zig");
6232const Liveness = @import("../Liveness.zig");6379const Liveness = @import("../Liveness.zig");
6233const LlvmObject = @import("../codegen/llvm.zig").Object;6380const LlvmObject = @import("../codegen/llvm.zig").Object;
src/link/Elf/Archive.zig+2-1
...@@ -71,6 +71,7 @@ pub fn isArchive(file: std.fs.File) bool {...@@ -71,6 +71,7 @@ pub fn isArchive(file: std.fs.File) bool {
71}71}
7272
73pub fn deinit(self: *Archive, allocator: Allocator) void {73pub fn deinit(self: *Archive, allocator: Allocator) void {
74 allocator.free(self.path);
74 allocator.free(self.data);75 allocator.free(self.data);
75 self.objects.deinit(allocator);76 self.objects.deinit(allocator);
76}77}
...@@ -122,7 +123,7 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void {...@@ -122,7 +123,7 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void {
122 };123 };
123124
124 const object = Object{125 const object = Object{
125 .archive = self.path,126 .archive = try gpa.dupe(u8, self.path),
126 .path = try gpa.dupe(u8, object_name[0 .. object_name.len - 1]), // To account for trailing '/'127 .path = try gpa.dupe(u8, object_name[0 .. object_name.len - 1]), // To account for trailing '/'
127 .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),128 .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),
128 .index = undefined,129 .index = undefined,
src/link/Elf/LdScript.zig created+533
...@@ -0,0 +1,533 @@
1cpu_arch: ?std.Target.Cpu.Arch = null,
2args: std.ArrayListUnmanaged(Elf.SystemLib) = .{},
3
4pub fn deinit(scr: *LdScript, allocator: Allocator) void {
5 scr.args.deinit(allocator);
6}
7
8pub const Error = error{
9 InvalidScript,
10 UnexpectedToken,
11 UnknownCpuArch,
12 OutOfMemory,
13};
14
15pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void {
16 const gpa = elf_file.base.allocator;
17 var tokenizer = Tokenizer{ .source = data };
18 var tokens = std.ArrayList(Token).init(gpa);
19 defer tokens.deinit();
20 var line_col = std.ArrayList(LineColumn).init(gpa);
21 defer line_col.deinit();
22
23 var line: usize = 0;
24 var prev_line_last_col: usize = 0;
25
26 while (true) {
27 const tok = tokenizer.next();
28 try tokens.append(tok);
29 const column = tok.start - prev_line_last_col;
30 try line_col.append(.{ .line = line, .column = column });
31 switch (tok.id) {
32 .invalid => {
33 // TODO errors
34 // elf_file.base.fatal("invalid token in ld script: '{s}' ({d}:{d})", .{
35 // tok.get(data),
36 // line,
37 // column,
38 // });
39 return error.InvalidScript;
40 },
41 .new_line => {
42 line += 1;
43 prev_line_last_col = tok.end;
44 },
45 .eof => break,
46 else => {},
47 }
48 }
49
50 var it = TokenIterator{ .tokens = tokens.items };
51 var parser = Parser{ .source = data, .it = &it };
52 var args = std.ArrayList(Elf.SystemLib).init(gpa);
53 scr.doParse(.{
54 .parser = &parser,
55 .args = &args,
56 }) catch |err| switch (err) {
57 error.UnexpectedToken => {
58 // const last_token_id = parser.it.pos - 1;
59 // const last_token = parser.it.get(last_token_id);
60 // const lcol = line_col.items[last_token_id];
61 // TODO errors
62 // elf_file.base.fatal("unexpected token in ld script: {s} : '{s}' ({d}:{d})", .{
63 // @tagName(last_token.id),
64 // last_token.get(data),
65 // lcol.line,
66 // lcol.column,
67 // });
68 return error.InvalidScript;
69 },
70 else => |e| return e,
71 };
72 scr.args = args.moveToUnmanaged();
73}
74
75fn doParse(scr: *LdScript, ctx: struct {
76 parser: *Parser,
77 args: *std.ArrayList(Elf.SystemLib),
78}) !void {
79 while (true) {
80 ctx.parser.skipAny(&.{ .comment, .new_line });
81
82 if (ctx.parser.maybe(.command)) |cmd_id| {
83 const cmd = ctx.parser.getCommand(cmd_id);
84 switch (cmd) {
85 .output_format => scr.cpu_arch = try ctx.parser.outputFormat(),
86 .group => try ctx.parser.group(ctx.args),
87 else => return error.UnexpectedToken,
88 }
89 } else break;
90 }
91
92 if (ctx.parser.it.next()) |tok| switch (tok.id) {
93 .eof => {},
94 else => return error.UnexpectedToken,
95 };
96}
97
98const LineColumn = struct {
99 line: usize,
100 column: usize,
101};
102
103const Command = enum {
104 output_format,
105 group,
106 as_needed,
107
108 fn fromString(s: []const u8) ?Command {
109 inline for (@typeInfo(Command).Enum.fields) |field| {
110 comptime var buf: [field.name.len]u8 = undefined;
111 inline for (field.name, 0..) |c, i| {
112 buf[i] = comptime std.ascii.toUpper(c);
113 }
114 if (std.mem.eql(u8, &buf, s)) return @field(Command, field.name);
115 }
116 return null;
117 }
118};
119
120const Parser = struct {
121 source: []const u8,
122 it: *TokenIterator,
123
124 fn outputFormat(p: *Parser) !std.Target.Cpu.Arch {
125 const value = value: {
126 if (p.skip(&.{.lparen})) {
127 const value_id = try p.require(.literal);
128 const value = p.it.get(value_id);
129 _ = try p.require(.rparen);
130 break :value value.get(p.source);
131 } else if (p.skip(&.{ .new_line, .lbrace })) {
132 const value_id = try p.require(.literal);
133 const value = p.it.get(value_id);
134 _ = p.skip(&.{.new_line});
135 _ = try p.require(.rbrace);
136 break :value value.get(p.source);
137 } else return error.UnexpectedToken;
138 };
139 if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64;
140 return error.UnknownCpuArch;
141 }
142
143 fn group(p: *Parser, args: *std.ArrayList(Elf.SystemLib)) !void {
144 if (!p.skip(&.{.lparen})) return error.UnexpectedToken;
145
146 while (true) {
147 if (p.maybe(.literal)) |tok_id| {
148 const tok = p.it.get(tok_id);
149 const path = tok.get(p.source);
150 try args.append(.{ .path = path, .needed = true });
151 } else if (p.maybe(.command)) |cmd_id| {
152 const cmd = p.getCommand(cmd_id);
153 switch (cmd) {
154 .as_needed => try p.asNeeded(args),
155 else => return error.UnexpectedToken,
156 }
157 } else break;
158 }
159
160 _ = try p.require(.rparen);
161 }
162
163 fn asNeeded(p: *Parser, args: *std.ArrayList(Elf.SystemLib)) !void {
164 if (!p.skip(&.{.lparen})) return error.UnexpectedToken;
165
166 while (p.maybe(.literal)) |tok_id| {
167 const tok = p.it.get(tok_id);
168 const path = tok.get(p.source);
169 try args.append(.{ .path = path, .needed = false });
170 }
171
172 _ = try p.require(.rparen);
173 }
174
175 fn skip(p: *Parser, comptime ids: []const Token.Id) bool {
176 const pos = p.it.pos;
177 inline for (ids) |id| {
178 const tok = p.it.next() orelse return false;
179 if (tok.id != id) {
180 p.it.seekTo(pos);
181 return false;
182 }
183 }
184 return true;
185 }
186
187 fn skipAny(p: *Parser, comptime ids: []const Token.Id) void {
188 outer: while (p.it.next()) |tok| {
189 inline for (ids) |id| {
190 if (id == tok.id) continue :outer;
191 }
192 break p.it.seekBy(-1);
193 }
194 }
195
196 fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index {
197 const pos = p.it.pos;
198 const tok = p.it.next() orelse return null;
199 if (tok.id == id) return pos;
200 p.it.seekBy(-1);
201 return null;
202 }
203
204 fn require(p: *Parser, comptime id: Token.Id) !Token.Index {
205 return p.maybe(id) orelse return error.UnexpectedToken;
206 }
207
208 fn getCommand(p: *Parser, index: Token.Index) Command {
209 const tok = p.it.get(index);
210 assert(tok.id == .command);
211 return Command.fromString(tok.get(p.source)).?;
212 }
213};
214
215const Token = struct {
216 id: Id,
217 start: usize,
218 end: usize,
219
220 const Id = enum {
221 // zig fmt: off
222 eof,
223 invalid,
224
225 new_line,
226 lparen, // (
227 rparen, // )
228 lbrace, // {
229 rbrace, // }
230
231 comment, // /* */
232
233 command, // literal with special meaning, see Command
234 literal,
235 // zig fmt: on
236 };
237
238 const Index = usize;
239
240 inline fn get(tok: Token, source: []const u8) []const u8 {
241 return source[tok.start..tok.end];
242 }
243};
244
245const Tokenizer = struct {
246 source: []const u8,
247 index: usize = 0,
248
249 fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool {
250 comptime var count: usize = 0;
251 inline while (count < pattern.len) : (count += 1) {
252 if (count >= slice.len) return false;
253 const c = slice[count];
254 if (pattern[count] != c) return false;
255 }
256 return true;
257 }
258
259 fn matches(tok: Tokenizer, comptime pattern: []const u8) bool {
260 return matchesPattern(pattern, tok.source[tok.index..]);
261 }
262
263 fn isCommand(tok: Tokenizer, start: usize, end: usize) bool {
264 return if (Command.fromString(tok.source[start..end]) == null) false else true;
265 }
266
267 fn next(tok: *Tokenizer) Token {
268 var result = Token{
269 .id = .eof,
270 .start = tok.index,
271 .end = undefined,
272 };
273
274 var state: enum {
275 start,
276 comment,
277 literal,
278 } = .start;
279
280 while (tok.index < tok.source.len) : (tok.index += 1) {
281 const c = tok.source[tok.index];
282 switch (state) {
283 .start => switch (c) {
284 ' ', '\t' => result.start += 1,
285
286 '\n' => {
287 result.id = .new_line;
288 tok.index += 1;
289 break;
290 },
291
292 '\r' => {
293 if (tok.matches("\r\n")) {
294 result.id = .new_line;
295 tok.index += "\r\n".len;
296 } else {
297 result.id = .invalid;
298 tok.index += 1;
299 }
300 break;
301 },
302
303 '/' => if (tok.matches("/*")) {
304 state = .comment;
305 tok.index += "/*".len;
306 } else {
307 state = .literal;
308 },
309
310 '(' => {
311 result.id = .lparen;
312 tok.index += 1;
313 break;
314 },
315
316 ')' => {
317 result.id = .rparen;
318 tok.index += 1;
319 break;
320 },
321
322 '{' => {
323 result.id = .lbrace;
324 tok.index += 1;
325 break;
326 },
327
328 '}' => {
329 result.id = .rbrace;
330 tok.index += 1;
331 break;
332 },
333
334 else => state = .literal,
335 },
336
337 .comment => switch (c) {
338 '*' => if (tok.matches("*/")) {
339 result.id = .comment;
340 tok.index += "*/".len;
341 break;
342 },
343 else => {},
344 },
345
346 .literal => switch (c) {
347 ' ', '(', '\n' => {
348 if (tok.isCommand(result.start, tok.index)) {
349 result.id = .command;
350 } else {
351 result.id = .literal;
352 }
353 break;
354 },
355
356 ')' => {
357 result.id = .literal;
358 break;
359 },
360
361 '\r' => {
362 if (tok.matches("\r\n")) {
363 if (tok.isCommand(result.start, tok.index)) {
364 result.id = .command;
365 } else {
366 result.id = .literal;
367 }
368 } else {
369 result.id = .invalid;
370 tok.index += 1;
371 }
372 break;
373 },
374
375 else => {},
376 },
377 }
378 }
379
380 result.end = tok.index;
381 return result;
382 }
383};
384
385const TokenIterator = struct {
386 tokens: []const Token,
387 pos: Token.Index = 0,
388
389 fn next(it: *TokenIterator) ?Token {
390 const token = it.peek() orelse return null;
391 it.pos += 1;
392 return token;
393 }
394
395 fn peek(it: TokenIterator) ?Token {
396 if (it.pos >= it.tokens.len) return null;
397 return it.tokens[it.pos];
398 }
399
400 inline fn reset(it: *TokenIterator) void {
401 it.pos = 0;
402 }
403
404 inline fn seekTo(it: *TokenIterator, pos: Token.Index) void {
405 it.pos = pos;
406 }
407
408 fn seekBy(it: *TokenIterator, offset: isize) void {
409 const new_pos = @as(isize, @bitCast(it.pos)) + offset;
410 if (new_pos < 0) {
411 it.pos = 0;
412 } else {
413 it.pos = @as(usize, @intCast(new_pos));
414 }
415 }
416
417 inline fn get(it: *TokenIterator, pos: Token.Index) Token {
418 assert(pos < it.tokens.len);
419 return it.tokens[pos];
420 }
421};
422
423const testing = std.testing;
424
425fn testExpectedTokens(input: []const u8, expected: []const Token.Id) !void {
426 var given = std.ArrayList(Token.Id).init(testing.allocator);
427 defer given.deinit();
428
429 var tokenizer = Tokenizer{ .source = input };
430 while (true) {
431 const tok = tokenizer.next();
432 if (tok.id == .invalid) {
433 std.debug.print(" {s} => '{s}'\n", .{ @tagName(tok.id), tok.get(input) });
434 }
435 try given.append(tok.id);
436 if (tok.id == .eof) break;
437 }
438
439 try testing.expectEqualSlices(Token.Id, expected, given.items);
440}
441
442test "Tokenizer - just comments" {
443 try testExpectedTokens(
444 \\/* GNU ld script
445 \\ Use the shared library, but some functions are only in
446 \\ the static library, so try that secondarily. */
447 , &.{ .comment, .eof });
448}
449
450test "Tokenizer - comments with a simple command" {
451 try testExpectedTokens(
452 \\/* GNU ld script
453 \\ Use the shared library, but some functions are only in
454 \\ the static library, so try that secondarily. */
455 \\OUTPUT_FORMAT(elf64-x86-64)
456 , &.{ .comment, .new_line, .command, .lparen, .literal, .rparen, .eof });
457}
458
459test "Tokenizer - libc.so" {
460 try testExpectedTokens(
461 \\/* GNU ld script
462 \\ Use the shared library, but some functions are only in
463 \\ the static library, so try that secondarily. */
464 \\OUTPUT_FORMAT(elf64-x86-64)
465 \\GROUP ( /a/b/c.so.6 /a/d/e.a AS_NEEDED ( /f/g/h.so.2 ) )
466 , &.{
467 .comment, .new_line, // GNU comment
468 .command, .lparen, .literal, .rparen, .new_line, // output format
469 .command, .lparen, .literal, .literal, // group start
470 .command, .lparen, .literal, .rparen, // as needed
471 .rparen, // group end
472 .eof,
473 });
474}
475
476test "Parser - output format" {
477 const source =
478 \\OUTPUT_FORMAT(elf64-x86-64)
479 ;
480 var tokenizer = Tokenizer{ .source = source };
481 var tokens = std.ArrayList(Token).init(testing.allocator);
482 defer tokens.deinit();
483 while (true) {
484 const tok = tokenizer.next();
485 try testing.expect(tok.id != .invalid);
486 try tokens.append(tok);
487 if (tok.id == .eof) break;
488 }
489 var it = TokenIterator{ .tokens = tokens.items };
490 var parser = Parser{ .source = source, .it = &it };
491 const tok_id = try parser.require(.command);
492 try testing.expectEqual(parser.getCommand(tok_id), .output_format);
493 const cpu_arch = try parser.outputFormat();
494 try testing.expectEqual(cpu_arch, .x86_64);
495}
496
497test "Parser - group with as-needed" {
498 const source =
499 \\GROUP ( /a/b/c.so.6 /a/d/e.a AS_NEEDED ( /f/g/h.so.2 ) )
500 ;
501 var tokenizer = Tokenizer{ .source = source };
502 var tokens = std.ArrayList(Token).init(testing.allocator);
503 defer tokens.deinit();
504 while (true) {
505 const tok = tokenizer.next();
506 try testing.expect(tok.id != .invalid);
507 try tokens.append(tok);
508 if (tok.id == .eof) break;
509 }
510 var it = TokenIterator{ .tokens = tokens.items };
511 var parser = Parser{ .source = source, .it = &it };
512
513 var args = std.ArrayList(Elf.LinkObject).init(testing.allocator);
514 defer args.deinit();
515 const tok_id = try parser.require(.command);
516 try testing.expectEqual(parser.getCommand(tok_id), .group);
517 try parser.group(&args);
518
519 try testing.expectEqualStrings("/a/b/c.so.6", args.items[0].path);
520 try testing.expect(args.items[0].needed);
521 try testing.expectEqualStrings("/a/d/e.a", args.items[1].path);
522 try testing.expect(args.items[1].needed);
523 try testing.expectEqualStrings("/f/g/h.so.2", args.items[2].path);
524 try testing.expect(!args.items[2].needed);
525}
526
527const LdScript = @This();
528
529const std = @import("std");
530const assert = std.debug.assert;
531
532const Allocator = std.mem.Allocator;
533const Elf = @import("../Elf.zig");
src/link/Elf/Object.zig+2
...@@ -34,6 +34,8 @@ pub fn isObject(file: std.fs.File) bool {...@@ -34,6 +34,8 @@ pub fn isObject(file: std.fs.File) bool {
34}34}
3535
36pub fn deinit(self: *Object, allocator: Allocator) void {36pub fn deinit(self: *Object, allocator: Allocator) void {
37 if (self.archive) |path| allocator.free(path);
38 allocator.free(self.path);
37 allocator.free(self.data);39 allocator.free(self.data);
38 self.shdrs.deinit(allocator);40 self.shdrs.deinit(allocator);
39 self.strings.deinit(allocator);41 self.strings.deinit(allocator);
src/link/Elf/SharedObject.zig+1
...@@ -33,6 +33,7 @@ pub fn isSharedObject(file: std.fs.File) bool {...@@ -33,6 +33,7 @@ pub fn isSharedObject(file: std.fs.File) bool {
33}33}
3434
35pub fn deinit(self: *SharedObject, allocator: Allocator) void {35pub fn deinit(self: *SharedObject, allocator: Allocator) void {
36 allocator.free(self.path);
36 allocator.free(self.data);37 allocator.free(self.data);
37 self.versyms.deinit(allocator);38 self.versyms.deinit(allocator);
38 self.verstrings.deinit(allocator);39 self.verstrings.deinit(allocator);