[lld-macho] Support -non_global_symbols_strip_list, -non_global_symbols_no_strip_list, -x

PR/55600

Differential Revision: https://reviews.llvm.org/D126046
This commit is contained in:
Vy Nguyen 2022-05-20 14:45:26 +07:00
parent d5ebba2aa6
commit fae6bd7563
5 changed files with 249 additions and 29 deletions

View File

@ -94,6 +94,13 @@ public:
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 @@ struct Configuration {
SymbolPatterns unexportedSymbols;
SymbolPatterns whyLive;
SymtabPresence localSymbolsPresence = SymtabPresence::All;
SymbolPatterns localSymbolPatterns;
bool zeroModTime = false;
llvm::StringRef osoPrefix;

View File

@ -938,26 +938,29 @@ bool SymbolPatterns::match(StringRef symbolName) const {
return matchLiteral(symbolName) || matchGlob(symbolName);
}
static void handleSymbolPatternsListHelper(const Arg *arg,
SymbolPatterns &symbolPatterns) {
StringRef path = arg->getValue();
Optional<MemoryBufferRef> 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<MemoryBufferRef> 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 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
">>> 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);

View File

@ -581,17 +581,14 @@ def S : Flag<["-"], "S">,
Group<grp_symtab>;
def x : Flag<["-"], "x">,
HelpText<"Exclude non-global symbols from the output symbol table">,
Flags<[HelpHidden]>,
Group<grp_symtab>;
def non_global_symbols_strip_list : Separate<["-"], "non_global_symbols_strip_list">,
MetaVarName<"<path>">,
HelpText<"Specify in <path> the non-global symbols that should be removed from the output symbol table">,
Flags<[HelpHidden]>,
Group<grp_symtab>;
def non_global_symbols_no_strip_list : Separate<["-"], "non_global_symbols_no_strip_list">,
MetaVarName<"<path>">,
HelpText<"Specify in <path> the non-global symbols that should remain in the output symbol table">,
Flags<[HelpHidden]>,
Group<grp_symtab>;
def oso_prefix : Separate<["-"], "oso_prefix">,
MetaVarName<"<path>">,

View File

@ -963,16 +963,41 @@ void SymtabSection::finalizeContents() {
symbols.push_back({sym, strx});
};
std::function<void(Symbol *)> 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<ObjFile>(file)) {
for (Symbol *sym : objFile->symbols) {
if (auto *defined = dyn_cast_or_null<Defined>(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<ObjFile>(file)) {
for (Symbol *sym : objFile->symbols) {
if (auto *defined = dyn_cast_or_null<Defined>(sym)) {
if (defined->isExternal() || !defined->isLive() ||
!defined->includeInSymtab)
continue;
localSymbolsHandler(sym);
}
}
}
}
@ -981,7 +1006,7 @@ void SymtabSection::finalizeContents() {
// __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 @@ void SymtabSection::finalizeContents() {
continue;
assert(defined->isExternal());
if (defined->privateExtern)
addSymbol(localSymbols, defined);
localSymbolsHandler(defined);
else
addSymbol(externalSymbols, defined);
} else if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {

View File

@ -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