[lld-macho] implement options -(un)exported_symbol(s_list)

Implement command-line options to alter a dylib's exported-symbols list:
* `-exported_symbol*` options override the default export list. The export list is compiled according to the command-line option(s) only.
* `-unexported_symbol*` options hide otherwise public symbols.
* `-*exported_symbol PATTERN` options specify a single literal or glob pattern.
* `-*exported_symbols_list FILE` options specify a file containing a series of lines containing symbol literals or glob patterns. Whitespace and `#`-prefix comments are stripped.

Note: This is a simple implementation of the primary use case. ld64 has much more complexity surrounding interactions with other options, many of which are obscure and undocumented. We will start simple and complexity as necessary.

Differential Revision: https://reviews.llvm.org/D98223
This commit is contained in:
Greg McGary 2021-03-03 12:15:09 -08:00
parent a478b0a199
commit 06c4aadeb6
5 changed files with 216 additions and 12 deletions

View File

@ -9,9 +9,12 @@
#ifndef LLD_MACHO_CONFIG_H
#define LLD_MACHO_CONFIG_H
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/GlobPattern.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/TextAPI/MachO/Architecture.h"
#include "llvm/TextAPI/MachO/Platform.h"
@ -47,6 +50,21 @@ enum class UndefinedSymbolTreatment {
dynamic_lookup,
};
class SymbolPatterns {
// GlobPattern can also match literals,
// but we prefer the O(1) lookup of DenseSet.
llvm::DenseSet<llvm::CachedHashStringRef> literals;
std::vector<llvm::GlobPattern> globs;
public:
bool empty() const { return literals.empty() && globs.empty(); }
void clear();
void insert(llvm::StringRef symbolName);
bool matchLiteral(llvm::StringRef symbolName) const;
bool matchGlob(llvm::StringRef symbolName) const;
bool match(llvm::StringRef symbolName) const;
};
struct Configuration {
Symbol *entry;
bool hasReexports = false;
@ -80,9 +98,13 @@ struct Configuration {
std::vector<llvm::StringRef> frameworkSearchPaths;
std::vector<llvm::StringRef> runtimePaths;
std::vector<Symbol *> explicitUndefineds;
llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
SectionRenameMap sectionRenameMap;
SegmentRenameMap segmentRenameMap;
SymbolPatterns exportedSymbols;
SymbolPatterns unexportedSymbols;
};
// The symbol with the highest priority should be ordered first in the output

View File

@ -719,6 +719,57 @@ static uint32_t parseDylibVersion(const opt::ArgList& args, unsigned id) {
return version.rawValue();
}
void SymbolPatterns::clear() {
literals.clear();
globs.clear();
}
void SymbolPatterns::insert(StringRef symbolName) {
if (symbolName.find_first_of("*?[]") == StringRef::npos)
literals.insert(CachedHashStringRef(symbolName));
else if (Expected<GlobPattern> pattern = GlobPattern::create(symbolName))
globs.emplace_back(*pattern);
else
error("invalid symbol-name pattern: " + symbolName);
}
bool SymbolPatterns::matchLiteral(StringRef symbolName) const {
return literals.contains(CachedHashStringRef(symbolName));
}
bool SymbolPatterns::matchGlob(StringRef symbolName) const {
for (const llvm::GlobPattern &glob : globs)
if (glob.match(symbolName))
return true;
return false;
}
bool SymbolPatterns::match(StringRef symbolName) const {
return matchLiteral(symbolName) || matchGlob(symbolName);
}
static void handleSymbolPatterns(opt::InputArgList &args,
SymbolPatterns &symbolPatterns,
unsigned singleOptionCode,
unsigned listFileOptionCode) {
for (opt::Arg *arg : args.filtered(singleOptionCode))
symbolPatterns.insert(arg->getValue());
for (opt::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);
}
}
}
bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
raw_ostream &stdoutOS, raw_ostream &stderrOS) {
lld::stdoutOS = &stdoutOS;
@ -843,6 +894,16 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
validName(arg->getValue(1));
}
handleSymbolPatterns(args, config->exportedSymbols, OPT_exported_symbol,
OPT_exported_symbols_list);
handleSymbolPatterns(args, config->unexportedSymbols, OPT_unexported_symbol,
OPT_unexported_symbols_list);
if (!config->exportedSymbols.empty() && !config->unexportedSymbols.empty()) {
error("cannot use both -exported_symbol* and -unexported_symbol* options\n"
">>> ignoring unexports");
config->unexportedSymbols.clear();
}
config->saveTemps = args.hasArg(OPT_save_temps);
config->adhocCodesign = args.hasFlag(

View File

@ -406,25 +406,21 @@ def d : Flag<["-"], "d">,
def grp_resolve : OptionGroup<"resolve">, HelpText<"SYMBOL RESOLUTION">;
def exported_symbols_list : Separate<["-"], "exported_symbols_list">,
MetaVarName<"<file>">,
HelpText<"Symbols specified in <file> remain global, while others become private externs">,
Flags<[HelpHidden]>,
Group<grp_resolve>;
def exported_symbol : Separate<["-"], "exported_symbol">,
MetaVarName<"<symbol>">,
HelpText<"<symbol> remains global, while others become private externs">,
Flags<[HelpHidden]>,
Group<grp_resolve>;
def unexported_symbols_list : Separate<["-"], "unexported_symbols_list">,
def exported_symbols_list : Separate<["-"], "exported_symbols_list">,
MetaVarName<"<file>">,
HelpText<"Global symbols specified in <file> become private externs">,
Flags<[HelpHidden]>,
HelpText<"Symbols specified in <file> remain global, while others become private externs">,
Group<grp_resolve>;
def unexported_symbol : Separate<["-"], "unexported_symbol">,
MetaVarName<"<symbol>">,
HelpText<"Global <symbol> becomes private extern">,
Flags<[HelpHidden]>,
Group<grp_resolve>;
def unexported_symbols_list : Separate<["-"], "unexported_symbols_list">,
MetaVarName<"<file>">,
HelpText<"Global symbols specified in <file> become private externs">,
Group<grp_resolve>;
def reexported_symbols_list : Separate<["-"], "reexported_symbols_list">,
MetaVarName<"<file>">,

View File

@ -601,8 +601,14 @@ void ExportSection::finalizeContents() {
trieBuilder.setImageBase(in.header->addr);
for (const Symbol *sym : symtab->getSymbols()) {
if (const auto *defined = dyn_cast<Defined>(sym)) {
if (defined->privateExtern)
continue;
if (config->exportedSymbols.empty()) {
if (defined->privateExtern ||
config->unexportedSymbols.match(defined->getName()))
continue;
} else {
if (!config->exportedSymbols.match(defined->getName()))
continue;
}
trieBuilder.addSymbol(*defined);
hasWeakSymbol = hasWeakSymbol || sym->isWeakDef();
}

View File

@ -0,0 +1,119 @@
# REQUIRES: x86
# RUN: split-file %s %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %t/default.s -o %t/default.o
## Check that mixing exported and unexported symbol options yields an error
# RUN: not %lld -dylib %t/default.o -o /dev/null \
# RUN: -exported_symbol a -unexported_symbol b 2>&1 | \
# RUN: FileCheck --check-prefix=CONFLICT %s
# CONFLICT: error: cannot use both -exported_symbol* and -unexported_symbol* options
# CONFLICT-NEXT: >>> ignoring unexports
#--- default.s
.macro DEFSYM, type, sym
\type \sym
\sym:
retq
.endm
DEFSYM .globl, _keep_globl
DEFSYM .globl, _hide_globl
DEFSYM .private_extern, _keep_private
DEFSYM .private_extern, _show_private
## Check that the export trie is unaltered
# RUN: %lld -dylib %t/default.o -o %t/default
# RUN: llvm-objdump --macho --exports-trie %t/default | \
# RUN: FileCheck --check-prefix=DEFAULT %s
# DEFAULT-LABEL: Exports trie:
# DEFAULT-DAG: _hide_globl
# DEFAULT-DAG: _keep_globl
# DEFAULT-NOT: _hide_private
# DEFAULT-NOT: _show_private
## Check that the export trie is properly augmented
## Check that non-matching literal pattern has no effect
# RUN: %lld -dylib %t/default.o -o %t/export \
# RUN: -exported_symbol _show_private \
# RUN: -exported_symbol _extra_cruft -exported_symbol '*xtra_cr?ft'
# RUN: llvm-objdump --macho --exports-trie %t/export | \
# RUN: FileCheck --check-prefix=EXPORTED %s
# EXPORTED-LABEL: Exports trie:
# EXPORTED-DAG: _show_private
# EXPORTED-NOT: _hide_globl
# EXPORTED-NOT: _keep_globl
# EXPORTED-NOT: _hide_private
# EXPORTED-NOT: {{.*}}xtra_cr{{.}}ft
## Check that the export trie is properly diminished
## Check that non-matching glob pattern has no effect
# RUN: %lld -dylib %t/default.o -o %t/unexport \
# RUN: -unexported_symbol _hide_global
# RUN: llvm-objdump --macho --exports-trie %t/unexport | \
# RUN: FileCheck --check-prefix=UNEXPORTED %s
# UNEXPORTED-LABEL: Exports trie:
# UNEXPORTED-DAG: _keep_globl
# UNEXPORTED-NOT: _hide_globl
# UNEXPORTED-NOT: _show_private
# UNEXPORTED-NOT: _hide_private
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \
# RUN: %t/symdefs.s -o %t/symdefs.o
#--- symdefs.s
.macro DEFSYM, sym
.private_extern \sym
\sym:
retq
.endm
DEFSYM literal_only
DEFSYM literal_also
DEFSYM globby_only
DEFSYM globby_also
#--- literals
literal_only # comment
literal_also
# globby_only
globby_also
## Check that only string-literal patterns match
## Check that comments and blank lines are stripped from symbol list
# RUN: %lld -dylib %t/symdefs.o -o %t/literal \
# RUN: -exported_symbols_list %t/literals
# RUN: llvm-objdump --macho --exports-trie %t/literal | \
# RUN: FileCheck --check-prefix=LITERAL %s
# LITERAL-DAG: literal_only
# LITERAL-DAG: literal_also
# LITERAL-DAG: globby_also
# LITERAL-NOT: globby_only
#--- globbys
# literal_only
l?ter[aeiou]l_*[^y] # comment
*gl?bby_*
## Check that only glob patterns match
## Check that comments and blank lines are stripped from symbol list
# RUN: %lld -dylib %t/symdefs.o -o %t/globby \
# RUN: -exported_symbols_list %t/globbys
# RUN: llvm-objdump --macho --exports-trie %t/globby | \
# RUN: FileCheck --check-prefix=GLOBBY %s
# GLOBBY-DAG: literal_also
# GLOBBY-DAG: globby_only
# GLOBBY-DAG: globby_also
# GLOBBY-NOT: literal_only