[llvm] Make llvm-nm's --export-symbols option work for ELF (PR #84379)

via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 18 09:40:31 PDT 2024


https://github.com/bd1976bris updated https://github.com/llvm/llvm-project/pull/84379

>From 3deed241ea030648525c930239479c33c98816ec Mon Sep 17 00:00:00 2001
From: Ben Dunbobbin <Ben.Dunbobbin at sony.com>
Date: Thu, 7 Mar 2024 20:50:06 +0000
Subject: [PATCH 1/2] Make llvm-nm's --export-symbols option work for ELF
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The --export-symbols option..

–help:
  –export-symbols Export symbol list for all inputs\

doc:–export-symbols
  Print sorted symbols with their visibility (if applicable), with duplicates removed.

.. was introduced in to llvm-nm for an AIX specific usecase. For non-XCOFF target it simply
 lists all symbols which seems incorrect given the option name and intended functionality.

 This PR is for implementing the option for ELF targets so that it lists the set of unique
 exports. This is the set of non-local definitions that have `default` or `protected` ELF
 visibility.
---
 llvm/lib/Object/ModuleSymbolTable.cpp       |  15 +++
 llvm/test/tools/llvm-nm/export-symbols.test | 108 ++++++++++++++++++++
 llvm/tools/llvm-nm/llvm-nm.cpp              |  16 +--
 3 files changed, 133 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/tools/llvm-nm/export-symbols.test

diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp b/llvm/lib/Object/ModuleSymbolTable.cpp
index 07f76688fa43e7..266df68d42b7cd 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -199,6 +199,19 @@ void ModuleSymbolTable::printSymbolName(raw_ostream &OS, Symbol S) const {
   Mang.getNameWithPrefix(OS, GV, false);
 }
 
+static bool isExportedToOtherDSO(const Triple TT, GlobalValue &GV) {
+  if (TT.isOSBinFormatELF())
+    // A defintition is exported if its non-local, and its visibility
+    // is either DEFAULT or PROTECTED. All other symbols are not exported.
+    return !GV.isDeclarationForLinker() && !GV.hasLocalLinkage() &&
+           (GV.hasDefaultVisibility() || GV.hasProtectedVisibility());
+  else if (TT.isOSBinFormatXCOFF())
+    return true;
+
+  // TODO: Add support for other file formats
+  return false;
+}
+
 uint32_t ModuleSymbolTable::getSymbolFlags(Symbol S) const {
   if (isa<AsmSymbol *>(S))
     return cast<AsmSymbol *>(S)->second;
@@ -214,6 +227,8 @@ uint32_t ModuleSymbolTable::getSymbolFlags(Symbol S) const {
     if (GVar->isConstant())
       Res |= BasicSymbolRef::SF_Const;
   }
+  if (isExportedToOtherDSO(Triple(FirstMod->getTargetTriple()), *GV))
+    Res |= BasicSymbolRef::SF_Exported;
   if (const GlobalObject *GO = GV->getAliaseeObject())
     if (isa<Function>(GO) || isa<GlobalIFunc>(GO))
       Res |= BasicSymbolRef::SF_Executable;
diff --git a/llvm/test/tools/llvm-nm/export-symbols.test b/llvm/test/tools/llvm-nm/export-symbols.test
new file mode 100644
index 00000000000000..58df4c91ba8d29
--- /dev/null
+++ b/llvm/test/tools/llvm-nm/export-symbols.test
@@ -0,0 +1,108 @@
+## Test the "--export-symbols" option.
+## The option prints out a sorted list of the unique exports from the input files.
+
+# RUN: rm -rf %t
+# RUN: split-file %s %t
+
+# RUN: llvm-as %t/1.ll -o %t/1.bc
+# RUN: llvm-as %t/2.ll -o %t/2.bc
+# RUN: llc -filetype=obj -mtriple=x86_64-pc-linux %t/1.ll -o %t/1.o
+# RUN: llc -filetype=obj -mtriple=x86_64-pc-linux %t/2.ll -o %t/2.o
+
+## Test the following cases:
+## - Show that non-exports are ignored.
+## - Show that only unique exports (with a different name or visibility) are printed.
+## - Show that XCOFF special cases are not handled specially.
+## - Show that the --no-weak option is honoured.
+## - Show that the XCOFF specific --no-rsrc is ignored.
+# RUN: llvm-nm --export-symbols %t/1.bc | FileCheck --check-prefixes=COMMON,WEAK %s --implicit-check-not={{.}}
+# RUN: llvm-nm --export-symbols %t/1.o | FileCheck --check-prefixes=COMMON,WEAK %s --implicit-check-not={{.}}
+# RUN: llvm-nm --export-symbols %t/1.bc %t/2.bc | FileCheck --check-prefixes=COMMON,WEAK,HIDDEN %s --implicit-check-not={{.}}
+# RUN: llvm-nm --export-symbols %t/1.o %t/2.o | FileCheck --check-prefixes=COMMON,WEAK,HIDDEN %s --implicit-check-not={{.}}
+# RUN: llvm-nm --export-symbols --no-weak --no-rsrc  %t/1.bc %t/2.bc %t/1.o %t/2.o | FileCheck --check-prefixes=COMMON,HIDDEN %s --implicit-check-not={{.}}
+
+# COMMON: .
+# COMMON: __1__
+# COMMON: __rsrc
+# COMMON: __sinit
+# COMMON: __sterm
+# COMMON: __tf1
+# COMMON: __tf9
+# COMMON: c
+# COMMON: d
+# HIDDEN: h
+# WEAK:   w
+
+#--- 1.ll
+target triple = "x86_64-unknown-linux-gnu"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define void @d() {
+  ret void
+}
+
+define hidden void @h() {
+  ret void
+}
+
+declare void @u()
+
+declare i32 @r(...)
+
+declare extern_weak void @wr()
+
+define internal void @l() {
+  ret void
+}
+
+ at c = common global i32 0
+
+define weak void @w() {
+  ret void
+}
+
+;; special cases from the AIX implementation.
+
+define void @__sinit() {
+  ret void
+}
+
+define void @__sterm() {
+  ret void
+}
+
+define void @.() {
+  ret void
+}
+
+;define void @(() {
+;  ret void
+;}
+
+define void @__1__() {
+  ret void
+}
+
+define void @__tf1() {
+  ret void
+}
+
+define void @__tf9() {
+  ret void
+}
+
+define void @__rsrc() {
+  ret void
+}
+
+#--- 2.ll
+target triple = "x86_64-unknown-linux-gnu"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+define protected void @d() {
+  ret void
+}
+
+define void @h() {
+  ret void
+}
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index e3b81451fcac91..b8b7ba83c577de 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -254,16 +254,17 @@ struct NMSymbol {
     return true;
   }
 
-  bool shouldPrint() const {
+  bool shouldPrint(bool OnlyExported) const {
     bool Undefined = SymFlags & SymbolRef::SF_Undefined;
     bool Global = SymFlags & SymbolRef::SF_Global;
     bool Weak = SymFlags & SymbolRef::SF_Weak;
+    bool Export = SymFlags & SymbolRef::SF_Exported;
     bool FormatSpecific = SymFlags & SymbolRef::SF_FormatSpecific;
     if ((!Undefined && UndefinedOnly) || (Undefined && DefinedOnly) ||
         (!Global && ExternalOnly) || (Weak && NoWeakSymbols) ||
         (FormatSpecific && !(SpecialSyms || DebugSyms)))
       return false;
-    return true;
+    return OnlyExported ? Export : true;
   }
 };
 
@@ -777,7 +778,7 @@ static void printSymbolList(SymbolicFile &Obj,
   }
 
   for (const NMSymbol &S : SymbolList) {
-    if (!S.shouldPrint())
+    if (!S.shouldPrint(false /*OnlyExported*/))
       continue;
 
     std::string Name = S.Name;
@@ -1768,8 +1769,10 @@ static void getXCOFFExports(XCOFFObjectFile *XCOFFObj,
       else if ((SymType & XCOFF::VISIBILITY_MASK) == XCOFF::SYM_V_EXPORTED)
         S.Visibility = "export";
     }
-    if (S.initializeFlags(*XCOFFObj))
+    if (S.initializeFlags(*XCOFFObj)) {
+      S.SymFlags |= SymbolRef::SF_Exported;
       SymbolList.push_back(S);
+    }
   }
 }
 
@@ -2392,8 +2395,9 @@ exportSymbolNamesFromFiles(const std::vector<std::string> &InputFilenames) {
   }
 
   // Delete symbols which should not be printed from SymolList.
-  llvm::erase_if(SymbolList,
-                 [](const NMSymbol &s) { return !s.shouldPrint(); });
+  llvm::erase_if(SymbolList, [](const NMSymbol &s) {
+    return !s.shouldPrint(true /*OnlyExported*/);
+  });
   sortSymbolList(SymbolList);
   SymbolList.erase(std::unique(SymbolList.begin(), SymbolList.end()),
                    SymbolList.end());

>From bc2930dfc121d43b6ccd1dca9bbd23f5baa04fd5 Mon Sep 17 00:00:00 2001
From: Ben Dunbobbin <Ben.Dunbobbin at sony.com>
Date: Mon, 18 Mar 2024 16:02:15 +0000
Subject: [PATCH 2/2] Implement --export-symbols for ELF

For ELF make this option display the set of exportable symbols. This is
particularly useful for bitcode as the binary tools do not offer an
easy way to dump the visibility of bitcode symbols. I have adjusted the
documentation to note that the linker ultimately decides which symbols
are exported and that --export-symbols can only show which symbols are
potential exports for ELF.

SF_Exported for bitcode:
SF_Exported is checked by some LLVM code and setting it for bitcode
symbols might change behaviour. Instead of setting SF_Exported
generally for bitcode symbols we instead set it for bitcode symbols
within llvm-nm.

For non-XCOFF file formats I assume that the --export-symbols is not in
not currently in use as it previously didn't produce sensible output.
Therefore, I do not have to worry about changing the behaviour for
those file formats. This change should improve the output for other
file formats and it should be easy to support them in future if
required. For now they are remain unsupported and I have changed the
help text/documentation to note that.
---
 llvm/docs/CommandGuide/llvm-nm.rst    |  6 ++++--
 llvm/lib/Object/ModuleSymbolTable.cpp | 15 -------------
 llvm/tools/llvm-nm/Opts.td            |  2 +-
 llvm/tools/llvm-nm/llvm-nm.cpp        | 31 ++++++++++++++++++++++++---
 4 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-nm.rst b/llvm/docs/CommandGuide/llvm-nm.rst
index 7067bb0a29a195..107c97d82af7be 100644
--- a/llvm/docs/CommandGuide/llvm-nm.rst
+++ b/llvm/docs/CommandGuide/llvm-nm.rst
@@ -168,8 +168,10 @@ OPTIONS
 
 .. option:: --export-symbols
 
- Print sorted symbols with their visibility (if applicable), with duplicates
- removed.
+ Print sorted exportable symbols, with duplicates removed. For AIX the
+ symbol visibility is included. Consult your linker documentation o
+ determine which of the exportable symbols will actually be exported.
+ This option is currently only implemented for ELF and XCOFF.
 
 .. option:: --extern-only, -g
 
diff --git a/llvm/lib/Object/ModuleSymbolTable.cpp b/llvm/lib/Object/ModuleSymbolTable.cpp
index 266df68d42b7cd..07f76688fa43e7 100644
--- a/llvm/lib/Object/ModuleSymbolTable.cpp
+++ b/llvm/lib/Object/ModuleSymbolTable.cpp
@@ -199,19 +199,6 @@ void ModuleSymbolTable::printSymbolName(raw_ostream &OS, Symbol S) const {
   Mang.getNameWithPrefix(OS, GV, false);
 }
 
-static bool isExportedToOtherDSO(const Triple TT, GlobalValue &GV) {
-  if (TT.isOSBinFormatELF())
-    // A defintition is exported if its non-local, and its visibility
-    // is either DEFAULT or PROTECTED. All other symbols are not exported.
-    return !GV.isDeclarationForLinker() && !GV.hasLocalLinkage() &&
-           (GV.hasDefaultVisibility() || GV.hasProtectedVisibility());
-  else if (TT.isOSBinFormatXCOFF())
-    return true;
-
-  // TODO: Add support for other file formats
-  return false;
-}
-
 uint32_t ModuleSymbolTable::getSymbolFlags(Symbol S) const {
   if (isa<AsmSymbol *>(S))
     return cast<AsmSymbol *>(S)->second;
@@ -227,8 +214,6 @@ uint32_t ModuleSymbolTable::getSymbolFlags(Symbol S) const {
     if (GVar->isConstant())
       Res |= BasicSymbolRef::SF_Const;
   }
-  if (isExportedToOtherDSO(Triple(FirstMod->getTargetTriple()), *GV))
-    Res |= BasicSymbolRef::SF_Exported;
   if (const GlobalObject *GO = GV->getAliaseeObject())
     if (isa<Function>(GO) || isa<GlobalIFunc>(GO))
       Res |= BasicSymbolRef::SF_Executable;
diff --git a/llvm/tools/llvm-nm/Opts.td b/llvm/tools/llvm-nm/Opts.td
index 04d9f5db5cf85d..8eed1fe12d56fe 100644
--- a/llvm/tools/llvm-nm/Opts.td
+++ b/llvm/tools/llvm-nm/Opts.td
@@ -18,7 +18,7 @@ def debug_syms : FF<"debug-syms", "Show all symbols, even debugger only">;
 def defined_only : FF<"defined-only", "Show only defined symbols">;
 defm demangle : BB<"demangle", "Demangle C++ symbol names", "Don't demangle symbol names">;
 def dynamic : FF<"dynamic", "Display dynamic symbols instead of normal symbols">;
-def export_symbols : FF<"export-symbols", "Export symbol list for all inputs">;
+def export_symbols : FF<"export-symbols", "Print the exportable symbol list for all inputs, currently only implemented for ELF and XCOFF">;
 def extern_only : FF<"extern-only", "Show only external symbols">;
 defm format : Eq<"format", "Specify output format: bsd (default), posix, sysv, darwin, just-symbols">, MetaVarName<"<format>">;
 def help : FF<"help", "Display this help">;
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index b8b7ba83c577de..ca75602d118864 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -778,7 +778,7 @@ static void printSymbolList(SymbolicFile &Obj,
   }
 
   for (const NMSymbol &S : SymbolList) {
-    if (!S.shouldPrint(false /*OnlyExported*/))
+    if (!S.shouldPrint(/*OnlyExported=*/false))
       continue;
 
     std::string Name = S.Name;
@@ -1885,8 +1885,33 @@ static bool getSymbolNamesFromObject(SymbolicFile &Obj,
             (SymbolVersions[I].IsVerDef ? "@@" : "@") + SymbolVersions[I].Name;
 
       S.Sym = Sym;
-      if (S.initializeFlags(Obj))
+      if (S.initializeFlags(Obj)) {
+        // Set SF_Exported for IR symbols so it can be used for deciding
+        // which symbols should be output for --export-symbols.
+        if (const IRObjectFile *IRObj = dyn_cast<IRObjectFile>(&Obj)) {
+
+          // SF_Exported is not currently set for bitcode symbols
+          // so we are free to use it here. This assert checks that
+          // that assumption remains valid.
+          assert(!(S.SymFlags & BasicSymbolRef::SF_Exported));
+
+          Triple TT = Triple(IRObj->getTargetTriple());
+          if (TT.isOSBinFormatELF()) {
+            // A defintition is potentially exportable if its non-local, and its
+            // visibility is either DEFAULT or PROTECTED. All other symbols are
+            // not exported.
+            bool defined = !(S.SymFlags & BasicSymbolRef::SF_Undefined);
+            bool global = S.SymFlags & BasicSymbolRef::SF_Global;
+            bool dynamic = !(S.SymFlags & BasicSymbolRef::SF_Hidden);
+            if (defined && global && dynamic)
+              S.SymFlags |= BasicSymbolRef::SF_Exported;
+          } else
+            // Behaviour implemented for XCOFF and other file formats
+            // is that --export-symbols prints all IR symbols.
+            S.SymFlags |= BasicSymbolRef::SF_Exported;
+        }
         SymbolList.push_back(S);
+      }
     }
   }
 
@@ -2396,7 +2421,7 @@ exportSymbolNamesFromFiles(const std::vector<std::string> &InputFilenames) {
 
   // Delete symbols which should not be printed from SymolList.
   llvm::erase_if(SymbolList, [](const NMSymbol &s) {
-    return !s.shouldPrint(true /*OnlyExported*/);
+    return !s.shouldPrint(/*OnlyExported=*/true);
   });
   sortSymbolList(SymbolList);
   SymbolList.erase(std::unique(SymbolList.begin(), SymbolList.end()),



More information about the llvm-commits mailing list