[llvm-objcopy] Add support for shell wildcards

Summary: GNU objcopy accepts the --wildcard flag to allow wildcard matching on symbol-related flags. (Note: it's implicitly true for section flags).

The basic syntax is to allow *, ?, \, and [] which work similarly to how they work in a shell. Additionally, starting a wildcard with ! causes that wildcard to prevent it from matching a flag.

Use an updated GlobPattern in libSupport to handle these patterns. It does not fully match the `fnmatch` used by GNU objcopy since named character classes (e.g. `[[:digit:]]`) are not supported, but this should support most existing use cases (mostly just `*` is what's used anyway).

Reviewers: jhenderson, MaskRay, evgeny777, espindola, alexshap

Reviewed By: MaskRay

Subscribers: nickdesaulniers, emaste, arichardson, hiraditya, jakehehrlich, abrachet, seiya, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D66613

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@375169 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/docs/CommandGuide/llvm-objcopy.rst b/docs/CommandGuide/llvm-objcopy.rst
index ccdcf13..56c3a77 100644
--- a/docs/CommandGuide/llvm-objcopy.rst
+++ b/docs/CommandGuide/llvm-objcopy.rst
@@ -142,6 +142,30 @@
 
  Read command-line options and commands from response file `<FILE>`.
 
+.. option:: --wildcard, -w
+
+  Allow wildcard syntax for symbol-related flags. On by default for
+  section-related flags. Incompatible with --regex.
+
+  Wildcard syntax allows the following special symbols:
+
+  ====================== ========================= ==================
+   Character              Meaning                   Equivalent
+  ====================== ========================= ==================
+  ``*``                  Any number of characters  ``.*``
+  ``?``                  Any single character      ``.``
+  ``\``                  Escape the next character ``\``
+  ``[a-z]``              Character class           ``[a-z]``
+  ``[!a-z]``, ``[^a-z]`` Negated character class   ``[^a-z]``
+  ====================== ========================= ==================
+
+  Additionally, starting a wildcard with '!' will prevent a match, even if
+  another flag matches. For example ``-w -N '*' -N '!x'`` will strip all symbols
+  except for ``x``.
+
+  The order of wildcards does not matter. For example, ``-w -N '*' -N '!x'`` is
+  the same as ``-w -N '!x' -N '*'``.
+
 COFF-SPECIFIC OPTIONS
 ---------------------
 
diff --git a/docs/CommandGuide/llvm-strip.rst b/docs/CommandGuide/llvm-strip.rst
index 41529bf..e1f07d9 100644
--- a/docs/CommandGuide/llvm-strip.rst
+++ b/docs/CommandGuide/llvm-strip.rst
@@ -104,6 +104,30 @@
 
  Read command-line options and commands from response file `<FILE>`.
 
+.. option:: --wildcard, -w
+
+  Allow wildcard syntax for symbol-related flags. On by default for
+  section-related flags. Incompatible with --regex.
+
+  Wildcard syntax allows the following special symbols:
+
+  ====================== ========================= ==================
+   Character              Meaning                   Equivalent
+  ====================== ========================= ==================
+  ``*``                  Any number of characters  ``.*``
+  ``?``                  Any single character      ``.``
+  ``\``                  Escape the next character ``\``
+  ``[a-z]``              Character class           ``[a-z]``
+  ``[!a-z]``, ``[^a-z]`` Negated character class   ``[^a-z]``
+  ====================== ========================= ==================
+
+  Additionally, starting a wildcard with '!' will prevent a match, even if
+  another flag matches. For example ``-w -N '*' -N '!x'`` will strip all symbols
+  except for ``x``.
+
+  The order of wildcards does not matter. For example, ``-w -N '*' -N '!x'`` is
+  the same as ``-w -N '!x' -N '*'``.
+
 COFF-SPECIFIC OPTIONS
 ---------------------
 
diff --git a/test/tools/llvm-objcopy/ELF/wildcard-flags.test b/test/tools/llvm-objcopy/ELF/wildcard-flags.test
new file mode 100644
index 0000000..29a91cd
--- /dev/null
+++ b/test/tools/llvm-objcopy/ELF/wildcard-flags.test
@@ -0,0 +1,162 @@
+## This test checks basic functionality of glob (or "shell wildcard") matching,
+## as well as verifying all the relevant flags in llvm-objcopy and llvm-strip
+## are configured correctly.
+## For more detailed syntax tests, see wildcard-syntax.test.
+
+# RUN: yaml2obj %s > %t.o
+
+## Check that --regex and --wildcard cannot be used together.
+# RUN: not llvm-objcopy --regex --wildcard %t.o %t.err.o 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=ERR
+# RUN: not llvm-strip --regex --wildcard %t.o -o %t.err.o 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=ERR
+
+# ERR: error: --regex and --wildcard are incompatible
+
+## Check that section removal flags default to glob matches.
+
+## --keep-section:
+# RUN: llvm-objcopy --strip-all --keep-section='.f*' %t.o %t.ksec.1.o
+# RUN: llvm-readobj --sections %t.ksec.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,SEC,FOO-SEC
+# RUN: llvm-strip --strip-all --keep-section='.f*' %t.o -o %t.ksec.2.o
+# RUN: cmp %t.ksec.1.o %t.ksec.2.o
+
+## --only-section:
+# RUN: llvm-objcopy --strip-all --only-section='.f*' %t.o %t.osec.1.o
+# RUN: llvm-readobj --sections %t.osec.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,SEC,FOO-SEC
+
+## --remove-section:
+# RUN: llvm-objcopy --strip-debug --remove-section='.s??tab' %t.o %t.rsec.1.o
+# RUN: llvm-readobj --sections %t.rsec.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,SEC,FOO-SEC,BAR-SEC
+# RUN: llvm-strip --strip-debug --remove-section='.s??tab' %t.o -o %t.rsec.2.o
+# RUN: cmp %t.rsec.1.o %t.rsec.2.o
+
+## Check that symbol removal options default to literal matches. Adding -w
+## enables glob support for these options.
+
+## --globalize-symbol:
+# RUN: llvm-objcopy --globalize-symbol='*' %t.o %t.globsym.1.o
+# RUN: llvm-readobj --symbols %t.globsym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,LOCAL,FOO-SYM,BAR-SYM
+
+# RUN: llvm-objcopy -w --globalize-symbol='*' %t.o %t.globsym.2.o
+# RUN: llvm-readobj --symbols %t.globsym.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,GLOBAL,FOO-SYM,BAR-SYM
+
+## --keep-symbol:
+# RUN: llvm-objcopy --discard-all --keep-symbol='f*' %t.o %t.ksym.1.o
+# RUN: llvm-readobj --symbols %t.ksym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK
+# RUN: llvm-strip --discard-all --keep-symbol='f*' %t.o -o %t.ksym.2.o
+# RUN: cmp %t.ksym.1.o %t.ksym.2.o
+
+# RUN: llvm-objcopy --discard-all -w --keep-symbol='f*' %t.o %t.ksym.3.o
+# RUN: llvm-readobj --symbols %t.ksym.3.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO-SYM
+# RUN: llvm-strip --discard-all -w --keep-symbol='f*' %t.o -o %t.ksym.4.o
+# RUN: cmp %t.ksym.3.o %t.ksym.4.o
+
+## --localize-symbol:
+## Note: Use %t.globsym.2.o instead of %t.o since those symbols are global.
+# RUN: llvm-objcopy --localize-symbol='*' %t.globsym.2.o %t.localsym.1.o
+# RUN: llvm-readobj --symbols %t.localsym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,GLOBAL,FOO-SYM,BAR-SYM
+
+# RUN: llvm-objcopy -w --localize-symbol='*' %t.globsym.2.o %t.localsym.2.o
+# RUN: llvm-readobj --symbols %t.localsym.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,LOCAL,FOO-SYM,BAR-SYM
+
+## --strip-symbol:
+# RUN: llvm-objcopy --strip-symbol='f*' %t.o %t.stripsym.1.o
+# RUN: llvm-readobj --symbols %t.stripsym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO-SYM,BAR-SYM
+# RUN: llvm-strip --strip-symbol='f*' %t.o -o %t.stripsym.2.o
+# RUN: cmp %t.stripsym.1.o %t.stripsym.2.o
+
+# RUN: llvm-objcopy -w --strip-symbol='f*' %t.o %t.stripsym.3.o
+# RUN: llvm-readobj --symbols %t.stripsym.3.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,BAR-SYM
+# RUN: llvm-strip -w --strip-symbol='f*' %t.o -o %t.stripsym.4.o
+# RUN: cmp %t.stripsym.3.o %t.stripsym.4.o
+
+## --strip-unneeded-symbol:
+# RUN: llvm-objcopy --strip-unneeded-symbol='f*' %t.o %t.stripunsym.1.o
+# RUN: llvm-readobj --symbols %t.stripunsym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO-SYM,BAR-SYM
+
+# RUN: llvm-objcopy -w --strip-unneeded-symbol='f*' %t.o %t.stripunsym.2.o
+# RUN: llvm-readobj --symbols %t.stripunsym.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,BAR-SYM
+
+## --weaken-symbol:
+## Note: Use %t.globsym.2.o instead of %t.o since those symbols are global.
+# RUN: llvm-objcopy --weaken-symbol='*' %t.globsym.2.o %t.weaksym.1.o
+# RUN: llvm-readobj --symbols %t.weaksym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,GLOBAL,FOO-SYM,BAR-SYM
+
+# RUN: llvm-objcopy -w --weaken-symbol='*' %t.globsym.2.o %t.weaksym.2.o
+# RUN: llvm-readobj --symbols %t.weaksym.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,WEAK,FOO-SYM,BAR-SYM
+
+## --keep-global-symbol:
+## Note: Use %t.globsym.2.o instead of %t.o since those symbols are global.
+# RUN: llvm-objcopy --keep-global-symbol='*' %t.globsym.2.o %t.keepgsym.1.o
+# RUN: llvm-readobj --symbols %t.keepgsym.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,LOCAL,FOO-SYM,BAR-SYM
+
+# RUN: llvm-objcopy -w --keep-global-symbol='*' %t.globsym.2.o %t.keepgsym.2.o
+# RUN: llvm-readobj --symbols %t.keepgsym.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:       --check-prefixes=CHECK,GLOBAL,FOO-SYM,BAR-SYM
+
+## Check that -w is accepted as an alias for --wildcard.
+# RUN: llvm-objcopy --wildcard --keep-global-symbol='*' %t.globsym.2.o %t.keepgsym.3.o
+# RUN: cmp %t.keepgsym.2.o %t.keepgsym.3.o
+
+# CHECK:   LoadName:
+# CHECK:   Name: (0)
+
+# FOO-SEC: Name: .foo
+
+# FOO-SYM: Name: foo
+# GLOBAL:  Binding: Global
+# WEAK:    Binding: Weak
+# LOCAL:   Binding: Local
+
+# BAR-SEC: Name: .bar
+# BAR-SYM: Name: bar
+# GLOBAL:  Binding: Global
+# WEAK:    Binding: Weak
+# LOCAL:   Binding: Local
+
+# SEC:     Name: .shstrtab
+
+!ELF
+FileHeader:
+  Class:     ELFCLASS64
+  Data:      ELFDATA2LSB
+  Type:      ET_REL
+  Machine:   EM_X86_64
+Sections:
+  - Name:    .foo
+    Type:    SHT_PROGBITS
+  - Name:    .bar
+    Type:    SHT_PROGBITS
+Symbols:
+  - Name:    foo
+    Type:    STT_FUNC
+    Section: .foo
+  - Name:    bar
+    Type:    STT_FUNC
+    Section: .foo
diff --git a/test/tools/llvm-objcopy/ELF/wildcard-syntax.test b/test/tools/llvm-objcopy/ELF/wildcard-syntax.test
new file mode 100644
index 0000000..0564289
--- /dev/null
+++ b/test/tools/llvm-objcopy/ELF/wildcard-syntax.test
@@ -0,0 +1,149 @@
+## This test checks that llvm-objcopy accepts glob (or "shell wildcard") syntax
+## for the --wildcard (-w) flag correctly.
+
+# RUN: yaml2obj --docnum=1 %s > %t.o
+
+## * matches all characters.
+# RUN: llvm-objcopy --remove-section='.f*' %t.o %t.glob.o
+# RUN: llvm-readobj --sections %t.glob.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,BAR
+
+## Glob matches are full matches. ("*a" does not match ".bar").
+# RUN: llvm-objcopy --remove-section='*a' %t.o %t.full.o
+# RUN: llvm-readobj --sections %t.full.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO,BAR
+
+## ? matches one character.
+# RUN: llvm-objcopy --remove-section='.b?r' %t.o %t.question.o
+# RUN: llvm-readobj --sections %t.question.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO
+
+## ! (as a leading character) prevents matches, and is not dependent on
+## ordering.
+# RUN: llvm-objcopy --remove-section='.???' --remove-section='!.f*' \
+# RUN:   %t.o %t.negmatch1.o
+# RUN: llvm-readobj --sections %t.negmatch1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO
+# RUN: llvm-objcopy --remove-section='!.f*' --remove-section='.???' \
+# RUN:   %t.o %t.negmatch2.o
+# RUN: llvm-readobj --sections %t.negmatch2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO
+# RUN: llvm-objcopy --remove-section='.???' --remove-section='!.f*' \
+# RUN:   --remove-section='.???' %t.o %t.negmatch3.o
+# RUN: llvm-readobj --sections %t.negmatch3.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO
+
+## [a-z] matches a range of characters.
+# RUN: llvm-objcopy --remove-section='.[a-c][a-a][q-s]' %t.o %t.range.o
+# RUN: llvm-readobj --sections %t.range.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,FOO
+
+## [^a-z] or [!a-z] match a negated range of characters.
+# RUN: llvm-objcopy --remove-section='.[^x]oo' %t.o %t.negrange.1.o
+# RUN: llvm-readobj --sections %t.negrange.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,BAR
+# RUN: llvm-objcopy --remove-section='.[!x]oo' %t.o %t.negrange.2.o
+# RUN: llvm-readobj --sections %t.negrange.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: --check-prefixes=CHECK,BAR
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+Sections:
+  - Name: .foo
+    Type: SHT_PROGBITS
+  - Name: .bar
+    Type: SHT_PROGBITS
+
+## Use a separate test file with special characters for the following tests.
+
+# RUN: yaml2obj --docnum=2 %s > %t.special.o
+
+## \ escapes wildcard characters.
+# RUN: llvm-objcopy --remove-section='\*' %t.special.o %t.escape.1.o
+# RUN: llvm-readobj --sections %t.escape.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,DOT,QUESTION,LEFT-BRACKET,RIGHT-BRACKET,INVALID-GLOB,Z,XYZ,FOO
+# RUN: llvm-objcopy --remove-section='\?' %t.special.o %t.escape.2.o
+# RUN: llvm-readobj --sections %t.escape.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,DOT,ASTERISK,LEFT-BRACKET,RIGHT-BRACKET,INVALID-GLOB,Z,XYZ,FOO
+
+## Special characters are not treated like regular expression characters.
+# RUN: llvm-objcopy --remove-section='.' %t.special.o %t.dot.o
+# RUN: llvm-readobj --sections %t.dot.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,ASTERISK,QUESTION,LEFT-BRACKET,RIGHT-BRACKET,INVALID-GLOB,Z,XYZ,FOO
+
+## Special characters in character classes are treated literally.
+## [*] should not get expanded to [.*], which would match both '.' and '*'
+# RUN: llvm-objcopy --remove-section='[*]' %t.special.o %t.class.1.o
+# RUN: llvm-readobj --sections %t.class.1.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,DOT,QUESTION,LEFT-BRACKET,RIGHT-BRACKET,INVALID-GLOB,Z,XYZ,FOO
+
+## ] doesn't close the character class as a first character. This glob matches
+## a single character which is one of ']xyz'. ']' and 'z' are removed, and more explicitly,
+## section 'xyz]' is not removed, i.e. the glob is not interpreted as "an empty
+## character class followed by 'xyz]'"
+# RUN: llvm-objcopy --remove-section='[]xyz]' %t.special.o %t.class.2.o
+# RUN: llvm-readobj --sections %t.class.2.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,DOT,ASTERISK,QUESTION,LEFT-BRACKET,INVALID-GLOB,XYZ,FOO
+
+## An invalid glob expression is interpreted as a literal instead.
+# RUN: llvm-objcopy --remove-section='][]' %t.special.o %t.class.3.o 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=WARN
+# RUN: llvm-readobj --sections %t.class.3.o \
+# RUN:   | FileCheck %s --implicit-check-not=Name: \
+# RUN:   --check-prefixes=CHECK,DOT,ASTERISK,QUESTION,LEFT-BRACKET,RIGHT-BRACKET,Z,XYZ,FOO
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+Sections:
+  - Name: .
+    Type: SHT_PROGBITS
+  - Name: '*'
+    Type: SHT_PROGBITS
+  - Name: '?'
+    Type: SHT_PROGBITS
+  - Name: '['
+    Type: SHT_PROGBITS
+  - Name: ']'
+    Type: SHT_PROGBITS
+  - Name: '][]'
+    Type: SHT_PROGBITS
+  - Name: z
+    Type: SHT_PROGBITS
+  - Name: 'xyz]'
+    Type: SHT_PROGBITS
+  - Name: '[]xyz]'
+    Type: SHT_PROGBITS
+  - Name: .foo
+    Type: SHT_PROGBITS
+
+# WARN: warning: invalid glob pattern: ][]
+
+# CHECK: LoadName:
+# CHECK:         Name: (0)
+# DOT:           Name: .
+# ASTERISK:      Name: *
+# QUESTION:      Name: ?
+# LEFT-BRACKET:  Name: [
+# RIGHT-BRACKET: Name: ]
+# INVALID-GLOB:  Name: ][]
+# Z:             Name: z
+# XYZ:           Name: xyz]
+# XYZ:           Name: []xyz]
+# FOO:           Name: .foo
+# BAR:           Name: .bar
+# CHECK:         Name: .symtab
+# CHECK:         Name: .strtab
+# CHECK:         Name: .shstrtab
diff --git a/tools/llvm-objcopy/CommonOpts.td b/tools/llvm-objcopy/CommonOpts.td
index 597163e..e8c092b 100644
--- a/tools/llvm-objcopy/CommonOpts.td
+++ b/tools/llvm-objcopy/CommonOpts.td
@@ -111,3 +111,13 @@
 def V : Flag<["-"], "V">,
         Alias<version>,
         HelpText<"Alias for --version">;
+
+def wildcard
+    : Flag<["--"], "wildcard">,
+      HelpText<"Allow wildcard syntax for symbol-related flags. Incompatible "
+               "with --regex. Allows using '*' to match any number of "
+               "characters, '?' to match any single character, '\' to escape "
+               "special characters, and '[]' to define character classes. "
+               "Wildcards beginning with '!' will prevent a match, for example "
+               "\"-N '*' -N '!x'\" will strip all symbols except for \"x\".">;
+def w : Flag<["-"], "w">, Alias<wildcard>, HelpText<"Alias for --wildcard">;
diff --git a/tools/llvm-objcopy/CopyConfig.cpp b/tools/llvm-objcopy/CopyConfig.cpp
index 952b3a7..c805631 100644
--- a/tools/llvm-objcopy/CopyConfig.cpp
+++ b/tools/llvm-objcopy/CopyConfig.cpp
@@ -260,8 +260,10 @@
   return {TargetInfo{Format, MI}};
 }
 
-static Error addSymbolsFromFile(NameMatcher &Symbols, BumpPtrAllocator &Alloc,
-                                StringRef Filename, bool UseRegex) {
+static Error
+addSymbolsFromFile(NameMatcher &Symbols, BumpPtrAllocator &Alloc,
+                   StringRef Filename, MatchStyle MS,
+                   llvm::function_ref<Error(Error)> ErrorCallback) {
   StringSaver Saver(Alloc);
   SmallVector<StringRef, 16> Lines;
   auto BufOrErr = MemoryBuffer::getFile(Filename);
@@ -274,21 +276,46 @@
     // it's not empty.
     auto TrimmedLine = Line.split('#').first.trim();
     if (!TrimmedLine.empty())
-      Symbols.addMatcher({Saver.save(TrimmedLine), UseRegex});
+      if (Error E = Symbols.addMatcher(NameOrPattern::create(
+              Saver.save(TrimmedLine), MS, ErrorCallback)))
+        return E;
   }
 
   return Error::success();
 }
 
-NameOrRegex::NameOrRegex(StringRef Pattern, bool IsRegex) {
-  if (!IsRegex) {
-    Name = Pattern;
-    return;
-  }
+Expected<NameOrPattern>
+NameOrPattern::create(StringRef Pattern, MatchStyle MS,
+                      llvm::function_ref<Error(Error)> ErrorCallback) {
+  switch (MS) {
+  case MatchStyle::Literal:
+    return NameOrPattern(Pattern);
+  case MatchStyle::Wildcard: {
+    SmallVector<char, 32> Data;
+    bool IsPositiveMatch = true;
+    if (Pattern[0] == '!') {
+      IsPositiveMatch = false;
+      Pattern = Pattern.drop_front();
+    }
+    Expected<GlobPattern> GlobOrErr = GlobPattern::create(Pattern);
 
-  SmallVector<char, 32> Data;
-  R = std::make_shared<Regex>(
-      ("^" + Pattern.ltrim('^').rtrim('$') + "$").toStringRef(Data));
+    // If we couldn't create it as a glob, report the error, but try again with
+    // a literal if the error reporting is non-fatal.
+    if (!GlobOrErr) {
+      if (Error E = ErrorCallback(GlobOrErr.takeError()))
+        return std::move(E);
+      return create(Pattern, MatchStyle::Literal, ErrorCallback);
+    }
+
+    return NameOrPattern(std::make_shared<GlobPattern>(*GlobOrErr),
+                         IsPositiveMatch);
+  }
+  case MatchStyle::Regex: {
+    SmallVector<char, 32> Data;
+    return NameOrPattern(std::make_shared<Regex>(
+        ("^" + Pattern.ltrim('^').rtrim('$') + "$").toStringRef(Data)));
+  }
+  }
 }
 
 static Error addSymbolsToRenameFromFile(StringMap<StringRef> &SymbolsToRename,
@@ -338,7 +365,9 @@
 // ParseObjcopyOptions returns the config and sets the input arguments. If a
 // help flag is set then ParseObjcopyOptions will print the help messege and
 // exit.
-Expected<DriverConfig> parseObjcopyOptions(ArrayRef<const char *> ArgsArr) {
+Expected<DriverConfig>
+parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
+                    llvm::function_ref<Error(Error)> ErrorCallback) {
   DriverConfig DC;
   ObjcopyOptTable T;
   unsigned MissingArgumentIndex, MissingArgumentCount;
@@ -387,7 +416,18 @@
         errc::invalid_argument,
         "--target cannot be used with --input-target or --output-target");
 
-  bool UseRegex = InputArgs.hasArg(OBJCOPY_regex);
+  if (InputArgs.hasArg(OBJCOPY_regex) && InputArgs.hasArg(OBJCOPY_wildcard))
+    return createStringError(errc::invalid_argument,
+                             "--regex and --wildcard are incompatible");
+
+  MatchStyle SectionMatchStyle = InputArgs.hasArg(OBJCOPY_regex)
+                                     ? MatchStyle::Regex
+                                     : MatchStyle::Wildcard;
+  MatchStyle SymbolMatchStyle = InputArgs.hasArg(OBJCOPY_regex)
+                                    ? MatchStyle::Regex
+                                    : InputArgs.hasArg(OBJCOPY_wildcard)
+                                          ? MatchStyle::Wildcard
+                                          : MatchStyle::Literal;
   StringRef InputFormat, OutputFormat;
   if (InputArgs.hasArg(OBJCOPY_target)) {
     InputFormat = InputArgs.getLastArgValue(OBJCOPY_target);
@@ -541,11 +581,17 @@
   }
 
   for (auto Arg : InputArgs.filtered(OBJCOPY_remove_section))
-    Config.ToRemove.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.ToRemove.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SectionMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_keep_section))
-    Config.KeepSection.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.KeepSection.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SectionMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_only_section))
-    Config.OnlySection.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.OnlySection.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SectionMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_add_section)) {
     StringRef ArgValue(Arg->getValue());
     if (!ArgValue.contains('='))
@@ -583,46 +629,68 @@
   if (Config.DiscardMode == DiscardType::All)
     Config.StripDebug = true;
   for (auto Arg : InputArgs.filtered(OBJCOPY_localize_symbol))
-    Config.SymbolsToLocalize.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToLocalize.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_localize_symbols))
     if (Error E = addSymbolsFromFile(Config.SymbolsToLocalize, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_keep_global_symbol))
-    Config.SymbolsToKeepGlobal.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToKeepGlobal.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_keep_global_symbols))
     if (Error E = addSymbolsFromFile(Config.SymbolsToKeepGlobal, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_globalize_symbol))
-    Config.SymbolsToGlobalize.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToGlobalize.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_globalize_symbols))
     if (Error E = addSymbolsFromFile(Config.SymbolsToGlobalize, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_weaken_symbol))
-    Config.SymbolsToWeaken.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToWeaken.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_weaken_symbols))
     if (Error E = addSymbolsFromFile(Config.SymbolsToWeaken, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_strip_symbol))
-    Config.SymbolsToRemove.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToRemove.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_strip_symbols))
     if (Error E = addSymbolsFromFile(Config.SymbolsToRemove, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_strip_unneeded_symbol))
-    Config.UnneededSymbolsToRemove.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E =
+            Config.UnneededSymbolsToRemove.addMatcher(NameOrPattern::create(
+                Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_strip_unneeded_symbols))
     if (Error E = addSymbolsFromFile(Config.UnneededSymbolsToRemove, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+                                     Arg->getValue(), SymbolMatchStyle,
+                                     ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbol))
-    Config.SymbolsToKeep.addMatcher({Arg->getValue(), UseRegex});
+    if (Error E = Config.SymbolsToKeep.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbols))
-    if (Error E = addSymbolsFromFile(Config.SymbolsToKeep, DC.Alloc,
-                                     Arg->getValue(), UseRegex))
+    if (Error E =
+            addSymbolsFromFile(Config.SymbolsToKeep, DC.Alloc, Arg->getValue(),
+                               SymbolMatchStyle, ErrorCallback))
       return std::move(E);
   for (auto Arg : InputArgs.filtered(OBJCOPY_add_symbol))
     Config.SymbolsToAdd.push_back(Arg->getValue());
@@ -688,7 +756,7 @@
 // exit.
 Expected<DriverConfig>
 parseStripOptions(ArrayRef<const char *> ArgsArr,
-                  std::function<Error(Error)> ErrorCallback) {
+                  llvm::function_ref<Error(Error)> ErrorCallback) {
   StripOptTable T;
   unsigned MissingArgumentIndex, MissingArgumentCount;
   llvm::opt::InputArgList InputArgs =
@@ -726,7 +794,17 @@
         "multiple input files cannot be used in combination with -o");
 
   CopyConfig Config;
-  bool UseRegexp = InputArgs.hasArg(STRIP_regex);
+
+  if (InputArgs.hasArg(STRIP_regex) && InputArgs.hasArg(STRIP_wildcard))
+    return createStringError(errc::invalid_argument,
+                             "--regex and --wildcard are incompatible");
+  MatchStyle SectionMatchStyle =
+      InputArgs.hasArg(STRIP_regex) ? MatchStyle::Regex : MatchStyle::Wildcard;
+  MatchStyle SymbolMatchStyle = InputArgs.hasArg(STRIP_regex)
+                                    ? MatchStyle::Regex
+                                    : InputArgs.hasArg(STRIP_wildcard)
+                                          ? MatchStyle::Wildcard
+                                          : MatchStyle::Literal;
   Config.AllowBrokenLinks = InputArgs.hasArg(STRIP_allow_broken_links);
   Config.StripDebug = InputArgs.hasArg(STRIP_strip_debug);
 
@@ -744,16 +822,24 @@
   Config.KeepFileSymbols = InputArgs.hasArg(STRIP_keep_file_symbols);
 
   for (auto Arg : InputArgs.filtered(STRIP_keep_section))
-    Config.KeepSection.addMatcher({Arg->getValue(), UseRegexp});
+    if (Error E = Config.KeepSection.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SectionMatchStyle, ErrorCallback)))
+      return std::move(E);
 
   for (auto Arg : InputArgs.filtered(STRIP_remove_section))
-    Config.ToRemove.addMatcher({Arg->getValue(), UseRegexp});
+    if (Error E = Config.ToRemove.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SectionMatchStyle, ErrorCallback)))
+      return std::move(E);
 
   for (auto Arg : InputArgs.filtered(STRIP_strip_symbol))
-    Config.SymbolsToRemove.addMatcher({Arg->getValue(), UseRegexp});
+    if (Error E = Config.SymbolsToRemove.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
 
   for (auto Arg : InputArgs.filtered(STRIP_keep_symbol))
-    Config.SymbolsToKeep.addMatcher({Arg->getValue(), UseRegexp});
+    if (Error E = Config.SymbolsToKeep.addMatcher(NameOrPattern::create(
+            Arg->getValue(), SymbolMatchStyle, ErrorCallback)))
+      return std::move(E);
 
   if (!InputArgs.hasArg(STRIP_no_strip_all) && !Config.StripDebug &&
       !Config.StripUnneeded && Config.DiscardMode == DiscardType::None &&
diff --git a/tools/llvm-objcopy/CopyConfig.h b/tools/llvm-objcopy/CopyConfig.h
index 745af0ce..55a55d3 100644
--- a/tools/llvm-objcopy/CopyConfig.h
+++ b/tools/llvm-objcopy/CopyConfig.h
@@ -19,6 +19,7 @@
 #include "llvm/Object/ELFTypes.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/GlobPattern.h"
 #include "llvm/Support/Regex.h"
 // Necessary for llvm::DebugCompressionType::None
 #include "llvm/Target/TargetOptions.h"
@@ -88,28 +89,58 @@
   Locals, // --discard-locals (-X)
 };
 
-class NameOrRegex {
+enum class MatchStyle {
+  Literal,  // Default for symbols.
+  Wildcard, // Default for sections, or enabled with --wildcard (-w).
+  Regex,    // Enabled with --regex.
+};
+
+class NameOrPattern {
   StringRef Name;
   // Regex is shared between multiple CopyConfig instances.
   std::shared_ptr<Regex> R;
+  std::shared_ptr<GlobPattern> G;
+  bool IsPositiveMatch = true;
+
+  NameOrPattern(StringRef N) : Name(N) {}
+  NameOrPattern(std::shared_ptr<Regex> R) : R(R) {}
+  NameOrPattern(std::shared_ptr<GlobPattern> G, bool IsPositiveMatch)
+      : G(G), IsPositiveMatch(IsPositiveMatch) {}
 
 public:
-  NameOrRegex(StringRef Pattern, bool IsRegex);
-  bool operator==(StringRef S) const { return R ? R->match(S) : Name == S; }
+  // ErrorCallback is used to handle recoverable errors. An Error returned
+  // by the callback aborts the parsing and is then returned by this function.
+  static Expected<NameOrPattern>
+  create(StringRef Pattern, MatchStyle MS,
+         llvm::function_ref<Error(Error)> ErrorCallback);
+
+  bool isPositiveMatch() const { return IsPositiveMatch; }
+  bool operator==(StringRef S) const {
+    return R ? R->match(S) : G ? G->match(S) : Name == S;
+  }
   bool operator!=(StringRef S) const { return !operator==(S); }
 };
 
 // Matcher that checks symbol or section names against the command line flags
 // provided for that option.
 class NameMatcher {
-  std::vector<NameOrRegex> Matchers;
+  std::vector<NameOrPattern> PosMatchers;
+  std::vector<NameOrPattern> NegMatchers;
 
 public:
-  void addMatcher(NameOrRegex Matcher) {
-    Matchers.push_back(std::move(Matcher));
+  Error addMatcher(Expected<NameOrPattern> Matcher) {
+    if (!Matcher)
+      return Matcher.takeError();
+    if (Matcher->isPositiveMatch())
+      PosMatchers.push_back(std::move(*Matcher));
+    else
+      NegMatchers.push_back(std::move(*Matcher));
+    return Error::success();
   }
-  bool matches(StringRef S) const { return is_contained(Matchers, S); }
-  bool empty() const { return Matchers.empty(); }
+  bool matches(StringRef S) const {
+    return is_contained(PosMatchers, S) && !is_contained(NegMatchers, S);
+  }
+  bool empty() const { return PosMatchers.empty() && NegMatchers.empty(); }
 };
 
 // Configuration for copying/stripping a single file.
@@ -214,8 +245,11 @@
 
 // ParseObjcopyOptions returns the config and sets the input arguments. If a
 // help flag is set then ParseObjcopyOptions will print the help messege and
-// exit.
-Expected<DriverConfig> parseObjcopyOptions(ArrayRef<const char *> ArgsArr);
+// exit. ErrorCallback is used to handle recoverable errors. An Error returned
+// by the callback aborts the parsing and is then returned by this function.
+Expected<DriverConfig>
+parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
+                    llvm::function_ref<Error(Error)> ErrorCallback);
 
 // ParseStripOptions returns the config and sets the input arguments. If a
 // help flag is set then ParseStripOptions will print the help messege and
@@ -223,7 +257,7 @@
 // by the callback aborts the parsing and is then returned by this function.
 Expected<DriverConfig>
 parseStripOptions(ArrayRef<const char *> ArgsArr,
-                  std::function<Error(Error)> ErrorCallback);
+                  llvm::function_ref<Error(Error)> ErrorCallback);
 
 } // namespace objcopy
 } // namespace llvm
diff --git a/tools/llvm-objcopy/llvm-objcopy.cpp b/tools/llvm-objcopy/llvm-objcopy.cpp
index db6d751..a68210f 100644
--- a/tools/llvm-objcopy/llvm-objcopy.cpp
+++ b/tools/llvm-objcopy/llvm-objcopy.cpp
@@ -335,7 +335,7 @@
 
   Expected<DriverConfig> DriverConfig =
       IsStrip ? parseStripOptions(Args, reportWarning)
-              : parseObjcopyOptions(Args);
+              : parseObjcopyOptions(Args, reportWarning);
   if (!DriverConfig) {
     logAllUnhandledErrors(DriverConfig.takeError(),
                           WithColor::error(errs(), ToolName));