diff --git a/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-aarch64.test b/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-aarch64.test
new file mode 100644
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-aarch64.test
@@ -0,0 +1,53 @@
+## Check that llvm-objcopy preserves AArch64 mapping symbols for relocatable files.
+# RUN: yaml2obj %s -o %t.o -DTYPE=REL
+# RUN: llvm-objcopy --strip-unneeded %t.o %t1.o
+# RUN: llvm-nm -j --special-syms %t1.o | FileCheck %s --match-full-lines
+# RUN: llvm-objcopy --discard-all %t.o %t2.o
+# RUN: llvm-nm -j --special-syms %t2.o | FileCheck %s --match-full-lines
+
+# CHECK:      $d
+# CHECK-NEXT: $d.d
+# CHECK-NEXT: $x
+# CHECK-NEXT: $x.x
+
+## A mapping symbol can be deleted if specified explicitly.
+# RUN: llvm-objcopy -w -N '$d*' %t.o %t3.o
+# RUN: llvm-nm --special-syms %t3.o | FileCheck /dev/null --implicit-check-not='$d'
+
+## Mapping symbols are not preserved for executable files
+# RUN: yaml2obj %s -o %t.exec -DTYPE=EXEC
+# RUN: llvm-objcopy --strip-unneeded %t.exec %t1.exec
+# RUN: llvm-nm --special-syms %t1.exec | count 0
+
+# RUN: yaml2obj %s -o %t.so -DTYPE=DYN
+# RUN: llvm-objcopy --strip-unneeded %t.so %t1.so
+# RUN: llvm-nm --special-syms %t1.so | count 0
+
+!ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_[[TYPE]]
+  Machine:  EM_AARCH64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+Symbols:
+  - Name:     $a
+    Section:  .text
+  - Name:     $d
+    Section:  .text
+  - Name:     $dd
+    Section:  .text
+  - Name:     $d.d
+    Section:  .text
+  - Name:     $m
+    Section:  .text
+  - Name:     $t.t
+    Section:  .text
+  - Name:     $x
+    Section:  .text
+  - Name:     $xx
+    Section:  .text
+  - Name:     $x.x
+    Section:  .text
diff --git a/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-arm.test b/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-arm.test
new file mode 100644
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/ELF/strip-unneeded-arm.test
@@ -0,0 +1,60 @@
+## Check that llvm-objcopy preserves ARM mapping symbols for relocatable files.
+# RUN: yaml2obj %s -o %t.o -DTYPE=REL
+# RUN: llvm-objcopy --strip-unneeded %t.o %t1.o
+# RUN: llvm-nm -j --special-syms %t1.o | FileCheck %s --match-full-lines
+# RUN: llvm-objcopy --discard-all %t.o %t2.o
+# RUN: llvm-nm -j --special-syms %t2.o | FileCheck %s --match-full-lines
+
+# CHECK:      $a
+# CHECK-NEXT: $a.a
+# CHECK-NEXT: $d
+# CHECK-NEXT: $d.d
+# CHECK-NEXT: $t
+# CHECK-NEXT: $t.t
+# CHECK-NOT:  $x
+
+## A mapping symbol can be deleted if specified explicitly.
+# RUN: llvm-objcopy -w -N '$d*' %t.o %t3.o
+# RUN: llvm-nm --special-syms %t3.o | FileCheck /dev/null --implicit-check-not='$d'
+
+## Mapping symbols are not preserved for executable files
+# RUN: yaml2obj %s -o %t.exec -DTYPE=EXEC
+# RUN: llvm-objcopy --strip-unneeded %t.exec %t1.exec
+# RUN: llvm-nm --special-syms %t1.exec | count 0
+
+# RUN: yaml2obj %s -o %t.so -DTYPE=DYN
+# RUN: llvm-objcopy --strip-unneeded %t.so %t1.so
+# RUN: llvm-nm --special-syms %t1.so | count 0
+
+!ELF
+FileHeader:
+  Class:    ELFCLASS32
+  Data:     ELFDATA2LSB
+  Type:     ET_[[TYPE]]
+  Machine:  EM_ARM
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+Symbols:
+  - Name:     $a
+    Section:  .text
+  - Name:     $aa
+    Section:  .text
+  - Name:     $a.a
+    Section:  .text
+  - Name:     $d
+    Section:  .text
+  - Name:     $dd
+    Section:  .text
+  - Name:     $d.d
+    Section:  .text
+  - Name:     $m
+    Section:  .text
+  - Name:     $t
+    Section:  .text
+  - Name:     $tt
+    Section:  .text
+  - Name:     $t.t
+    Section:  .text
+  - Name:     $x
+    Section:  .text
diff --git a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
--- a/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
+++ b/llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp
@@ -227,6 +227,41 @@
   return Obj.replaceSections(FromTo);
 }
 
+static bool isAArch64MappingSymbol(const Symbol &Sym) {
+  if (Sym.Binding != STB_LOCAL || Sym.Type != STT_NOTYPE ||
+      Sym.getShndx() == SHN_UNDEF)
+    return false;
+  StringRef Name = Sym.Name;
+  if (!Name.consume_front("$x") && !Name.consume_front("$d"))
+    return false;
+  return Name.empty() || Name.startswith(".");
+}
+
+static bool isArmMappingSymbol(const Symbol &Sym) {
+  if (Sym.Binding != STB_LOCAL || Sym.Type != STT_NOTYPE ||
+      Sym.getShndx() == SHN_UNDEF)
+    return false;
+  StringRef Name = Sym.Name;
+  if (!Name.consume_front("$a") && !Name.consume_front("$d") &&
+      !Name.consume_front("$t"))
+    return false;
+  return Name.empty() || Name.startswith(".");
+}
+
+// Check if the symbol should be preserved because it is required by ABI.
+static bool isRequiredByABISymbol(const Object &Obj, const Symbol &Sym) {
+  switch (Obj.Machine) {
+  case EM_AARCH64:
+    // Mapping symbols should be preserved for a relocatable object file.
+    return Obj.isRelocatable() && isAArch64MappingSymbol(Sym);
+  case EM_ARM:
+    // Mapping symbols should be preserved for a relocatable object file.
+    return Obj.isRelocatable() && isArmMappingSymbol(Sym);
+  default:
+    return false;
+  }
+}
+
 static bool isUnneededSymbol(const Symbol &Sym) {
   return !Sym.Referenced &&
          (Sym.Binding == STB_LOCAL || Sym.getShndx() == SHN_UNDEF) &&
@@ -297,20 +332,23 @@
         (ELFConfig.KeepFileSymbols && Sym.Type == STT_FILE))
       return false;
 
-    if ((Config.DiscardMode == DiscardType::All ||
-         (Config.DiscardMode == DiscardType::Locals &&
-          StringRef(Sym.Name).startswith(".L"))) &&
-        Sym.Binding == STB_LOCAL && Sym.getShndx() != SHN_UNDEF &&
-        Sym.Type != STT_FILE && Sym.Type != STT_SECTION)
+    if (Config.SymbolsToRemove.matches(Sym.Name))
       return true;
 
     if (Config.StripAll || Config.StripAllGNU)
       return true;
 
+    if (isRequiredByABISymbol(Obj, Sym))
+      return false;
+
     if (Config.StripDebug && Sym.Type == STT_FILE)
       return true;
 
-    if (Config.SymbolsToRemove.matches(Sym.Name))
+    if ((Config.DiscardMode == DiscardType::All ||
+         (Config.DiscardMode == DiscardType::Locals &&
+          StringRef(Sym.Name).startswith(".L"))) &&
+        Sym.Binding == STB_LOCAL && Sym.getShndx() != SHN_UNDEF &&
+        Sym.Type != STT_FILE && Sym.Type != STT_SECTION)
       return true;
 
     if ((Config.StripUnneeded ||