// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually // a shared object file but an ASCII text file containing a linker // script to include a "real" libc.so file. Therefore, we need to // support a (very limited) subset of the linker script language. #include "mold.h" #include #include template static thread_local MemoryMappedFile *current_file; static std::string_view get_line(std::string_view input, const char *pos) { assert(input.data() <= pos); assert(pos < input.data() + input.size()); i64 start = input.rfind('\n', pos - input.data()); if (start == input.npos) start = 0; else start++; i64 end = input.find('\n', pos - input.data()); if (end == input.npos) end = input.size(); return input.substr(start, end - start); } template class SyntaxError { public: SyntaxError(Context &ctx, std::string_view errpos) : out(ctx) { std::string_view contents = current_file->get_contents(ctx); std::string_view line = get_line(contents, errpos.data()); i64 lineno = 1; for (i64 i = 0; contents.data() + i < line.data(); i++) if (contents[i] == '\n') lineno++; i64 column = errpos.data() - line.data(); std::stringstream ss; ss << current_file->name << ":" << lineno << ": "; i64 indent = ss.tellp(); ss << line << "\n" << std::setw(indent + column) << " " << "^ "; out << ss.str(); } template SyntaxError &operator<<(T &&val) { out << std::forward(val); return *this; } Fatal out; }; template static std::vector tokenize(Context &ctx, std::string_view input) { std::vector vec; while (!input.empty()) { if (isspace(input[0])) { input = input.substr(1); continue; } if (input.starts_with("/*")) { i64 pos = input.find("*/", 2); if (pos == std::string_view::npos) SyntaxError(ctx, input) << "unclosed comment"; input = input.substr(pos + 2); continue; } if (input[0] == '#') { i64 pos = input.find("\n", 1); if (pos == std::string_view::npos) break; input = input.substr(pos + 1); continue; } if (input[0] == '"') { i64 pos = input.find('"', 1); if (pos == std::string_view::npos) SyntaxError(ctx, input) << "unclosed string literal"; vec.push_back(input.substr(0, pos + 1)); input = input.substr(pos + 1); continue; } i64 pos = input.find_first_not_of( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789_.$/\\~=+[]*?-!^:"); if (pos == 0) pos = 1; else if (pos == input.npos) pos = input.size(); vec.push_back(input.substr(0, pos)); input = input.substr(pos); } return vec; } template static std::span skip(Context &ctx, std::span tok, std::string_view str) { if (tok.empty()) Fatal(ctx) << current_file->name << ": expected '" << str << "', but got EOF"; if (tok[0] != str) SyntaxError(ctx, tok[0]) << "expected '" << str << "'"; return tok.subspan(1); } static std::string_view unquote(std::string_view s) { if (s.size() > 0 && s[0] == '"') { assert(s[s.size() - 1] == '"'); return s.substr(1, s.size() - 2); } return s; } template static std::span read_output_format(Context &ctx, std::span tok) { tok = skip(ctx, tok, "("); while (!tok.empty() && tok[0] != ")") tok = tok.subspan(1); if (tok.empty()) Fatal(ctx) << current_file->name << ": expected ')', but got EOF"; return tok.subspan(1); } template static MemoryMappedFile *resolve_path(Context &ctx, std::string_view tok) { std::string str(unquote(tok)); if (str.starts_with("/")) return MemoryMappedFile::must_open(ctx, ctx.arg.sysroot + str); if (str.starts_with("-l")) return find_library(ctx, str.substr(2)); if (std::string path = path_dirname(current_file->name) + "/"; MemoryMappedFile *mb = MemoryMappedFile::open(ctx, path + str)) return mb; if (MemoryMappedFile *mb = MemoryMappedFile::open(ctx, str)) return mb; for (std::string_view dir : ctx.arg.library_paths) { std::string root = dir.starts_with("/") ? ctx.arg.sysroot : ""; std::string path = root + std::string(dir) + "/" + str; if (MemoryMappedFile *mb = MemoryMappedFile::open(ctx, path)) return mb; } SyntaxError(ctx, tok) << "library not found: " << str; } template static std::span read_group(Context &ctx, std::span tok) { tok = skip(ctx, tok, "("); while (!tok.empty() && tok[0] != ")") { if (tok[0] == "AS_NEEDED") { bool orig = ctx.as_needed; ctx.as_needed = true; tok = read_group(ctx, tok.subspan(1)); ctx.as_needed = orig; continue; } MemoryMappedFile *mb = resolve_path(ctx, tok[0]); read_file(ctx, mb); tok = tok.subspan(1); } if (tok.empty()) Fatal(ctx) << current_file->name << ": expected ')', but got EOF"; return tok.subspan(1); } template void parse_linker_script(Context &ctx, MemoryMappedFile *mb) { current_file = mb; std::vector vec = tokenize(ctx, mb->get_contents(ctx)); std::span tok = vec; while (!tok.empty()) { if (tok[0] == "OUTPUT_FORMAT") tok = read_output_format(ctx, tok.subspan(1)); else if (tok[0] == "INPUT" || tok[0] == "GROUP") tok = read_group(ctx, tok.subspan(1)); else SyntaxError(ctx, tok[0]) << "unknown token"; } } static bool read_label(std::span &tok, std::string label) { if (tok.size() >= 1 && tok[0] == label + ":") { tok = tok.subspan(1); return true; } if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") { tok = tok.subspan(2); return true; } return false; } template static void parse_version_script_commands(Context &ctx, std::span &tok, i16 &ver, bool is_extern_cpp) { bool is_global = true; while (!tok.empty() && tok[0] != "}") { if (read_label(tok, "global")) { is_global = true; continue; } if (read_label(tok, "local")) { is_global = false; continue; } if (tok[0] == "extern") { tok = tok.subspan(1); tok = skip(ctx, tok, "\"C++\""); tok = skip(ctx, tok, "{"); parse_version_script_commands(ctx, tok, ver, true); tok = skip(ctx, tok, "}"); tok = skip(ctx, tok, ";"); continue; } if (tok[0] == "*") ctx.arg.default_version = (is_global ? ver : VER_NDX_LOCAL); else ctx.arg.version_patterns.push_back({tok[0], ver, is_extern_cpp}); tok = skip(ctx, tok.subspan(1), ";"); } } template void parse_version_script(Context &ctx, std::string path) { current_file = MemoryMappedFile::must_open(ctx, path); std::vector vec = tokenize(ctx, current_file->get_contents(ctx)); std::span tok = vec; i16 next_ver = VER_NDX_LAST_RESERVED + 1; while (!tok.empty()) { i16 ver = VER_NDX_GLOBAL; if (tok[0] != "{") { ver = next_ver++; ctx.arg.version_definitions.push_back(tok[0]); tok = tok.subspan(1); } tok = skip(ctx, tok, "{"); parse_version_script_commands(ctx, tok, ver, false); tok = skip(ctx, tok, "}"); if (!tok.empty() && tok[0] != ";") tok = tok.subspan(1); tok = skip(ctx, tok, ";"); } if (!tok.empty()) SyntaxError(ctx, tok[0]) << "trailing garbage token"; } template void parse_dynamic_list(Context &ctx, std::string path) { current_file = MemoryMappedFile::must_open(ctx, path); std::vector vec = tokenize(ctx, current_file->get_contents(ctx)); std::span tok = vec; tok = skip(ctx, tok, "{"); i16 ver = VER_NDX_GLOBAL; while (!tok.empty() && tok[0] != "}") { if (read_label(tok, "global")) { ver = VER_NDX_GLOBAL; continue; } if (read_label(tok, "local")) { ver = VER_NDX_LOCAL; continue; } if (tok[0] == "*") ctx.arg.default_version = ver; else ctx.arg.version_patterns.push_back({tok[0], ver, false}); tok = skip(ctx, tok.subspan(1), ";"); } tok = skip(ctx, tok, "}"); tok = skip(ctx, tok, ";"); if (!tok.empty()) SyntaxError(ctx, tok[0]) << "trailing garbage token"; } #define INSTANTIATE(E) \ template \ void parse_linker_script(Context &ctx, MemoryMappedFile *mb); \ template \ void parse_version_script(Context &ctx, std::string path); \ template \ void parse_dynamic_list(Context &ctx, std::string path) INSTANTIATE(X86_64); INSTANTIATE(I386);