| ... | ... | @@ -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"); |