| //===- lib/DebugInfo/Symbolize/DIPrinter.cpp ------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines the DIPrinter class, which is responsible for printing |
| // structures defined in DebugInfo/DIContext.h |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/DebugInfo/Symbolize/DIPrinter.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/DebugInfo/DIContext.h" |
| #include "llvm/Support/ErrorOr.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| |
| namespace llvm { |
| namespace symbolize { |
| |
| class SourceCode { |
| std::unique_ptr<MemoryBuffer> MemBuf; |
| |
| std::optional<StringRef> |
| load(StringRef FileName, const std::optional<StringRef> &EmbeddedSource) { |
| if (Lines <= 0) |
| return std::nullopt; |
| |
| if (EmbeddedSource) |
| return EmbeddedSource; |
| else { |
| ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = |
| MemoryBuffer::getFile(FileName); |
| if (!BufOrErr) |
| return std::nullopt; |
| MemBuf = std::move(*BufOrErr); |
| return MemBuf->getBuffer(); |
| } |
| } |
| |
| std::optional<StringRef> pruneSource(const std::optional<StringRef> &Source) { |
| if (!Source) |
| return std::nullopt; |
| size_t FirstLinePos = StringRef::npos, Pos = 0; |
| for (int64_t L = 1; L <= LastLine; ++L, ++Pos) { |
| if (L == FirstLine) |
| FirstLinePos = Pos; |
| Pos = Source->find('\n', Pos); |
| if (Pos == StringRef::npos) |
| break; |
| } |
| if (FirstLinePos == StringRef::npos) |
| return std::nullopt; |
| return Source->substr(FirstLinePos, (Pos == StringRef::npos) |
| ? StringRef::npos |
| : Pos - FirstLinePos); |
| } |
| |
| public: |
| const int64_t Line; |
| const int Lines; |
| const int64_t FirstLine; |
| const int64_t LastLine; |
| const std::optional<StringRef> PrunedSource; |
| |
| SourceCode(StringRef FileName, int64_t Line, int Lines, |
| const std::optional<StringRef> &EmbeddedSource = |
| std::optional<StringRef>()) |
| : Line(Line), Lines(Lines), |
| FirstLine(std::max(static_cast<int64_t>(1), Line - Lines / 2)), |
| LastLine(FirstLine + Lines - 1), |
| PrunedSource(pruneSource(load(FileName, EmbeddedSource))) {} |
| |
| void format(raw_ostream &OS) { |
| if (!PrunedSource) |
| return; |
| size_t MaxLineNumberWidth = std::ceil(std::log10(LastLine)); |
| int64_t L = FirstLine; |
| for (size_t Pos = 0; Pos < PrunedSource->size(); ++L) { |
| size_t PosEnd = PrunedSource->find('\n', Pos); |
| StringRef String = PrunedSource->substr( |
| Pos, (PosEnd == StringRef::npos) ? StringRef::npos : (PosEnd - Pos)); |
| if (String.endswith("\r")) |
| String = String.drop_back(1); |
| OS << format_decimal(L, MaxLineNumberWidth); |
| if (L == Line) |
| OS << " >: "; |
| else |
| OS << " : "; |
| OS << String << '\n'; |
| if (PosEnd == StringRef::npos) |
| break; |
| Pos = PosEnd + 1; |
| } |
| } |
| }; |
| |
| void PlainPrinterBase::printHeader(uint64_t Address) { |
| if (Config.PrintAddress) { |
| OS << "0x"; |
| OS.write_hex(Address); |
| StringRef Delimiter = Config.Pretty ? ": " : "\n"; |
| OS << Delimiter; |
| } |
| } |
| |
| // Prints source code around in the FileName the Line. |
| void PlainPrinterBase::printContext(SourceCode SourceCode) { |
| SourceCode.format(OS); |
| } |
| |
| void PlainPrinterBase::printFunctionName(StringRef FunctionName, bool Inlined) { |
| if (Config.PrintFunctions) { |
| if (FunctionName == DILineInfo::BadString) |
| FunctionName = DILineInfo::Addr2LineBadString; |
| StringRef Delimiter = Config.Pretty ? " at " : "\n"; |
| StringRef Prefix = (Config.Pretty && Inlined) ? " (inlined by) " : ""; |
| OS << Prefix << FunctionName << Delimiter; |
| } |
| } |
| |
| void LLVMPrinter::printSimpleLocation(StringRef Filename, |
| const DILineInfo &Info) { |
| OS << Filename << ':' << Info.Line << ':' << Info.Column << '\n'; |
| printContext( |
| SourceCode(Filename, Info.Line, Config.SourceContextLines, Info.Source)); |
| } |
| |
| void GNUPrinter::printSimpleLocation(StringRef Filename, |
| const DILineInfo &Info) { |
| OS << Filename << ':' << Info.Line; |
| if (Info.Discriminator) |
| OS << " (discriminator " << Info.Discriminator << ')'; |
| OS << '\n'; |
| printContext( |
| SourceCode(Filename, Info.Line, Config.SourceContextLines, Info.Source)); |
| } |
| |
| void PlainPrinterBase::printVerbose(StringRef Filename, |
| const DILineInfo &Info) { |
| OS << " Filename: " << Filename << '\n'; |
| if (Info.StartLine) { |
| OS << " Function start filename: " << Info.StartFileName << '\n'; |
| OS << " Function start line: " << Info.StartLine << '\n'; |
| } |
| printStartAddress(Info); |
| OS << " Line: " << Info.Line << '\n'; |
| OS << " Column: " << Info.Column << '\n'; |
| if (Info.Discriminator) |
| OS << " Discriminator: " << Info.Discriminator << '\n'; |
| } |
| |
| void LLVMPrinter::printStartAddress(const DILineInfo &Info) { |
| if (Info.StartAddress) { |
| OS << " Function start address: 0x"; |
| OS.write_hex(*Info.StartAddress); |
| OS << '\n'; |
| } |
| } |
| |
| void LLVMPrinter::printFooter() { OS << '\n'; } |
| |
| void PlainPrinterBase::print(const DILineInfo &Info, bool Inlined) { |
| printFunctionName(Info.FunctionName, Inlined); |
| StringRef Filename = Info.FileName; |
| if (Filename == DILineInfo::BadString) |
| Filename = DILineInfo::Addr2LineBadString; |
| if (Config.Verbose) |
| printVerbose(Filename, Info); |
| else |
| printSimpleLocation(Filename, Info); |
| } |
| |
| void PlainPrinterBase::print(const Request &Request, const DILineInfo &Info) { |
| printHeader(*Request.Address); |
| print(Info, false); |
| printFooter(); |
| } |
| |
| void PlainPrinterBase::print(const Request &Request, |
| const DIInliningInfo &Info) { |
| printHeader(*Request.Address); |
| uint32_t FramesNum = Info.getNumberOfFrames(); |
| if (FramesNum == 0) |
| print(DILineInfo(), false); |
| else |
| for (uint32_t I = 0; I < FramesNum; ++I) |
| print(Info.getFrame(I), I > 0); |
| printFooter(); |
| } |
| |
| void PlainPrinterBase::print(const Request &Request, const DIGlobal &Global) { |
| printHeader(*Request.Address); |
| StringRef Name = Global.Name; |
| if (Name == DILineInfo::BadString) |
| Name = DILineInfo::Addr2LineBadString; |
| OS << Name << "\n"; |
| OS << Global.Start << " " << Global.Size << "\n"; |
| if (Global.DeclFile.empty()) |
| OS << "??:?\n"; |
| else |
| OS << Global.DeclFile << ":" << Global.DeclLine << "\n"; |
| printFooter(); |
| } |
| |
| void PlainPrinterBase::print(const Request &Request, |
| const std::vector<DILocal> &Locals) { |
| printHeader(*Request.Address); |
| if (Locals.empty()) |
| OS << DILineInfo::Addr2LineBadString << '\n'; |
| else |
| for (const DILocal &L : Locals) { |
| if (L.FunctionName.empty()) |
| OS << DILineInfo::Addr2LineBadString; |
| else |
| OS << L.FunctionName; |
| OS << '\n'; |
| |
| if (L.Name.empty()) |
| OS << DILineInfo::Addr2LineBadString; |
| else |
| OS << L.Name; |
| OS << '\n'; |
| |
| if (L.DeclFile.empty()) |
| OS << DILineInfo::Addr2LineBadString; |
| else |
| OS << L.DeclFile; |
| |
| OS << ':' << L.DeclLine << '\n'; |
| |
| if (L.FrameOffset) |
| OS << *L.FrameOffset; |
| else |
| OS << DILineInfo::Addr2LineBadString; |
| OS << ' '; |
| |
| if (L.Size) |
| OS << *L.Size; |
| else |
| OS << DILineInfo::Addr2LineBadString; |
| OS << ' '; |
| |
| if (L.TagOffset) |
| OS << *L.TagOffset; |
| else |
| OS << DILineInfo::Addr2LineBadString; |
| OS << '\n'; |
| } |
| printFooter(); |
| } |
| |
| void PlainPrinterBase::printInvalidCommand(const Request &Request, |
| StringRef Command) { |
| OS << Command << '\n'; |
| } |
| |
| bool PlainPrinterBase::printError(const Request &Request, |
| const ErrorInfoBase &ErrorInfo, |
| StringRef ErrorBanner) { |
| ES << ErrorBanner; |
| ErrorInfo.log(ES); |
| ES << '\n'; |
| // Print an empty struct too. |
| return true; |
| } |
| |
| static std::string toHex(uint64_t V) { |
| return ("0x" + Twine::utohexstr(V)).str(); |
| } |
| |
| static json::Object toJSON(const Request &Request, StringRef ErrorMsg = "") { |
| json::Object Json({{"ModuleName", Request.ModuleName.str()}}); |
| if (Request.Address) |
| Json["Address"] = toHex(*Request.Address); |
| if (!ErrorMsg.empty()) |
| Json["Error"] = json::Object({{"Message", ErrorMsg.str()}}); |
| return Json; |
| } |
| |
| void JSONPrinter::print(const Request &Request, const DILineInfo &Info) { |
| DIInliningInfo InliningInfo; |
| InliningInfo.addFrame(Info); |
| print(Request, InliningInfo); |
| } |
| |
| void JSONPrinter::print(const Request &Request, const DIInliningInfo &Info) { |
| json::Array Array; |
| for (uint32_t I = 0, N = Info.getNumberOfFrames(); I < N; ++I) { |
| const DILineInfo &LineInfo = Info.getFrame(I); |
| json::Object Object( |
| {{"FunctionName", LineInfo.FunctionName != DILineInfo::BadString |
| ? LineInfo.FunctionName |
| : ""}, |
| {"StartFileName", LineInfo.StartFileName != DILineInfo::BadString |
| ? LineInfo.StartFileName |
| : ""}, |
| {"StartLine", LineInfo.StartLine}, |
| {"StartAddress", |
| LineInfo.StartAddress ? toHex(*LineInfo.StartAddress) : ""}, |
| {"FileName", |
| LineInfo.FileName != DILineInfo::BadString ? LineInfo.FileName : ""}, |
| {"Line", LineInfo.Line}, |
| {"Column", LineInfo.Column}, |
| {"Discriminator", LineInfo.Discriminator}}); |
| SourceCode SourceCode(LineInfo.FileName, LineInfo.Line, |
| Config.SourceContextLines, LineInfo.Source); |
| std::string FormattedSource; |
| raw_string_ostream Stream(FormattedSource); |
| SourceCode.format(Stream); |
| if (!FormattedSource.empty()) |
| Object["Source"] = std::move(FormattedSource); |
| Array.push_back(std::move(Object)); |
| } |
| json::Object Json = toJSON(Request); |
| Json["Symbol"] = std::move(Array); |
| if (ObjectList) |
| ObjectList->push_back(std::move(Json)); |
| else |
| printJSON(std::move(Json)); |
| } |
| |
| void JSONPrinter::print(const Request &Request, const DIGlobal &Global) { |
| json::Object Data( |
| {{"Name", Global.Name != DILineInfo::BadString ? Global.Name : ""}, |
| {"Start", toHex(Global.Start)}, |
| {"Size", toHex(Global.Size)}}); |
| json::Object Json = toJSON(Request); |
| Json["Data"] = std::move(Data); |
| if (ObjectList) |
| ObjectList->push_back(std::move(Json)); |
| else |
| printJSON(std::move(Json)); |
| } |
| |
| void JSONPrinter::print(const Request &Request, |
| const std::vector<DILocal> &Locals) { |
| json::Array Frame; |
| for (const DILocal &Local : Locals) { |
| json::Object FrameObject( |
| {{"FunctionName", Local.FunctionName}, |
| {"Name", Local.Name}, |
| {"DeclFile", Local.DeclFile}, |
| {"DeclLine", int64_t(Local.DeclLine)}, |
| {"Size", Local.Size ? toHex(*Local.Size) : ""}, |
| {"TagOffset", Local.TagOffset ? toHex(*Local.TagOffset) : ""}}); |
| if (Local.FrameOffset) |
| FrameObject["FrameOffset"] = *Local.FrameOffset; |
| Frame.push_back(std::move(FrameObject)); |
| } |
| json::Object Json = toJSON(Request); |
| Json["Frame"] = std::move(Frame); |
| if (ObjectList) |
| ObjectList->push_back(std::move(Json)); |
| else |
| printJSON(std::move(Json)); |
| } |
| |
| void JSONPrinter::printInvalidCommand(const Request &Request, |
| StringRef Command) { |
| printError(Request, |
| StringError("unable to parse arguments: " + Command, |
| std::make_error_code(std::errc::invalid_argument)), |
| ""); |
| } |
| |
| bool JSONPrinter::printError(const Request &Request, |
| const ErrorInfoBase &ErrorInfo, |
| StringRef ErrorBanner) { |
| json::Object Json = toJSON(Request, ErrorInfo.message()); |
| if (ObjectList) |
| ObjectList->push_back(std::move(Json)); |
| else |
| printJSON(std::move(Json)); |
| return false; |
| } |
| |
| void JSONPrinter::listBegin() { |
| assert(!ObjectList); |
| ObjectList = std::make_unique<json::Array>(); |
| } |
| |
| void JSONPrinter::listEnd() { |
| assert(ObjectList); |
| printJSON(std::move(*ObjectList)); |
| ObjectList.reset(); |
| } |
| |
| } // end namespace symbolize |
| } // end namespace llvm |