| author | |
| committer | |
| log | 470d887d801a982e4cd7ecfc5b0e736d92e8b414 |
| tree | 97872e7cfafde2059c6cb68209f0306fc7ffa9ee |
| parent | 1bbe521074727e0ac474413aa23fa1f6121c577c |
| parent | 52e0ca1312cac5fad5e941528d6c44f34985f537 |
| signature |
elf: link against libc installation6 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 dep | 1383 | // 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; |
| 1653 | 1660 | ||
| 1654 | fn parsePositional( | 1661 | fn 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 | } |
| 1687 | 1700 | ||
| 1688 | fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseErrorCtx) ParseError!void { | 1701 | fn 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( |
| 1718 | 1731 | ||
| 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); |
| 1724 | 1737 | ||
| 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 | } |
| 1766 | 1782 | ||
| 1783 | fn 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 | |||
| 1872 | fn 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 | } |
| 5888 | 6021 | ||
| 6022 | fn 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 | |||
| 5889 | const ParseErrorCtx = struct { | 6035 | const 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 | }; |
| 6184 | 6330 | ||
| 6185 | const SystemLib = struct { | 6331 | pub 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; |
| 6228 | const GotSection = synthetic_sections.GotSection; | 6374 | const GotSection = synthetic_sections.GotSection; |
| 6229 | const GotPltSection = synthetic_sections.GotPltSection; | 6375 | const GotPltSection = synthetic_sections.GotPltSection; |
| 6230 | const HashSection = synthetic_sections.HashSection; | 6376 | const HashSection = synthetic_sections.HashSection; |
| 6377 | const LdScript = @import("Elf/LdScript.zig"); | ||
| 6231 | const LinkerDefined = @import("Elf/LinkerDefined.zig"); | 6378 | const LinkerDefined = @import("Elf/LinkerDefined.zig"); |
| 6232 | const Liveness = @import("../Liveness.zig"); | 6379 | const Liveness = @import("../Liveness.zig"); |
| 6233 | const LlvmObject = @import("../codegen/llvm.zig").Object; | 6380 | const 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 | } |
| 72 | 72 | ||
| 73 | pub fn deinit(self: *Archive, allocator: Allocator) void { | 73 | pub 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 | }; |
| 123 | 124 | ||
| 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 @@ | ||
| 1 | cpu_arch: ?std.Target.Cpu.Arch = null, | ||
| 2 | args: std.ArrayListUnmanaged(Elf.SystemLib) = .{}, | ||
| 3 | |||
| 4 | pub fn deinit(scr: *LdScript, allocator: Allocator) void { | ||
| 5 | scr.args.deinit(allocator); | ||
| 6 | } | ||
| 7 | |||
| 8 | pub const Error = error{ | ||
| 9 | InvalidScript, | ||
| 10 | UnexpectedToken, | ||
| 11 | UnknownCpuArch, | ||
| 12 | OutOfMemory, | ||
| 13 | }; | ||
| 14 | |||
| 15 | pub 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 | |||
| 75 | fn 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 | |||
| 98 | const LineColumn = struct { | ||
| 99 | line: usize, | ||
| 100 | column: usize, | ||
| 101 | }; | ||
| 102 | |||
| 103 | const 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 | |||
| 120 | const 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 | |||
| 215 | const 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 | |||
| 245 | const 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 | |||
| 385 | const 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 | |||
| 423 | const testing = std.testing; | ||
| 424 | |||
| 425 | fn 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 | |||
| 442 | test "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 | |||
| 450 | test "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 | |||
| 459 | test "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 | |||
| 476 | test "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 | |||
| 497 | test "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 | |||
| 527 | const LdScript = @This(); | ||
| 528 | |||
| 529 | const std = @import("std"); | ||
| 530 | const assert = std.debug.assert; | ||
| 531 | |||
| 532 | const Allocator = std.mem.Allocator; | ||
| 533 | const 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 | } |
| 35 | 35 | ||
| 36 | pub fn deinit(self: *Object, allocator: Allocator) void { | 36 | pub 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 | } |
| 34 | 34 | ||
| 35 | pub fn deinit(self: *SharedObject, allocator: Allocator) void { | 35 | pub 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); |