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 @@ -332,6 +332,10 @@ Add symbol description to disassembly output. +.. option:: --traceback-table + + Decode traceback table for disassembly output. + BUGS ---- diff --git a/llvm/include/llvm/BinaryFormat/XCOFF.h b/llvm/include/llvm/BinaryFormat/XCOFF.h --- a/llvm/include/llvm/BinaryFormat/XCOFF.h +++ b/llvm/include/llvm/BinaryFormat/XCOFF.h @@ -284,6 +284,61 @@ StringRef getMappingClassString(XCOFF::StorageMappingClass SMC); StringRef getRelocationTypeString(XCOFF::RelocationType Type); +struct TracebackTable { + + // Byte 1 + static constexpr uint32_t VersionMask = 0xFF000000; + static constexpr uint8_t VersionShift = 24; + + // Byte 2 + static constexpr uint32_t LanguageIdMask = 0xFF0000; + static constexpr uint8_t LanguageIdShift = 16; + + // Byte 3 + static constexpr uint32_t GlobaLinkageMask = 0x8000; + static constexpr uint32_t IsEprolMask = 0x4000; + static constexpr uint32_t HasCodeLenMask = 0x2000; + static constexpr uint32_t IntProcMask = 0x1000; + static constexpr uint32_t HasCtlMask = 0x0800; + static constexpr uint32_t TOCLessMask = 0x0400; + static constexpr uint32_t FPPresentMask = 0x0200; + static constexpr uint32_t LogAbortMask = 0x0100; + + // Byte 4 + static constexpr uint32_t IntHandlerMask = 0x80; + static constexpr uint32_t NamePresentMask = 0x40; + static constexpr uint32_t UsedAllocaMask = 0x20; + static constexpr uint32_t ClDisInvMask = 0x1C; + static constexpr uint32_t SavesCRMask = 0x02; + static constexpr uint32_t SavesLRMask = 0x01; + + static constexpr uint8_t ClDisInvShift = 2; + + // Byte 5 + static constexpr uint32_t StoresBCMask = 0x80000000; + static constexpr uint32_t FixupMask = 0x40000000; + static constexpr uint32_t FPSavedMask = 0x3F000000; + static constexpr uint32_t FPSavedShift = 24; + + // Byte 6 + static constexpr uint32_t HasVecInfoMask = 0x800000; + static constexpr uint32_t Spare4Mask = 0x400000; + static constexpr uint32_t GPSavedMask = 0x3F0000; + static constexpr uint32_t GPSavedShift = 16; + + // Byte 7 + static constexpr uint32_t NumberOfFixedParaMask = 0xFF00; + static constexpr uint8_t NumberOfFixedParaShift = 8; + + // Byte 8 + static constexpr uint32_t NumberOfFPParaMask = 0xFE; + static constexpr uint32_t ParmsOnStackMask = 0x01; + static constexpr uint8_t NumberOfFPParaShift = 01; + + static constexpr uint32_t FixedParaTypeBit = 0x80000000; + static constexpr uint32_t FloatPointParaTypeBit = 0x40000000; +}; + } // end namespace XCOFF } // end namespace llvm diff --git a/llvm/include/llvm/Object/XCOFFObjectFile.h b/llvm/include/llvm/Object/XCOFFObjectFile.h --- a/llvm/include/llvm/Object/XCOFFObjectFile.h +++ b/llvm/include/llvm/Object/XCOFFObjectFile.h @@ -391,6 +391,60 @@ bool isFunction() const; }; +class XCOFFTracebackTable { + const uint8_t *TBPtr; + const uint64_t Size; + Optional ParaType; + Optional CodeLen; + Optional HandMask; + Optional CtlInfo; + Optional FunctionNameLen; + Optional FunctionName; + Optional AllocaRegister; + +public: + XCOFFTracebackTable(const uint8_t *Ptr, const uint64_t S); + + uint8_t getVersion(); + uint8_t getLanguageID(); + + bool isGlobalLinkage(); + bool isEprol(); + bool hasFunctionCodeLen(); + bool isInternalProcedure(); + bool hasCtl(); + bool isTOCLess(); + bool isFloatPointPresent(); + bool isLogAbort(); + + bool isInterruptHandler(); + bool isFuncNamePresent(); + bool usedAlloca(); + uint8_t getClDisInv(); + bool doesCRSaved(); + bool doesLRSaved(); + + bool doesBackChainStore(); + uint8_t getNumOfFPRsSaved(); + + bool hasVecInfo(); + uint8_t getNumofGPRsSaved(); + + uint8_t getNumberOfFixedPara(); + + uint8_t getNumberOfFPPara(); + bool isParaOnStack(); + + Optional getParaType() { return ParaType; } + Optional getCodeLen() { return CodeLen; } + Optional getHandMask() { return HandMask; } + Optional getCtlInfo() { return CtlInfo; } + Optional getFunctionNameLen() { return FunctionNameLen; } + Optional getFunctionName() { return FunctionName; } + Optional getAllocaRegister() { return AllocaRegister; } +}; + +bool doesXCOFFTracebackTableBegin(ArrayRef Bytes); } // namespace object } // namespace llvm diff --git a/llvm/lib/Object/XCOFFObjectFile.cpp b/llvm/lib/Object/XCOFFObjectFile.cpp --- a/llvm/lib/Object/XCOFFObjectFile.cpp +++ b/llvm/lib/Object/XCOFFObjectFile.cpp @@ -16,6 +16,9 @@ #include namespace llvm { + +using namespace XCOFF; + namespace object { static const uint8_t FunctionSym = 0x20; @@ -49,6 +52,11 @@ : StringRef(Name, XCOFF::NameSize); } +bool doesXCOFFTracebackTableBegin(ArrayRef Bytes) { + assert(Bytes.size() == 4 && "Traceback table started with 4 bytes zero."); + return (*reinterpret_cast(Bytes.data()) == 0); +} + template StringRef XCOFFSectionHeader::getName() const { const T &DerivedXCOFFSectionHeader = static_cast(*this); return generateXCOFFFixedNameStringRef(DerivedXCOFFSectionHeader.Name); @@ -834,5 +842,175 @@ template struct XCOFFSectionHeader; template struct XCOFFSectionHeader; +static StringRef parseParaType(uint32_t Value, unsigned int ParaNum) { + std::string ParaType; + for (unsigned I = 0; I < ParaNum; ++I) { + if (I != 0) + ParaType += ", "; + if ((Value & TracebackTable::FixedParaTypeBit) == 0) { + // Fixed parameter type. + ParaType += "i"; + Value <<= 1; + continue; + } else { + if ((Value & TracebackTable::FloatPointParaTypeBit) == 0) + // Float parameter type. + ParaType += "f"; + else + // Double parameter type. + ParaType += "d"; + + Value <<= 2; + continue; + } + } + return ParaType; +} + +XCOFFTracebackTable::XCOFFTracebackTable(const uint8_t *Ptr, const uint64_t S) + : TBPtr(Ptr), Size(S) { + uint64_t Pos = 8; + assert(Pos <= Size && "The size of traceback table at least need 8 bytes."); + unsigned int ParaNum = getNumberOfFixedPara() + getNumberOfFPPara(); + if (ParaNum) { + assert(Pos + 4 <= Size && + "Not enough size for traceback table's parameter type."); + ParaType = parseParaType(support::endian::read32be(TBPtr + Pos), ParaNum); + Pos += 4; + } + + if (hasFunctionCodeLen()) { + assert(Pos + 4 <= Size && + "Not enough size for traceback table's function code len."); + CodeLen = support::endian::read32be(TBPtr + Pos); + Pos += 4; + } + + if (isInterruptHandler()) { + assert(Pos + 4 <= Size && + "Not enough size for traceback table's Interrupt handler."); + HandMask = support::endian::read32be(TBPtr + Pos); + Pos += 4; + } + + if (hasCtl()) { + assert(Pos + 4 <= Size && + "Not enough size for traceback table's ctl info."); + CtlInfo = support::endian::read32be(TBPtr + Pos); + Pos += 4; + } + + if (isFuncNamePresent()) { + assert(Pos + 2 <= Size && + "Not enough size for traceback table's function name len."); + uint16_t Len = support::endian::read16be(TBPtr + Pos); + Pos += 2; + assert(Pos + Len <= Size && + "Not enough size for traceback table's function name."); + FunctionName = StringRef((const char *)(TBPtr + Pos), Len); + Pos += Len; + FunctionNameLen = Len; + } + + if (usedAlloca()) { + assert(Pos + 1 <= Size && + "Not enough size for traceback table's alloca register."); + AllocaRegister = *(TBPtr + Pos); + Pos++; + } +} + +#define GETBITWITHMASK(P, X) \ + (support::endian::read32be(TBPtr + P) & TracebackTable::X) +#define GETBITWITHMASKSHIFT(P, X, S) \ + (support::endian::read32be(TBPtr + P) & TracebackTable::X) >> \ + TracebackTable::S + +uint8_t XCOFFTracebackTable::getVersion() { + return GETBITWITHMASKSHIFT(0, VersionMask, VersionShift); +} + +uint8_t XCOFFTracebackTable::getLanguageID() { + return GETBITWITHMASKSHIFT(0, LanguageIdMask, LanguageIdShift); +} + +bool XCOFFTracebackTable::isGlobalLinkage() { + return GETBITWITHMASK(0, GlobaLinkageMask); +} + +bool XCOFFTracebackTable::isEprol() { return GETBITWITHMASK(0, IsEprolMask); } + +bool XCOFFTracebackTable::hasFunctionCodeLen() { + return GETBITWITHMASK(0, HasCodeLenMask); +} + +bool XCOFFTracebackTable::isInternalProcedure() { + return GETBITWITHMASK(0, IntProcMask); +} + +bool XCOFFTracebackTable::hasCtl() { return GETBITWITHMASK(0, HasCtlMask); } + +bool XCOFFTracebackTable::isTOCLess() { return GETBITWITHMASK(0, TOCLessMask); } + +bool XCOFFTracebackTable::isFloatPointPresent() { + return GETBITWITHMASK(0, FPPresentMask); +} + +bool XCOFFTracebackTable::isLogAbort() { + return GETBITWITHMASK(0, LogAbortMask); +} + +bool XCOFFTracebackTable::isInterruptHandler() { + return GETBITWITHMASK(0, IntHandlerMask); +} + +bool XCOFFTracebackTable::isFuncNamePresent() { + return GETBITWITHMASK(0, NamePresentMask); +} + +bool XCOFFTracebackTable::usedAlloca() { + return GETBITWITHMASK(0, UsedAllocaMask); +} + +uint8_t XCOFFTracebackTable::getClDisInv() { + return GETBITWITHMASKSHIFT(0, ClDisInvMask, ClDisInvShift); +} + +bool XCOFFTracebackTable::doesCRSaved() { + return GETBITWITHMASK(0, SavesCRMask); +} + +bool XCOFFTracebackTable::doesLRSaved() { + return GETBITWITHMASK(0, SavesLRMask); +} + +bool XCOFFTracebackTable::doesBackChainStore() { + return GETBITWITHMASK(4, StoresBCMask); +} + +uint8_t XCOFFTracebackTable::getNumOfFPRsSaved() { + return GETBITWITHMASKSHIFT(4, FPSavedMask, FPSavedShift); +} + +bool XCOFFTracebackTable::hasVecInfo() { + return GETBITWITHMASK(4, HasVecInfoMask); +} + +uint8_t XCOFFTracebackTable::getNumofGPRsSaved() { + return GETBITWITHMASKSHIFT(4, GPSavedMask, GPSavedShift); +} + +uint8_t XCOFFTracebackTable::getNumberOfFixedPara() { + return GETBITWITHMASKSHIFT(4, NumberOfFixedParaMask, NumberOfFixedParaShift); +} + +uint8_t XCOFFTracebackTable::getNumberOfFPPara() { + return GETBITWITHMASKSHIFT(4, NumberOfFPParaMask, NumberOfFPParaShift); +} + +bool XCOFFTracebackTable::isParaOnStack() { + return GETBITWITHMASK(4, ParmsOnStackMask); +} + } // namespace object } // namespace llvm diff --git a/llvm/test/tools/llvm-objdump/XCOFF/disassemble-tracebacktable.test b/llvm/test/tools/llvm-objdump/XCOFF/disassemble-tracebacktable.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objdump/XCOFF/disassemble-tracebacktable.test @@ -0,0 +1,11 @@ +# REQUIRES: powerpc-registered-target + +# RUN: llvm-objdump -d --traceback-table --symbol-description %p/Inputs/xcoff-section-headers.o | \ +# RUN: FileCheck %s + + + +CHECK-LABEL: 00000000 (idx: 16) .func: +CHECK: c: 00 00 00 00 # Traceback table begin +CHECK-NEXT: 10: 00 00 20 40 # Version = 0 +CHECK-NEXT: # Language = C diff --git a/llvm/tools/llvm-objdump/XCOFFDump.h b/llvm/tools/llvm-objdump/XCOFFDump.h --- a/llvm/tools/llvm-objdump/XCOFFDump.h +++ b/llvm/tools/llvm-objdump/XCOFFDump.h @@ -10,6 +10,7 @@ #define LLVM_TOOLS_LLVM_OBJDUMP_XCOFFDUMP_H #include "llvm/Object/XCOFFObjectFile.h" +#include "llvm/Support/raw_ostream.h" namespace llvm { @@ -28,6 +29,11 @@ Error getXCOFFRelocationValueString(const object::XCOFFObjectFile *Obj, const object::RelocationRef &RelRef, llvm::SmallVectorImpl &Result); +void formatTracebackTableOutput(ArrayRef Bytes, uint64_t Address, + raw_ostream &OS); + +void dumpTracebackTable(ArrayRef Bytes, uint64_t Address, + raw_ostream &OS, uint64_t End); } // namespace objdump } // namespace llvm #endif diff --git a/llvm/tools/llvm-objdump/XCOFFDump.cpp b/llvm/tools/llvm-objdump/XCOFFDump.cpp --- a/llvm/tools/llvm-objdump/XCOFFDump.cpp +++ b/llvm/tools/llvm-objdump/XCOFFDump.cpp @@ -15,9 +15,13 @@ #include "llvm-objdump.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/MC/MCInstPrinter.h" +#include "llvm/Support/Endian.h" using namespace llvm; using namespace llvm::object; +using namespace llvm::XCOFF; +using namespace llvm::support; Error objdump::getXCOFFRelocationValueString(const XCOFFObjectFile *Obj, const RelocationRef &Rel, @@ -86,3 +90,54 @@ return Result; } + +void objdump::formatTracebackTableOutput(ArrayRef Bytes, + uint64_t Address, raw_ostream &OS) { + size_t Start = OS.tell(); + if (!NoLeadingAddr) + OS << format("%8" PRIx64 ":", Address); + if (!NoShowRawInsn) { + OS << ' '; + dumpBytes(Bytes, OS); + } + + // The output of printInst starts with a tab. Print some spaces so that + // the tab has 1 column and advances to the target tab stop. + unsigned TabStop = NoShowRawInsn ? 16 : 40; + unsigned Column = OS.tell() - Start; + OS.indent(Column < TabStop - 1 ? TabStop - 1 - Column : 7 - Column % 8); +} + +std::string SourceLanguageIdentifier[] = { + "C", "FORTRAN", "Pascal", "ADA", "PL/I", + "BASIC", "LISP", "COBOL", "Modulas2", "C++", + "RPG", "PL8", "Assembler", "Java", "Objective C"}; + +void objdump::dumpTracebackTable(ArrayRef Bytes, uint64_t Address, + raw_ostream &OS, uint64_t End) { + unsigned int Index = 0; + unsigned TabStop = (NoShowRawInsn ? 16 : 40) - 1; + + // Backtrace table boundary. + formatTracebackTableOutput(Bytes.slice(Index, 4), Address, OS); + OS << "# Traceback table begin\n"; + Index += 4; + + XCOFFTracebackTable TBT{Bytes.data() + Index, End - Address}; + + formatTracebackTableOutput(Bytes.slice(Index, 4), Address + Index, OS); + Index += 4; + OS << "# Version = " << (int)TBT.getVersion() << "\n"; + + OS.indent(TabStop); + unsigned LangId = TBT.getLanguageID(); + + OS << "# Language = " + << (LangId < sizeof(SourceLanguageIdentifier) / sizeof(char *) + ? SourceLanguageIdentifier[LangId] + : "Unknown") + << "\n"; +} +#undef BITCONTENT_ONEBIT +#undef BITCONTENT_SHIFT +#undef BITCONTENT_END 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 @@ -45,6 +45,7 @@ extern cl::opt SectionHeaders; extern cl::opt SectionContents; extern cl::opt SymbolDescription; +extern cl::opt TracebackTable; extern cl::opt SymbolTable; extern cl::opt TripleName; extern cl::opt UnwindInfo; 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 @@ -138,6 +138,12 @@ "option is for XCOFF files only"), cl::init(false), cl::cat(ObjdumpCat)); +cl::opt objdump::TracebackTable( + "traceback-table", + cl::desc("Decode traceback table for disassembly. This " + "option is for XCOFF files only"), + cl::init(false), cl::cat(ObjdumpCat)); + static cl::list DisassembleSymbols("disassemble-symbols", cl::CommaSeparated, cl::desc("List of symbols to disassemble. " @@ -1155,7 +1161,7 @@ const uint64_t Addr = unwrapOrError(Symbol.getAddress(), FileName); const StringRef Name = unwrapOrError(Symbol.getName(), FileName); - if (Obj->isXCOFF() && SymbolDescription) { + if (Obj->isXCOFF() && (SymbolDescription || TracebackTable)) { const auto *XCOFFObj = cast(Obj); DataRefImpl SymbolDRI = Symbol.getRawDataRefImpl(); @@ -1173,7 +1179,7 @@ static SymbolInfoTy createDummySymbolInfo(const ObjectFile *Obj, const uint64_t Addr, StringRef &Name, uint8_t Type) { - if (Obj->isXCOFF() && SymbolDescription) + if (Obj->isXCOFF() && (SymbolDescription || TracebackTable)) return SymbolInfoTy(Addr, Name, None, None, false); else return SymbolInfoTy(Addr, Name, Type); @@ -1452,6 +1458,12 @@ Symbols[SI].Type != ELF::STT_OBJECT && !DisassembleAll; bool DumpARMELFData = false; + bool DumpTracebackTableForXCOFFFunction = + Obj->isXCOFF() && Section.isText() && TracebackTable && + Symbols[SI].XCOFFSymInfo.StorageMappingClass && + Symbols[SI].XCOFFSymInfo.StorageMappingClass.getValue() == + XCOFF::XMC_PR; + while (Index < End) { // ARM and AArch64 ELF binaries can interleave data and text in the // same section. We rely on the markers introduced to understand what @@ -1492,6 +1504,15 @@ } } + if (DumpTracebackTableForXCOFFFunction && + doesXCOFFTracebackTableBegin(Bytes.slice(Index, 4))) { + dumpTracebackTable(Bytes.slice(Index), + SectionAddr + Index + VMAAdjustment, outs(), + End); + Index = End; + continue; + } + // Disassemble a real instruction or a data when disassemble all is // provided MCInst Inst; diff --git a/llvm/unittests/Object/CMakeLists.txt b/llvm/unittests/Object/CMakeLists.txt --- a/llvm/unittests/Object/CMakeLists.txt +++ b/llvm/unittests/Object/CMakeLists.txt @@ -11,6 +11,7 @@ ObjectFileTest.cpp SymbolSizeTest.cpp SymbolicFileTest.cpp + XCOFFObjectFileTest.cpp ) target_link_libraries(ObjectTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/Object/XCOFFObjectFileTest.cpp b/llvm/unittests/Object/XCOFFObjectFileTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Object/XCOFFObjectFileTest.cpp @@ -0,0 +1,70 @@ +//===- XCOFFObjectFileTest.cpp - Tests for XCOFFObjectFile ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/XCOFFObjectFile.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::object; + +TEST(XCOFFObjectFileTest, doesXCOFFTracebackTableBegin) { + ArrayRef T{0, 0, 0, 0}; + EXPECT_TRUE(doesXCOFFTracebackTableBegin(T)); + + ArrayRef F{0, 0, 0, 1}; + EXPECT_FALSE(doesXCOFFTracebackTableBegin(F)); + + uint8_t V[] = {0x00, 0x00, 0x22, 0x40, 0x80, 0x00, 0x01, 0x05, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x07, 0x61, 0x64, + 0x64, 0x5f, 0x61, 0x6c, 0x6c, 0x00, 0x00, 0x00}; + + XCOFFTracebackTable TT{V, sizeof(V)}; + + EXPECT_EQ(TT.getVersion(), 0); + + EXPECT_EQ(TT.getLanguageID(), 0); + + EXPECT_FALSE(TT.isGlobalLinkage()); + EXPECT_FALSE(TT.isEprol()); + EXPECT_TRUE(TT.hasFunctionCodeLen()); + EXPECT_FALSE(TT.isInternalProcedure()); + EXPECT_FALSE(TT.hasCtl()); + EXPECT_FALSE(TT.isTOCLess()); + EXPECT_TRUE(TT.isFloatPointPresent()); + EXPECT_FALSE(TT.isLogAbort()); + + EXPECT_FALSE(TT.isInterruptHandler()); + EXPECT_TRUE(TT.isFuncNamePresent()); + EXPECT_FALSE(TT.usedAlloca()); + EXPECT_EQ(TT.usedAlloca(), 0); + EXPECT_FALSE(TT.doesCRSaved()); + EXPECT_FALSE(TT.doesLRSaved()); + + EXPECT_TRUE(TT.doesBackChainStore()); + EXPECT_EQ(TT.getNumOfFPRsSaved(), 0); + + EXPECT_FALSE(TT.hasVecInfo()); + EXPECT_EQ(TT.getNumofGPRsSaved(), 0); + + EXPECT_EQ(TT.getNumberOfFixedPara(), 1); + + EXPECT_EQ(TT.getNumberOfFPPara(), 2); + EXPECT_TRUE(TT.isParaOnStack()); + + EXPECT_STREQ(TT.getParaType().getValue().data(), "i, f, d"); + + EXPECT_EQ(TT.getCodeLen().getValue(), 64u); + + EXPECT_FALSE(TT.getHandMask()); + EXPECT_FALSE(TT.getCtlInfo()); + + EXPECT_EQ(TT.getFunctionNameLen().getValue(), 7); + EXPECT_STREQ(TT.getFunctionName().getValue().data(), "add_all"); + + EXPECT_FALSE(TT.getAllocaRegister()); +}