| 1 | //===- LinePrinter.cpp ------------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "llvm/DebugInfo/PDB/Native/LinePrinter.h" |
| 10 | |
| 11 | #include "llvm/ADT/STLExtras.h" |
| 12 | #include "llvm/DebugInfo/MSF/MSFCommon.h" |
| 13 | #include "llvm/DebugInfo/MSF/MappedBlockStream.h" |
| 14 | #include "llvm/DebugInfo/PDB/Native/InputFile.h" |
| 15 | #include "llvm/DebugInfo/PDB/Native/NativeSession.h" |
| 16 | #include "llvm/DebugInfo/PDB/Native/PDBFile.h" |
| 17 | #include "llvm/DebugInfo/PDB/UDTLayout.h" |
| 18 | #include "llvm/Object/COFF.h" |
| 19 | #include "llvm/Support/BinaryStreamReader.h" |
| 20 | #include "llvm/Support/Format.h" |
| 21 | #include "llvm/Support/FormatAdapters.h" |
| 22 | #include "llvm/Support/FormatVariadic.h" |
| 23 | #include "llvm/Support/Regex.h" |
| 24 | |
| 25 | #include <algorithm> |
| 26 | |
| 27 | using namespace llvm; |
| 28 | using namespace llvm::msf; |
| 29 | using namespace llvm::pdb; |
| 30 | |
| 31 | namespace { |
| 32 | bool IsItemExcluded(llvm::StringRef Item, |
| 33 | std::list<llvm::Regex> &IncludeFilters, |
| 34 | std::list<llvm::Regex> &ExcludeFilters) { |
| 35 | if (Item.empty()) |
| 36 | return false; |
| 37 | |
| 38 | auto match_pred = [Item](llvm::Regex &R) { return R.match(String: Item); }; |
| 39 | |
| 40 | // Include takes priority over exclude. If the user specified include |
| 41 | // filters, and none of them include this item, them item is gone. |
| 42 | if (!IncludeFilters.empty() && !any_of(Range&: IncludeFilters, P: match_pred)) |
| 43 | return true; |
| 44 | |
| 45 | if (any_of(Range&: ExcludeFilters, P: match_pred)) |
| 46 | return true; |
| 47 | |
| 48 | return false; |
| 49 | } |
| 50 | } // namespace |
| 51 | |
| 52 | using namespace llvm; |
| 53 | |
| 54 | LinePrinter::LinePrinter(int Indent, bool UseColor, llvm::raw_ostream &Stream, |
| 55 | const FilterOptions &Filters) |
| 56 | : OS(Stream), IndentSpaces(Indent), CurrentIndent(0), UseColor(UseColor), |
| 57 | Filters(Filters) { |
| 58 | SetFilters(List&: ExcludeTypeFilters, Begin: Filters.ExcludeTypes.begin(), |
| 59 | End: Filters.ExcludeTypes.end()); |
| 60 | SetFilters(List&: ExcludeSymbolFilters, Begin: Filters.ExcludeSymbols.begin(), |
| 61 | End: Filters.ExcludeSymbols.end()); |
| 62 | SetFilters(List&: ExcludeCompilandFilters, Begin: Filters.ExcludeCompilands.begin(), |
| 63 | End: Filters.ExcludeCompilands.end()); |
| 64 | |
| 65 | SetFilters(List&: IncludeTypeFilters, Begin: Filters.IncludeTypes.begin(), |
| 66 | End: Filters.IncludeTypes.end()); |
| 67 | SetFilters(List&: IncludeSymbolFilters, Begin: Filters.IncludeSymbols.begin(), |
| 68 | End: Filters.IncludeSymbols.end()); |
| 69 | SetFilters(List&: IncludeCompilandFilters, Begin: Filters.IncludeCompilands.begin(), |
| 70 | End: Filters.IncludeCompilands.end()); |
| 71 | } |
| 72 | |
| 73 | void LinePrinter::Indent(uint32_t Amount) { |
| 74 | if (Amount == 0) |
| 75 | Amount = IndentSpaces; |
| 76 | CurrentIndent += Amount; |
| 77 | } |
| 78 | |
| 79 | void LinePrinter::Unindent(uint32_t Amount) { |
| 80 | if (Amount == 0) |
| 81 | Amount = IndentSpaces; |
| 82 | CurrentIndent = std::max<int>(a: 0, b: CurrentIndent - Amount); |
| 83 | } |
| 84 | |
| 85 | void LinePrinter::NewLine() { |
| 86 | OS << "\n" ; |
| 87 | OS.indent(NumSpaces: CurrentIndent); |
| 88 | } |
| 89 | |
| 90 | void LinePrinter::print(const Twine &T) { OS << T; } |
| 91 | |
| 92 | void LinePrinter::printLine(const Twine &T) { |
| 93 | NewLine(); |
| 94 | OS << T; |
| 95 | } |
| 96 | |
| 97 | bool LinePrinter::IsClassExcluded(const ClassLayout &Class) { |
| 98 | if (IsTypeExcluded(TypeName: Class.getName(), Size: Class.getSize())) |
| 99 | return true; |
| 100 | if (Class.deepPaddingSize() < Filters.PaddingThreshold) |
| 101 | return true; |
| 102 | return false; |
| 103 | } |
| 104 | |
| 105 | void LinePrinter::formatBinary(StringRef Label, ArrayRef<uint8_t> Data, |
| 106 | uint64_t StartOffset) { |
| 107 | NewLine(); |
| 108 | OS << Label << " (" ; |
| 109 | if (!Data.empty()) { |
| 110 | OS << "\n" ; |
| 111 | OS << format_bytes_with_ascii(Bytes: Data, FirstByteOffset: StartOffset, NumPerLine: 32, ByteGroupSize: 4, |
| 112 | IndentLevel: CurrentIndent + IndentSpaces, Upper: true); |
| 113 | NewLine(); |
| 114 | } |
| 115 | OS << ")" ; |
| 116 | } |
| 117 | |
| 118 | void LinePrinter::formatBinary(StringRef Label, ArrayRef<uint8_t> Data, |
| 119 | uint64_t Base, uint64_t StartOffset) { |
| 120 | NewLine(); |
| 121 | OS << Label << " (" ; |
| 122 | if (!Data.empty()) { |
| 123 | OS << "\n" ; |
| 124 | Base += StartOffset; |
| 125 | OS << format_bytes_with_ascii(Bytes: Data, FirstByteOffset: Base, NumPerLine: 32, ByteGroupSize: 4, |
| 126 | IndentLevel: CurrentIndent + IndentSpaces, Upper: true); |
| 127 | NewLine(); |
| 128 | } |
| 129 | OS << ")" ; |
| 130 | } |
| 131 | |
| 132 | namespace { |
| 133 | struct Run { |
| 134 | Run() = default; |
| 135 | explicit Run(uint32_t Block) : Block(Block) {} |
| 136 | uint32_t Block = 0; |
| 137 | uint64_t ByteLen = 0; |
| 138 | }; |
| 139 | } // namespace |
| 140 | |
| 141 | static std::vector<Run> computeBlockRuns(uint32_t BlockSize, |
| 142 | const msf::MSFStreamLayout &Layout) { |
| 143 | std::vector<Run> Runs; |
| 144 | if (Layout.Length == 0) |
| 145 | return Runs; |
| 146 | |
| 147 | ArrayRef<support::ulittle32_t> Blocks = Layout.Blocks; |
| 148 | assert(!Blocks.empty()); |
| 149 | uint64_t StreamBytesRemaining = Layout.Length; |
| 150 | uint32_t CurrentBlock = Blocks[0]; |
| 151 | Runs.emplace_back(args&: CurrentBlock); |
| 152 | while (!Blocks.empty()) { |
| 153 | Run *CurrentRun = &Runs.back(); |
| 154 | uint32_t NextBlock = Blocks.front(); |
| 155 | if (NextBlock < CurrentBlock || (NextBlock - CurrentBlock > 1)) { |
| 156 | Runs.emplace_back(args&: NextBlock); |
| 157 | CurrentRun = &Runs.back(); |
| 158 | } |
| 159 | uint64_t Used = |
| 160 | std::min(a: static_cast<uint64_t>(BlockSize), b: StreamBytesRemaining); |
| 161 | CurrentRun->ByteLen += Used; |
| 162 | StreamBytesRemaining -= Used; |
| 163 | CurrentBlock = NextBlock; |
| 164 | Blocks = Blocks.drop_front(); |
| 165 | } |
| 166 | return Runs; |
| 167 | } |
| 168 | |
| 169 | static std::pair<Run, uint64_t> findRun(uint64_t Offset, ArrayRef<Run> Runs) { |
| 170 | for (const auto &R : Runs) { |
| 171 | if (Offset < R.ByteLen) |
| 172 | return std::make_pair(x: R, y&: Offset); |
| 173 | Offset -= R.ByteLen; |
| 174 | } |
| 175 | llvm_unreachable("Invalid offset!" ); |
| 176 | } |
| 177 | |
| 178 | void LinePrinter::formatMsfStreamData(StringRef Label, PDBFile &File, |
| 179 | uint32_t StreamIdx, |
| 180 | StringRef StreamPurpose, uint64_t Offset, |
| 181 | uint64_t Size) { |
| 182 | if (StreamIdx >= File.getNumStreams()) { |
| 183 | formatLine(Fmt: "Stream {0}: Not present" , Items&: StreamIdx); |
| 184 | return; |
| 185 | } |
| 186 | if (Size + Offset > File.getStreamByteSize(StreamIndex: StreamIdx)) { |
| 187 | formatLine( |
| 188 | Fmt: "Stream {0}: Invalid offset and size, range out of stream bounds" , |
| 189 | Items&: StreamIdx); |
| 190 | return; |
| 191 | } |
| 192 | |
| 193 | auto S = File.createIndexedStream(SN: StreamIdx); |
| 194 | if (!S) { |
| 195 | NewLine(); |
| 196 | formatLine(Fmt: "Stream {0}: Not present" , Items&: StreamIdx); |
| 197 | return; |
| 198 | } |
| 199 | |
| 200 | uint64_t End = |
| 201 | (Size == 0) ? S->getLength() : std::min(a: Offset + Size, b: S->getLength()); |
| 202 | Size = End - Offset; |
| 203 | |
| 204 | formatLine(Fmt: "Stream {0}: {1} (dumping {2:N} / {3:N} bytes)" , Items&: StreamIdx, |
| 205 | Items&: StreamPurpose, Items&: Size, Items: S->getLength()); |
| 206 | AutoIndent Indent(*this); |
| 207 | BinaryStreamRef Slice(*S); |
| 208 | BinarySubstreamRef Substream; |
| 209 | Substream.Offset = Offset; |
| 210 | Substream.StreamData = Slice.drop_front(N: Offset).keep_front(N: Size); |
| 211 | |
| 212 | auto Layout = File.getStreamLayout(StreamIdx); |
| 213 | formatMsfStreamData(Label, File, Stream: Layout, Substream); |
| 214 | } |
| 215 | |
| 216 | void LinePrinter::formatMsfStreamData(StringRef Label, PDBFile &File, |
| 217 | const msf::MSFStreamLayout &Stream, |
| 218 | BinarySubstreamRef Substream) { |
| 219 | BinaryStreamReader Reader(Substream.StreamData); |
| 220 | |
| 221 | auto Runs = computeBlockRuns(BlockSize: File.getBlockSize(), Layout: Stream); |
| 222 | |
| 223 | NewLine(); |
| 224 | OS << Label << " (" ; |
| 225 | while (Reader.bytesRemaining() > 0) { |
| 226 | OS << "\n" ; |
| 227 | |
| 228 | Run FoundRun; |
| 229 | uint64_t RunOffset; |
| 230 | std::tie(args&: FoundRun, args&: RunOffset) = findRun(Offset: Substream.Offset, Runs); |
| 231 | assert(FoundRun.ByteLen >= RunOffset); |
| 232 | uint64_t Len = FoundRun.ByteLen - RunOffset; |
| 233 | Len = std::min(a: Len, b: Reader.bytesRemaining()); |
| 234 | uint64_t Base = FoundRun.Block * File.getBlockSize() + RunOffset; |
| 235 | ArrayRef<uint8_t> Data; |
| 236 | consumeError(Err: Reader.readBytes(Buffer&: Data, Size: Len)); |
| 237 | OS << format_bytes_with_ascii(Bytes: Data, FirstByteOffset: Base, NumPerLine: 32, ByteGroupSize: 4, |
| 238 | IndentLevel: CurrentIndent + IndentSpaces, Upper: true); |
| 239 | if (Reader.bytesRemaining() > 0) { |
| 240 | NewLine(); |
| 241 | OS << formatv(Fmt: " {0}" , |
| 242 | Vals: fmt_align(Item: "<discontinuity>" , Where: AlignStyle::Center, Amount: 114, Fill: '-')); |
| 243 | } |
| 244 | Substream.Offset += Len; |
| 245 | } |
| 246 | NewLine(); |
| 247 | OS << ")" ; |
| 248 | } |
| 249 | |
| 250 | void LinePrinter::formatMsfStreamBlocks( |
| 251 | PDBFile &File, const msf::MSFStreamLayout &StreamLayout) { |
| 252 | auto Blocks = ArrayRef(StreamLayout.Blocks); |
| 253 | uint64_t L = StreamLayout.Length; |
| 254 | |
| 255 | while (L > 0) { |
| 256 | NewLine(); |
| 257 | assert(!Blocks.empty()); |
| 258 | OS << formatv(Fmt: "Block {0} (\n" , Vals: uint32_t(Blocks.front())); |
| 259 | uint64_t UsedBytes = |
| 260 | std::min(a: L, b: static_cast<uint64_t>(File.getBlockSize())); |
| 261 | ArrayRef<uint8_t> BlockData = |
| 262 | cantFail(ValOrErr: File.getBlockData(BlockIndex: Blocks.front(), NumBytes: File.getBlockSize())); |
| 263 | uint64_t BaseOffset = Blocks.front(); |
| 264 | BaseOffset *= File.getBlockSize(); |
| 265 | OS << format_bytes_with_ascii(Bytes: BlockData, FirstByteOffset: BaseOffset, NumPerLine: 32, ByteGroupSize: 4, |
| 266 | IndentLevel: CurrentIndent + IndentSpaces, Upper: true); |
| 267 | NewLine(); |
| 268 | OS << ")" ; |
| 269 | NewLine(); |
| 270 | L -= UsedBytes; |
| 271 | Blocks = Blocks.drop_front(); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | bool LinePrinter::IsTypeExcluded(llvm::StringRef TypeName, uint64_t Size) { |
| 276 | if (IsItemExcluded(Item: TypeName, IncludeFilters&: IncludeTypeFilters, ExcludeFilters&: ExcludeTypeFilters)) |
| 277 | return true; |
| 278 | if (Size < Filters.SizeThreshold) |
| 279 | return true; |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | bool LinePrinter::IsSymbolExcluded(llvm::StringRef SymbolName) { |
| 284 | return IsItemExcluded(Item: SymbolName, IncludeFilters&: IncludeSymbolFilters, ExcludeFilters&: ExcludeSymbolFilters); |
| 285 | } |
| 286 | |
| 287 | bool LinePrinter::IsCompilandExcluded(llvm::StringRef CompilandName) { |
| 288 | return IsItemExcluded(Item: CompilandName, IncludeFilters&: IncludeCompilandFilters, |
| 289 | ExcludeFilters&: ExcludeCompilandFilters); |
| 290 | } |
| 291 | |
| 292 | WithColor::WithColor(LinePrinter &P, PDB_ColorItem C) |
| 293 | : OS(P.OS), UseColor(P.hasColor()) { |
| 294 | if (UseColor) |
| 295 | applyColor(C); |
| 296 | } |
| 297 | |
| 298 | WithColor::~WithColor() { |
| 299 | if (UseColor) |
| 300 | OS.resetColor(); |
| 301 | } |
| 302 | |
| 303 | void WithColor::applyColor(PDB_ColorItem C) { |
| 304 | switch (C) { |
| 305 | case PDB_ColorItem::None: |
| 306 | OS.resetColor(); |
| 307 | return; |
| 308 | case PDB_ColorItem::Comment: |
| 309 | OS.changeColor(Color: raw_ostream::GREEN, Bold: false); |
| 310 | return; |
| 311 | case PDB_ColorItem::Address: |
| 312 | OS.changeColor(Color: raw_ostream::YELLOW, /*bold=*/Bold: true); |
| 313 | return; |
| 314 | case PDB_ColorItem::Keyword: |
| 315 | OS.changeColor(Color: raw_ostream::MAGENTA, Bold: true); |
| 316 | return; |
| 317 | case PDB_ColorItem::Register: |
| 318 | case PDB_ColorItem::Offset: |
| 319 | OS.changeColor(Color: raw_ostream::YELLOW, Bold: false); |
| 320 | return; |
| 321 | case PDB_ColorItem::Type: |
| 322 | OS.changeColor(Color: raw_ostream::CYAN, Bold: true); |
| 323 | return; |
| 324 | case PDB_ColorItem::Identifier: |
| 325 | OS.changeColor(Color: raw_ostream::CYAN, Bold: false); |
| 326 | return; |
| 327 | case PDB_ColorItem::Path: |
| 328 | OS.changeColor(Color: raw_ostream::CYAN, Bold: false); |
| 329 | return; |
| 330 | case PDB_ColorItem::Padding: |
| 331 | case PDB_ColorItem::SectionHeader: |
| 332 | OS.changeColor(Color: raw_ostream::RED, Bold: true); |
| 333 | return; |
| 334 | case PDB_ColorItem::LiteralValue: |
| 335 | OS.changeColor(Color: raw_ostream::GREEN, Bold: true); |
| 336 | return; |
| 337 | } |
| 338 | } |
| 339 | |