diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -94,6 +94,13 @@ bool match(llvm::StringRef symbolName) const; }; +enum class SymtabPresence { + All, + None, + SelectivelyIncluded, + SelectivelyExcluded, +}; + struct Configuration { Symbol *entry = nullptr; bool hasReexports = false; @@ -179,6 +186,9 @@ SymbolPatterns unexportedSymbols; SymbolPatterns whyLive; + SymtabPresence localSymbolsPresence = SymtabPresence::All; + SymbolPatterns localSymbolPatterns; + bool zeroModTime = false; llvm::StringRef osoPrefix; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -938,26 +938,29 @@ return matchLiteral(symbolName) || matchGlob(symbolName); } +static void handleSymbolPatternsListHelper(const Arg *arg, + SymbolPatterns &symbolPatterns) { + StringRef path = arg->getValue(); + Optional buffer = readFile(path); + if (!buffer) { + error("Could not read symbol file: " + path); + return; + } + MemoryBufferRef mbref = *buffer; + for (StringRef line : args::getLines(mbref)) { + line = line.take_until([](char c) { return c == '#'; }).trim(); + if (!line.empty()) + symbolPatterns.insert(line); + } +} static void handleSymbolPatterns(InputArgList &args, SymbolPatterns &symbolPatterns, unsigned singleOptionCode, unsigned listFileOptionCode) { for (const Arg *arg : args.filtered(singleOptionCode)) symbolPatterns.insert(arg->getValue()); - for (const Arg *arg : args.filtered(listFileOptionCode)) { - StringRef path = arg->getValue(); - Optional buffer = readFile(path); - if (!buffer) { - error("Could not read symbol file: " + path); - continue; - } - MemoryBufferRef mbref = *buffer; - for (StringRef line : args::getLines(mbref)) { - line = line.take_until([](char c) { return c == '#'; }).trim(); - if (!line.empty()) - symbolPatterns.insert(line); - } - } + for (const Arg *arg : args.filtered(listFileOptionCode)) + handleSymbolPatternsListHelper(arg, symbolPatterns); } static void createFiles(const InputArgList &args) { @@ -1406,9 +1409,53 @@ ">>> ignoring unexports"); config->unexportedSymbols.clear(); } + + // Imitating LD64's: + // -non_global_symbols_no_strip_list and -non_global_symbols_strip_list can't + // both be present. + // But -x can be used with either of these two, in which case, the last arg + // takes effect. + // (TODO: This is kind of confusing - considering disallowing using them + // together for a more straightforward behaviour) + { + bool includeLocal = false; + bool excludeLocal = false; + for (const Arg *arg : + args.filtered(OPT_x, OPT_non_global_symbols_no_strip_list, + OPT_non_global_symbols_strip_list)) { + switch (arg->getOption().getID()) { + case OPT_x: + config->localSymbolsPresence = SymtabPresence::None; + + break; + case OPT_non_global_symbols_no_strip_list: + if (excludeLocal) { + error("cannot use both -non_global_symbols_no_strip_list and " + "-non_global_symbols_strip_list"); + } else { + includeLocal = true; + config->localSymbolsPresence = SymtabPresence::SelectivelyIncluded; + handleSymbolPatternsListHelper(arg, config->localSymbolPatterns); + } + break; + case OPT_non_global_symbols_strip_list: + if (includeLocal) { + error("cannot use both -non_global_symbols_no_strip_list and " + "-non_global_symbols_strip_list"); + } else { + excludeLocal = true; + config->localSymbolsPresence = SymtabPresence::SelectivelyExcluded; + handleSymbolPatternsListHelper(arg, config->localSymbolPatterns); + } + break; + default: + llvm_unreachable("unexpected option"); + } + } + } // Explicitly-exported literal symbols must be defined, but might - // languish in an archive if unreferenced elsewhere. Light a fire - // under those lazy symbols! + // languish in an archive if unreferenced elsewhere or if they are in the + // non-global strip list. Light a fire under those lazy symbols! for (const CachedHashStringRef &cachedName : config->exportedSymbols.literals) symtab->addUndefined(cachedName.val(), /*file=*/nullptr, /*isWeakRef=*/false); diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -581,17 +581,14 @@ Group; def x : Flag<["-"], "x">, HelpText<"Exclude non-global symbols from the output symbol table">, - Flags<[HelpHidden]>, Group; def non_global_symbols_strip_list : Separate<["-"], "non_global_symbols_strip_list">, MetaVarName<"">, HelpText<"Specify in the non-global symbols that should be removed from the output symbol table">, - Flags<[HelpHidden]>, Group; def non_global_symbols_no_strip_list : Separate<["-"], "non_global_symbols_no_strip_list">, MetaVarName<"">, HelpText<"Specify in the non-global symbols that should remain in the output symbol table">, - Flags<[HelpHidden]>, Group; def oso_prefix : Separate<["-"], "oso_prefix">, MetaVarName<"">, diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -963,16 +963,41 @@ symbols.push_back({sym, strx}); }; + std::function localSymbolsHandler; + switch (config->localSymbolsPresence) { + case SymtabPresence::All: + localSymbolsHandler = [&](Symbol *sym) { addSymbol(localSymbols, sym); }; + break; + case SymtabPresence::None: + localSymbolsHandler = [&](Symbol *) { /* Do nothing*/ }; + break; + case SymtabPresence::SelectivelyIncluded: + localSymbolsHandler = [&](Symbol *sym) { + if (config->localSymbolPatterns.match(sym->getName())) + addSymbol(localSymbols, sym); + }; + break; + case SymtabPresence::SelectivelyExcluded: + localSymbolsHandler = [&](Symbol *sym) { + if (!config->localSymbolPatterns.match(sym->getName())) + addSymbol(localSymbols, sym); + }; + break; + } + // Local symbols aren't in the SymbolTable, so we walk the list of object // files to gather them. - for (const InputFile *file : inputFiles) { - if (auto *objFile = dyn_cast(file)) { - for (Symbol *sym : objFile->symbols) { - if (auto *defined = dyn_cast_or_null(sym)) { - if (defined->isExternal() || !defined->isLive() || - !defined->includeInSymtab) - continue; - addSymbol(localSymbols, sym); + // But if `-x` is set, then we don't need to. + if (config->localSymbolsPresence != SymtabPresence::None) { + for (const InputFile *file : inputFiles) { + if (auto *objFile = dyn_cast(file)) { + for (Symbol *sym : objFile->symbols) { + if (auto *defined = dyn_cast_or_null(sym)) { + if (defined->isExternal() || !defined->isLive() || + !defined->includeInSymtab) + continue; + localSymbolsHandler(sym); + } } } } @@ -981,7 +1006,7 @@ // __dyld_private is a local symbol too. It's linker-created and doesn't // exist in any object file. if (Defined *dyldPrivate = in.stubHelper->dyldPrivate) - addSymbol(localSymbols, dyldPrivate); + localSymbolsHandler(dyldPrivate); for (Symbol *sym : symtab->getSymbols()) { if (!sym->isLive()) @@ -991,7 +1016,7 @@ continue; assert(defined->isExternal()); if (defined->privateExtern) - addSymbol(localSymbols, defined); + localSymbolsHandler(defined); else addSymbol(externalSymbols, defined); } else if (auto *dysym = dyn_cast(sym)) { diff --git a/lld/test/MachO/local-symbol-output.s b/lld/test/MachO/local-symbol-output.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/local-symbol-output.s @@ -0,0 +1,141 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %t/main.s -o %t/main.o + +## Check that -non_global_symbols_no_strip_list and -non_global_symbols_strip_list +## can't be used at the same time. +# RUN: not %lld %t/main.o -o /dev/null \ +# RUN: -non_global_symbols_no_strip_list %t/foo.txt \ +# RUN: -non_global_symbols_strip_list %t/foo.txt 2>&1 | \ +# RUN: FileCheck --check-prefix=CONFLICT %s + +# CONFLICT: error: cannot use both -non_global_symbols_no_strip_list and -non_global_symbols_strip_list + +## Check that -x causes none of the local symbols to be emitted. +# RUN: %lld %t/main.o -x -o %t/no_local.out +# RUN: llvm-nm %t/no_local.out | FileCheck --check-prefix NO_LOCAL %s + +# NO_LOCAL-NOT: t _foo +# NO_LOCAL-NOT: t _bar +# NO_LOCAL-NOT: t _baz +# NO_LOCAL: T _main + +## Check that when using -x with -non_global_symbols_no_strip_list, whichever appears +## last in the command line arg list will take precedence. +# RUN: %lld %t/main.o -x -non_global_symbols_no_strip_list %t/foo.txt -o %t/x_then_no_strip.out +# RUN: llvm-nm %t/x_then_no_strip.out | FileCheck --check-prefix X-NO-STRIP %s + +# RUN: %lld %t/main.o -non_global_symbols_no_strip_list %t/foo.txt -x -o %t/no_strip_then_x.out +# RUN: llvm-nm %t/no_strip_then_x.out | FileCheck --check-prefix NO_LOCAL %s + +# X-NO-STRIP-NOT: t _bar +# X-NO-STRIP-DAG: t _foo +# X-NO-STRIP-DAG: T _main + +## Check that -non_global_symbols_no_strip_list can be specified more than once +## (The final no-strip list is the union of all these) +# RUN: %lld %t/main.o -o %t/no_strip_multi.out \ +# RUN: -non_global_symbols_no_strip_list %t/foo.txt \ +# RUN: -non_global_symbols_no_strip_list %t/bar.txt +# RUN: llvm-nm %t/no_strip_multi.out | FileCheck --check-prefix NO-STRIP-MULTI %s + +# NO-STRIP-MULTI-NOT: t _baz +# NO-STRIP-MULTI-DAG: t _foo +# NO-STRIP-MULTI-DAG: t _bar +# NO-STRIP-MULTI-DAG: T _main + +## Check that when using -x with -non_global_symbols_strip_list, whichever appears +## last in the command line arg list will take precedence. +# RUN: %lld %t/main.o -x -non_global_symbols_strip_list %t/foo.txt -o %t/x_then_strip.out +# RUN: llvm-nm %t/x_then_strip.out | FileCheck --check-prefix X-STRIP %s + +# RUN: %lld %t/main.o -non_global_symbols_strip_list %t/foo.txt -x -o %t/strip_then_x.out +# RUN: llvm-nm %t/no_strip_then_x.out | FileCheck --check-prefix NO_LOCAL %s + +# X-STRIP-NOT: t _foo +# X-STRIP-DAG: t _bar +# X-STRIP-DAG: t _baz +# X-STRIP-DAG: T _main + +## Check that -non_global_symbols_strip_list can be specified more than once +## (The final strip list is the union of all these) +# RUN: %lld %t/main.o -o %t/strip_multi.out \ +# RUN: -non_global_symbols_strip_list %t/foo.txt \ +# RUN: -non_global_symbols_strip_list %t/bar.txt +# RUN: llvm-nm %t/strip_multi.out | FileCheck --check-prefix STRIP-MULTI %s + +# STRIP-MULTI-NOT: t _foo +# STRIP-MULTI-NOT: t _bar +# STRIP-MULTI-DAG: t _baz +# STRIP-MULTI-DAG: T _main + +## Test interactions with exported_symbol. +# RUN: %lld %t/main.o -o %t/strip_all_export_one.out \ +# RUN: -x -exported_symbol _foo \ +# RUN: -undefined dynamic_lookup +# RUN: llvm-nm %t/strip_all_export_one.out | FileCheck --check-prefix STRIP-EXP %s + +# STRIP-EXP: U _foo +# STRIP-EXP-EMPTY: + +## Test interactions of -x and -non_global_symbols_strip_list with unexported_symbol. +# RUN: %lld %t/main.o -o %t/strip_x_unexport_one.out \ +# RUN: -x -unexported_symbol _globby \ +# RUN: -undefined dynamic_lookup + +# RUN: %lld %t/main.o -o %t/strip_all_unexport_one.out \ +# RUN: -non_global_symbols_strip_list %t/globby.txt \ +# RUN: -non_global_symbols_strip_list %t/foo.txt \ +# RUN: -non_global_symbols_strip_list %t/bar.txt \ +# RUN: -unexported_symbol _globby \ +# RUN: -undefined dynamic_lookup + +# RUN: llvm-nm %t/strip_x_unexport_one.out | FileCheck --check-prefix STRIP-UNEXP %s +# RUN: llvm-nm %t/strip_all_unexport_one.out | FileCheck --check-prefix STRIP-UNEXP %s + +## -unexported_symbol made _globby a local, therefore it should be stripped by -x too +# STRIP-UNEXP: T __mh_execute_header +# STRIP-UNEXP-DAG: T _main +# STRIP-UNEXP-EMPTY: + +## Test interactions of -non_global_symbols_strip_list and unexported_symbol. +# RUN: %lld %t/main.o -undefined dynamic_lookup -o %t/no_strip_unexport.out \ +# RUN: -non_global_symbols_no_strip_list %t/globby.txt \ +# RUN: -unexported_symbol _globby + +# RUN: llvm-nm %t/no_strip_unexport.out | FileCheck --check-prefix NOSTRIP-UNEXP %s + +# NOSTRIP-UNEXP: T __mh_execute_header +# NOSTRIP-UNEXP-DAG: T _main +# NOSTRIP-UNEXP-DAG: t _globby +# NOSTRIP-UNEXP-EMPTY: + +#--- foo.txt +_foo + +#--- bar.txt +_bar + +#--- globby.txt +_globby + +#--- main.s +.globl _main +.globl _globby + +_foo: + ret + +_bar: + ret + +_baz: + ret + +_main: + callq _foo + ret + + _globby: + ret \ No newline at end of file