diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst --- a/llvm/docs/CommandGuide/llvm-objdump.rst +++ b/llvm/docs/CommandGuide/llvm-objdump.rst @@ -85,6 +85,10 @@ Display the symbol table. +.. option:: -T, --dynamic-syms + + Display the contents of the dynamic symbol table. + .. option:: -u, --unwind-info Display the unwind info of the input(s). diff --git a/llvm/test/tools/llvm-objdump/X86/elf-dynamic-symbols.test b/llvm/test/tools/llvm-objdump/X86/elf-dynamic-symbols.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/X86/elf-dynamic-symbols.test @@ -0,0 +1,107 @@ +## Test that llvm-objdump can dump dynamic symbols. +# RUN: yaml2obj --docnum=1 %s -o %t1 +# RUN: llvm-objdump --dynamic-syms %t1 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=DYNSYM +# RUN: llvm-objdump -T %t1 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=DYNSYM + +# DYNSYM:DYNAMIC SYMBOL TABLE: +# DYNSYM-NEXT:0000000000000000 l DO .data 0000000000000000 localsym +# DYNSYM-NEXT:0000000000000000 g DO .data 0000000000000000 globalsym +# DYNSYM-NEXT:0000000000000000 g DO .data 0000000000000000 uniqueglobalsym +# DYNSYM-NEXT:0000000000000000 w DO .data 0000000000000000 weaksym +# DYNSYM-NEXT:0000000000000000 g Df .data 0000000000000000 filesym +# DYNSYM-NEXT:0000000000000000 g DF .data 0000000000000000 funcsym +# DYNSYM-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .data + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_WRITE ] +DynamicSymbols: + - Name: localsym + Type: STT_OBJECT + Section: .data + Binding: STB_LOCAL + - Name: globalsym + Type: STT_OBJECT + Section: .data + Binding: STB_GLOBAL + - Name: uniqueglobalsym + Type: STT_OBJECT + Section: .data + Binding: STB_GNU_UNIQUE + - Name: weaksym + Type: STT_OBJECT + Section: .data + Binding: STB_WEAK + - Name: filesym + Type: STT_FILE + Section: .data + Binding: STB_GLOBAL + - Name: funcsym + Type: STT_FUNC + Section: .data + Binding: STB_GLOBAL + +## Test dumping ELF files with no .dynsym section. +# RUN: yaml2obj --docnum=2 %s -o %t2 +# RUN: llvm-objdump --dynamic-syms %t2 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=NODYNSYM +# RUN: llvm-objdump -T %t2 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=NODYNSYM + +# NODYNSYM:DYNAMIC SYMBOL TABLE: +# NODYNSYM-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + +## Test dumping ELF files with logically empty .dynsym section (only has a 0-index NULL symbol). +# RUN: yaml2obj --docnum=3 %s -o %t3 +# RUN: llvm-objdump --dynamic-syms %t3 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=ONLY-NULL +# RUN: llvm-objdump -T %t3 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=ONLY-NULL + +# ONLY-NULL:DYNAMIC SYMBOL TABLE: +# ONLY-NULL-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +DynamicSymbols: [] + +## Test dumping ELF files with truly empty .dynsym section (size of .dynsym section is 0). +# RUN: yaml2obj --docnum=4 %s -o %t4 +# RUN: llvm-objdump --dynamic-syms %t4 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=EMPTY +# RUN: llvm-objdump -T %t4 | \ +# RUN: FileCheck %s --match-full-lines --strict-whitespace --check-prefix=EMPTY + +# EMPTY:DYNAMIC SYMBOL TABLE: +# EMPTY-EMPTY: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .dynsym + Type: SHT_DYNSYM + Size: 0 diff --git a/llvm/test/tools/llvm-objdump/unimplemented-features.test b/llvm/test/tools/llvm-objdump/unimplemented-features.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/unimplemented-features.test @@ -0,0 +1,35 @@ +## Test dumping dynamic symbols reports a warning if the operation is unsupported for the file format. +# RUN: yaml2obj %s --docnum=1 -o %t1.o +# RUN: llvm-objdump --dynamic-syms %t1.o 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t1.o --match-full-lines --strict-whitespace + +## Test dumping dynamic symbols reports a warning if the operation is unsupported for the file format. +# RUN: yaml2obj %s --docnum=2 -o %t2.o +# RUN: llvm-objdump --dynamic-syms %t2.o 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t2.o --match-full-lines --strict-whitespace + +## Test dumping dynamic symbols reports a warning if the operation is unsupported for the file format. +# RUN: llvm-objdump --dynamic-syms %p/Inputs/xcoff-section-headers.o 2>&1 | \ +# RUN: FileCheck %s -DFILE=%p/Inputs/xcoff-section-headers.o --match-full-lines --strict-whitespace + +# CHECK:DYNAMIC SYMBOL TABLE: +# CHECK-NEXT:{{.*}}llvm-objdump: warning: '[[FILE]]': this operation is not currently supported for this file format +# CHECK-EMPTY: + +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x80000003 + filetype: 0x00000002 + ncmds: 0 + sizeofcmds: 0 + flags: 0x00218085 + reserved: 0x00000000 + +--- !COFF +header: + Machine: IMAGE_FILE_MACHINE_AMD64 + Characteristics: [] +sections: [] +symbols: [] diff --git a/llvm/tools/llvm-objdump/llvm-objdump.h b/llvm/tools/llvm-objdump/llvm-objdump.h --- a/llvm/tools/llvm-objdump/llvm-objdump.h +++ b/llvm/tools/llvm-objdump/llvm-objdump.h @@ -138,7 +138,11 @@ void printSectionHeaders(const object::ObjectFile *O); void printSectionContents(const object::ObjectFile *O); void printSymbolTable(const object::ObjectFile *O, StringRef ArchiveName, - StringRef ArchitectureName = StringRef()); + StringRef ArchitectureName = StringRef(), + bool DumpDynamic = false); +void printSymbol(const object::ObjectFile *O, const object::SymbolRef &Symbol, + StringRef FileName, StringRef ArchiveName, + StringRef ArchitectureName, bool DumpDynamic); LLVM_ATTRIBUTE_NORETURN void reportError(StringRef File, Twine Message); LLVM_ATTRIBUTE_NORETURN void reportError(Error E, StringRef FileName, StringRef ArchiveName = "", diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -318,6 +318,15 @@ cl::NotHidden, cl::Grouping, cl::aliasopt(SymbolTable)); +cl::opt DynamicSymbolTable( + "dynamic-syms", + cl::desc("Display the contents of the dynamic symbol table"), + cl::cat(ObjdumpCat)); +static cl::alias DynamicSymbolTableShort("T", + cl::desc("Alias for --dynamic-syms"), + cl::NotHidden, cl::Grouping, + cl::aliasopt(DynamicSymbolTable)); + cl::opt TripleName("triple", cl::desc("Target triple to disassemble for, " "see -version for available targets"), @@ -1843,136 +1852,159 @@ } void printSymbolTable(const ObjectFile *O, StringRef ArchiveName, - StringRef ArchitectureName) { - outs() << "SYMBOL TABLE:\n"; - - if (const COFFObjectFile *Coff = dyn_cast(O)) { - printCOFFSymbolTable(Coff); + StringRef ArchitectureName, bool DumpDynamic) { + if (O->isCOFF() && !DumpDynamic) { + outs() << "SYMBOL TABLE:\n"; + printCOFFSymbolTable(cast(O)); return; } const StringRef FileName = O->getFileName(); - const MachOObjectFile *MachO = dyn_cast(O); - for (auto I = O->symbol_begin(), E = O->symbol_end(); I != E; ++I) { - const SymbolRef &Symbol = *I; - uint64_t Address = unwrapOrError(Symbol.getAddress(), FileName, ArchiveName, - ArchitectureName); - if ((Address < StartAddress) || (Address > StopAddress)) - continue; - SymbolRef::Type Type = unwrapOrError(Symbol.getType(), FileName, - ArchiveName, ArchitectureName); - uint32_t Flags = Symbol.getFlags(); - // Don't ask a Mach-O STAB symbol for its section unless you know that - // STAB symbol's section field refers to a valid section index. Otherwise - // the symbol may error trying to load a section that does not exist. - bool isSTAB = false; - if (MachO) { - DataRefImpl SymDRI = Symbol.getRawDataRefImpl(); - uint8_t NType = (MachO->is64Bit() ? - MachO->getSymbol64TableEntry(SymDRI).n_type: - MachO->getSymbolTableEntry(SymDRI).n_type); - if (NType & MachO::N_STAB) - isSTAB = true; - } - section_iterator Section = isSTAB ? O->section_end() : - unwrapOrError(Symbol.getSection(), FileName, - ArchiveName, ArchitectureName); + if (!DumpDynamic) { + outs() << "SYMBOL TABLE:\n"; + for (auto I = O->symbol_begin(); I != O->symbol_end(); ++I) + printSymbol(O, *I, FileName, ArchiveName, ArchitectureName, DumpDynamic); + return; + } - StringRef Name; - if (Type == SymbolRef::ST_Debug && Section != O->section_end()) { - if (Expected NameOrErr = Section->getName()) - Name = *NameOrErr; - else - consumeError(NameOrErr.takeError()); + outs() << "DYNAMIC SYMBOL TABLE:\n"; + if (!O->isELF()) { + reportWarning( + "this operation is not currently supported for this file format", + FileName); + return; + } - } else { - Name = unwrapOrError(Symbol.getName(), FileName, ArchiveName, - ArchitectureName); - } + const ELFObjectFileBase *ELF = cast(O); + for (auto I = ELF->getDynamicSymbolIterators().begin(); + I != ELF->getDynamicSymbolIterators().end(); ++I) + printSymbol(O, *I, FileName, ArchiveName, ArchitectureName, DumpDynamic); +} - bool Global = Flags & SymbolRef::SF_Global; - bool Weak = Flags & SymbolRef::SF_Weak; - bool Absolute = Flags & SymbolRef::SF_Absolute; - bool Common = Flags & SymbolRef::SF_Common; - bool Hidden = Flags & SymbolRef::SF_Hidden; - - char GlobLoc = ' '; - if ((Section != O->section_end() || Absolute) && !Weak) - GlobLoc = Global ? 'g' : 'l'; - char Debug = (Type == SymbolRef::ST_Debug || Type == SymbolRef::ST_File) - ? 'd' : ' '; - char FileFunc = ' '; - if (Type == SymbolRef::ST_File) - FileFunc = 'f'; - else if (Type == SymbolRef::ST_Function) - FileFunc = 'F'; - else if (Type == SymbolRef::ST_Data) - FileFunc = 'O'; - - const char *Fmt = O->getBytesInAddress() > 4 ? "%016" PRIx64 : - "%08" PRIx64; - - outs() << format(Fmt, Address) << " " - << GlobLoc // Local -> 'l', Global -> 'g', Neither -> ' ' - << (Weak ? 'w' : ' ') // Weak? - << ' ' // Constructor. Not supported yet. - << ' ' // Warning. Not supported yet. - << ' ' // Indirect reference to another symbol. - << Debug // Debugging (d) or dynamic (D) symbol. - << FileFunc // Name of function (F), file (f) or object (O). - << ' '; - if (Absolute) { - outs() << "*ABS*"; - } else if (Common) { - outs() << "*COM*"; - } else if (Section == O->section_end()) { - outs() << "*UND*"; - } else { - if (const MachOObjectFile *MachO = - dyn_cast(O)) { - DataRefImpl DR = Section->getRawDataRefImpl(); - StringRef SegmentName = MachO->getSectionFinalSegmentName(DR); - outs() << SegmentName << ","; - } - StringRef SectionName = - unwrapOrError(Section->getName(), O->getFileName()); - outs() << SectionName; - } +void printSymbol(const ObjectFile *O, const SymbolRef &Symbol, + StringRef FileName, StringRef ArchiveName, + StringRef ArchitectureName, bool DumpDynamic) { + const MachOObjectFile *MachO = dyn_cast(O); + uint64_t Address = unwrapOrError(Symbol.getAddress(), FileName, ArchiveName, + ArchitectureName); + if ((Address < StartAddress) || (Address > StopAddress)) + return; + SymbolRef::Type Type = + unwrapOrError(Symbol.getType(), FileName, ArchiveName, ArchitectureName); + uint32_t Flags = Symbol.getFlags(); + + // Don't ask a Mach-O STAB symbol for its section unless you know that + // STAB symbol's section field refers to a valid section index. Otherwise + // the symbol may error trying to load a section that does not exist. + bool IsSTAB = false; + if (MachO) { + DataRefImpl SymDRI = Symbol.getRawDataRefImpl(); + uint8_t NType = + (MachO->is64Bit() ? MachO->getSymbol64TableEntry(SymDRI).n_type + : MachO->getSymbolTableEntry(SymDRI).n_type); + if (NType & MachO::N_STAB) + IsSTAB = true; + } + section_iterator Section = IsSTAB + ? O->section_end() + : unwrapOrError(Symbol.getSection(), FileName, + ArchiveName, ArchitectureName); + + StringRef Name; + if (Type == SymbolRef::ST_Debug && Section != O->section_end()) { + if (Expected NameOrErr = Section->getName()) + Name = *NameOrErr; + else + consumeError(NameOrErr.takeError()); + + } else { + Name = unwrapOrError(Symbol.getName(), FileName, ArchiveName, + ArchitectureName); + } - if (Common || isa(O)) { - uint64_t Val = - Common ? Symbol.getAlignment() : ELFSymbolRef(Symbol).getSize(); - outs() << '\t' << format(Fmt, Val); + bool Global = Flags & SymbolRef::SF_Global; + bool Weak = Flags & SymbolRef::SF_Weak; + bool Absolute = Flags & SymbolRef::SF_Absolute; + bool Common = Flags & SymbolRef::SF_Common; + bool Hidden = Flags & SymbolRef::SF_Hidden; + + char GlobLoc = ' '; + if ((Section != O->section_end() || Absolute) && !Weak) + GlobLoc = Global ? 'g' : 'l'; + char Debug = ' '; + if (DumpDynamic) + Debug = 'D'; + else if (Type == SymbolRef::ST_Debug || Type == SymbolRef::ST_File) + Debug = 'd'; + + char FileFunc = ' '; + if (Type == SymbolRef::ST_File) + FileFunc = 'f'; + else if (Type == SymbolRef::ST_Function) + FileFunc = 'F'; + else if (Type == SymbolRef::ST_Data) + FileFunc = 'O'; + + const char *Fmt = O->getBytesInAddress() > 4 ? "%016" PRIx64 : "%08" PRIx64; + + outs() << format(Fmt, Address) << " " + << GlobLoc // Local -> 'l', Global -> 'g', Neither -> ' ' + << (Weak ? 'w' : ' ') // Weak? + << ' ' // Constructor. Not supported yet. + << ' ' // Warning. Not supported yet. + << ' ' // Indirect reference to another symbol. + << Debug // Debugging (d) or dynamic (D) symbol. + << FileFunc // Name of function (F), file (f) or object (O). + << ' '; + if (Absolute) { + outs() << "*ABS*"; + } else if (Common) { + outs() << "*COM*"; + } else if (Section == O->section_end()) { + outs() << "*UND*"; + } else { + if (const MachOObjectFile *MachO = dyn_cast(O)) { + DataRefImpl DR = Section->getRawDataRefImpl(); + StringRef SegmentName = MachO->getSectionFinalSegmentName(DR); + outs() << SegmentName << ","; } + StringRef SectionName = unwrapOrError(Section->getName(), FileName); + outs() << SectionName; + } - if (isa(O)) { - uint8_t Other = ELFSymbolRef(Symbol).getOther(); - switch (Other) { - case ELF::STV_DEFAULT: - break; - case ELF::STV_INTERNAL: - outs() << " .internal"; - break; - case ELF::STV_HIDDEN: - outs() << " .hidden"; - break; - case ELF::STV_PROTECTED: - outs() << " .protected"; - break; - default: - outs() << format(" 0x%02x", Other); - break; - } - } else if (Hidden) { + if (Common || isa(O)) { + uint64_t Val = + Common ? Symbol.getAlignment() : ELFSymbolRef(Symbol).getSize(); + outs() << '\t' << format(Fmt, Val); + } + + if (isa(O)) { + uint8_t Other = ELFSymbolRef(Symbol).getOther(); + switch (Other) { + case ELF::STV_DEFAULT: + break; + case ELF::STV_INTERNAL: + outs() << " .internal"; + break; + case ELF::STV_HIDDEN: outs() << " .hidden"; + break; + case ELF::STV_PROTECTED: + outs() << " .protected"; + break; + default: + outs() << format(" 0x%02x", Other); + break; } - - if (Demangle) - outs() << ' ' << demangle(std::string(Name)) << '\n'; - else - outs() << ' ' << Name << '\n'; + } else if (Hidden) { + outs() << " .hidden"; } + + if (Demangle) + outs() << ' ' << demangle(std::string(Name)) << '\n'; + else + outs() << ' ' << Name << '\n'; } static void printUnwindInfo(const ObjectFile *O) { @@ -2216,6 +2248,9 @@ printSectionHeaders(O); if (SymbolTable) printSymbolTable(O, ArchiveName); + if (DynamicSymbolTable) + printSymbolTable(O, ArchiveName, /*ArchitectureName=*/"", + /*DumpDynamic=*/true); if (DwarfDumpType != DIDT_Null) { std::unique_ptr DICtx = DWARFContext::create(*O); // Dump the complete DWARF structure. @@ -2355,7 +2390,7 @@ if (!ArchiveHeaders && !Disassemble && DwarfDumpType == DIDT_Null && !DynamicRelocations && !FileHeaders && !PrivateHeaders && !RawClangAST && !Relocations && !SectionHeaders && !SectionContents && !SymbolTable && - !UnwindInfo && !FaultMapSection && + !DynamicSymbolTable && !UnwindInfo && !FaultMapSection && !(MachOOpt && (Bind || DataInCode || DylibId || DylibsUsed || ExportsTrie || FirstPrivateHeader || IndirectSymbols || InfoPlist || LazyBind ||