xref: /freebsd/contrib/llvm-project/llvm/lib/DebugInfo/LogicalView/LVReaderHandler.cpp (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
1bdd1243dSDimitry Andric //===-- LVReaderHandler.cpp -----------------------------------------------===//
2bdd1243dSDimitry Andric //
3bdd1243dSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4bdd1243dSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5bdd1243dSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6bdd1243dSDimitry Andric //
7bdd1243dSDimitry Andric //===----------------------------------------------------------------------===//
8bdd1243dSDimitry Andric //
9bdd1243dSDimitry Andric // This class implements the Reader Handler.
10bdd1243dSDimitry Andric //
11bdd1243dSDimitry Andric //===----------------------------------------------------------------------===//
12bdd1243dSDimitry Andric 
13bdd1243dSDimitry Andric #include "llvm/DebugInfo/LogicalView/LVReaderHandler.h"
14*06c3fb27SDimitry Andric #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
15bdd1243dSDimitry Andric #include "llvm/DebugInfo/LogicalView/Core/LVCompare.h"
16*06c3fb27SDimitry Andric #include "llvm/DebugInfo/LogicalView/Readers/LVCodeViewReader.h"
17bdd1243dSDimitry Andric #include "llvm/DebugInfo/LogicalView/Readers/LVELFReader.h"
18*06c3fb27SDimitry Andric #include "llvm/DebugInfo/PDB/Native/NativeSession.h"
19*06c3fb27SDimitry Andric #include "llvm/DebugInfo/PDB/PDB.h"
20*06c3fb27SDimitry Andric #include "llvm/Object/COFF.h"
21bdd1243dSDimitry Andric 
22bdd1243dSDimitry Andric using namespace llvm;
23bdd1243dSDimitry Andric using namespace llvm::object;
24bdd1243dSDimitry Andric using namespace llvm::pdb;
25bdd1243dSDimitry Andric using namespace llvm::logicalview;
26bdd1243dSDimitry Andric 
27bdd1243dSDimitry Andric #define DEBUG_TYPE "ReaderHandler"
28bdd1243dSDimitry Andric 
29bdd1243dSDimitry Andric Error LVReaderHandler::process() {
30bdd1243dSDimitry Andric   if (Error Err = createReaders())
31bdd1243dSDimitry Andric     return Err;
32bdd1243dSDimitry Andric   if (Error Err = printReaders())
33bdd1243dSDimitry Andric     return Err;
34bdd1243dSDimitry Andric   if (Error Err = compareReaders())
35bdd1243dSDimitry Andric     return Err;
36bdd1243dSDimitry Andric 
37bdd1243dSDimitry Andric   return Error::success();
38bdd1243dSDimitry Andric }
39bdd1243dSDimitry Andric 
40bdd1243dSDimitry Andric Error LVReaderHandler::createReader(StringRef Filename, LVReaders &Readers,
41bdd1243dSDimitry Andric                                     PdbOrObj &Input, StringRef FileFormatName,
42bdd1243dSDimitry Andric                                     StringRef ExePath) {
43*06c3fb27SDimitry Andric   auto CreateOneReader = [&]() -> std::unique_ptr<LVReader> {
44*06c3fb27SDimitry Andric     if (isa<ObjectFile *>(Input)) {
45*06c3fb27SDimitry Andric       ObjectFile &Obj = *cast<ObjectFile *>(Input);
46*06c3fb27SDimitry Andric       if (Obj.isCOFF()) {
47*06c3fb27SDimitry Andric         COFFObjectFile *COFF = cast<COFFObjectFile>(&Obj);
48*06c3fb27SDimitry Andric         return std::make_unique<LVCodeViewReader>(Filename, FileFormatName,
49*06c3fb27SDimitry Andric                                                   *COFF, W, ExePath);
50*06c3fb27SDimitry Andric       }
51bdd1243dSDimitry Andric       if (Obj.isELF() || Obj.isMachO())
52*06c3fb27SDimitry Andric         return std::make_unique<LVELFReader>(Filename, FileFormatName, Obj, W);
53*06c3fb27SDimitry Andric     }
54*06c3fb27SDimitry Andric     if (isa<PDBFile *>(Input)) {
55*06c3fb27SDimitry Andric       PDBFile &Pdb = *cast<PDBFile *>(Input);
56*06c3fb27SDimitry Andric       return std::make_unique<LVCodeViewReader>(Filename, FileFormatName, Pdb,
57*06c3fb27SDimitry Andric                                                 W, ExePath);
58bdd1243dSDimitry Andric     }
59bdd1243dSDimitry Andric     return nullptr;
60bdd1243dSDimitry Andric   };
61bdd1243dSDimitry Andric 
62*06c3fb27SDimitry Andric   std::unique_ptr<LVReader> ReaderObj = CreateOneReader();
63*06c3fb27SDimitry Andric   if (!ReaderObj)
64bdd1243dSDimitry Andric     return createStringError(errc::invalid_argument,
65bdd1243dSDimitry Andric                              "unable to create reader for: '%s'",
66bdd1243dSDimitry Andric                              Filename.str().c_str());
67bdd1243dSDimitry Andric 
68*06c3fb27SDimitry Andric   LVReader *Reader = ReaderObj.get();
69*06c3fb27SDimitry Andric   Readers.emplace_back(std::move(ReaderObj));
70bdd1243dSDimitry Andric   return Reader->doLoad();
71bdd1243dSDimitry Andric }
72bdd1243dSDimitry Andric 
73bdd1243dSDimitry Andric Error LVReaderHandler::handleArchive(LVReaders &Readers, StringRef Filename,
74bdd1243dSDimitry Andric                                      Archive &Arch) {
75bdd1243dSDimitry Andric   Error Err = Error::success();
76bdd1243dSDimitry Andric   for (const Archive::Child &Child : Arch.children(Err)) {
77bdd1243dSDimitry Andric     Expected<MemoryBufferRef> BuffOrErr = Child.getMemoryBufferRef();
78bdd1243dSDimitry Andric     if (Error Err = BuffOrErr.takeError())
79bdd1243dSDimitry Andric       return createStringError(errorToErrorCode(std::move(Err)), "%s",
80bdd1243dSDimitry Andric                                Filename.str().c_str());
81bdd1243dSDimitry Andric     Expected<StringRef> NameOrErr = Child.getName();
82bdd1243dSDimitry Andric     if (Error Err = NameOrErr.takeError())
83bdd1243dSDimitry Andric       return createStringError(errorToErrorCode(std::move(Err)), "%s",
84bdd1243dSDimitry Andric                                Filename.str().c_str());
85bdd1243dSDimitry Andric     std::string Name = (Filename + "(" + NameOrErr.get() + ")").str();
86bdd1243dSDimitry Andric     if (Error Err = handleBuffer(Readers, Name, BuffOrErr.get()))
87bdd1243dSDimitry Andric       return createStringError(errorToErrorCode(std::move(Err)), "%s",
88bdd1243dSDimitry Andric                                Filename.str().c_str());
89bdd1243dSDimitry Andric   }
90bdd1243dSDimitry Andric 
91bdd1243dSDimitry Andric   return Error::success();
92bdd1243dSDimitry Andric }
93bdd1243dSDimitry Andric 
94*06c3fb27SDimitry Andric // Search for a matching executable image for the given PDB path.
95*06c3fb27SDimitry Andric static std::string searchForExe(const StringRef Path,
96*06c3fb27SDimitry Andric                                 const StringRef Extension) {
97*06c3fb27SDimitry Andric   SmallString<128> ExePath(Path);
98*06c3fb27SDimitry Andric   llvm::sys::path::replace_extension(ExePath, Extension);
99*06c3fb27SDimitry Andric 
100*06c3fb27SDimitry Andric   std::unique_ptr<IPDBSession> Session;
101*06c3fb27SDimitry Andric   if (Error Err = loadDataForEXE(PDB_ReaderType::Native, ExePath, Session)) {
102*06c3fb27SDimitry Andric     consumeError(std::move(Err));
103*06c3fb27SDimitry Andric     return {};
104*06c3fb27SDimitry Andric   }
105*06c3fb27SDimitry Andric   // We have a candidate for the executable image.
106*06c3fb27SDimitry Andric   Expected<std::string> PdbPathOrErr = NativeSession::searchForPdb({ExePath});
107*06c3fb27SDimitry Andric   if (!PdbPathOrErr) {
108*06c3fb27SDimitry Andric     consumeError(PdbPathOrErr.takeError());
109*06c3fb27SDimitry Andric     return {};
110*06c3fb27SDimitry Andric   }
111*06c3fb27SDimitry Andric   // Convert any Windows backslashes into forward slashes to get the path.
112*06c3fb27SDimitry Andric   std::string ConvertedPath = sys::path::convert_to_slash(
113*06c3fb27SDimitry Andric       PdbPathOrErr.get(), sys::path::Style::windows);
114*06c3fb27SDimitry Andric   if (ConvertedPath == Path)
115*06c3fb27SDimitry Andric     return std::string(ExePath);
116*06c3fb27SDimitry Andric 
117*06c3fb27SDimitry Andric   return {};
118*06c3fb27SDimitry Andric }
119*06c3fb27SDimitry Andric 
120*06c3fb27SDimitry Andric // Search for a matching object image for the given PDB path.
121*06c3fb27SDimitry Andric static std::string searchForObj(const StringRef Path,
122*06c3fb27SDimitry Andric                                 const StringRef Extension) {
123*06c3fb27SDimitry Andric   SmallString<128> ObjPath(Path);
124*06c3fb27SDimitry Andric   llvm::sys::path::replace_extension(ObjPath, Extension);
125*06c3fb27SDimitry Andric   if (llvm::sys::fs::exists(ObjPath)) {
126*06c3fb27SDimitry Andric     ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
127*06c3fb27SDimitry Andric         MemoryBuffer::getFileOrSTDIN(ObjPath);
128*06c3fb27SDimitry Andric     if (!BuffOrErr)
129*06c3fb27SDimitry Andric       return {};
130*06c3fb27SDimitry Andric     return std::string(ObjPath);
131*06c3fb27SDimitry Andric   }
132*06c3fb27SDimitry Andric 
133*06c3fb27SDimitry Andric   return {};
134*06c3fb27SDimitry Andric }
135*06c3fb27SDimitry Andric 
136bdd1243dSDimitry Andric Error LVReaderHandler::handleBuffer(LVReaders &Readers, StringRef Filename,
137bdd1243dSDimitry Andric                                     MemoryBufferRef Buffer, StringRef ExePath) {
138*06c3fb27SDimitry Andric   // As PDB does not support the Binary interface, at this point we can check
139*06c3fb27SDimitry Andric   // if the buffer corresponds to a PDB or PE file.
140*06c3fb27SDimitry Andric   file_magic FileMagic = identify_magic(Buffer.getBuffer());
141*06c3fb27SDimitry Andric   if (FileMagic == file_magic::pdb) {
142*06c3fb27SDimitry Andric     if (!ExePath.empty())
143*06c3fb27SDimitry Andric       return handleObject(Readers, Filename, Buffer.getBuffer(), ExePath);
144*06c3fb27SDimitry Andric 
145*06c3fb27SDimitry Andric     // Search in the directory derived from the given 'Filename' for a
146*06c3fb27SDimitry Andric     // matching object file (.o, .obj, .lib) or a matching executable file
147*06c3fb27SDimitry Andric     // (.exe/.dll) and try to create the reader based on the matched file.
148*06c3fb27SDimitry Andric     // If no matching file is found then we load the original PDB file.
149*06c3fb27SDimitry Andric     std::vector<StringRef> ExecutableExtensions = {"exe", "dll"};
150*06c3fb27SDimitry Andric     for (StringRef Extension : ExecutableExtensions) {
151*06c3fb27SDimitry Andric       std::string ExecutableImage = searchForExe(Filename, Extension);
152*06c3fb27SDimitry Andric       if (ExecutableImage.empty())
153*06c3fb27SDimitry Andric         continue;
154*06c3fb27SDimitry Andric       if (Error Err = handleObject(Readers, Filename, Buffer.getBuffer(),
155*06c3fb27SDimitry Andric                                    ExecutableImage)) {
156*06c3fb27SDimitry Andric         consumeError(std::move(Err));
157*06c3fb27SDimitry Andric         continue;
158*06c3fb27SDimitry Andric       }
159*06c3fb27SDimitry Andric       return Error::success();
160*06c3fb27SDimitry Andric     }
161*06c3fb27SDimitry Andric 
162*06c3fb27SDimitry Andric     std::vector<StringRef> ObjectExtensions = {"o", "obj", "lib"};
163*06c3fb27SDimitry Andric     for (StringRef Extension : ObjectExtensions) {
164*06c3fb27SDimitry Andric       std::string ObjectImage = searchForObj(Filename, Extension);
165*06c3fb27SDimitry Andric       if (ObjectImage.empty())
166*06c3fb27SDimitry Andric         continue;
167*06c3fb27SDimitry Andric       if (Error Err = handleFile(Readers, ObjectImage)) {
168*06c3fb27SDimitry Andric         consumeError(std::move(Err));
169*06c3fb27SDimitry Andric         continue;
170*06c3fb27SDimitry Andric       }
171*06c3fb27SDimitry Andric       return Error::success();
172*06c3fb27SDimitry Andric     }
173*06c3fb27SDimitry Andric 
174*06c3fb27SDimitry Andric     // No matching executable/object image was found. Load the given PDB.
175*06c3fb27SDimitry Andric     return handleObject(Readers, Filename, Buffer.getBuffer(), ExePath);
176*06c3fb27SDimitry Andric   }
177*06c3fb27SDimitry Andric   if (FileMagic == file_magic::pecoff_executable) {
178*06c3fb27SDimitry Andric     // If we have a valid executable, try to find a matching PDB file.
179*06c3fb27SDimitry Andric     Expected<std::string> PdbPath = NativeSession::searchForPdb({Filename});
180*06c3fb27SDimitry Andric     if (errorToErrorCode(PdbPath.takeError())) {
181*06c3fb27SDimitry Andric       return createStringError(
182*06c3fb27SDimitry Andric           errc::not_supported,
183*06c3fb27SDimitry Andric           "Binary object format in '%s' does not have debug info.",
184*06c3fb27SDimitry Andric           Filename.str().c_str());
185*06c3fb27SDimitry Andric     }
186*06c3fb27SDimitry Andric     // Process the matching PDB file and pass the executable filename.
187*06c3fb27SDimitry Andric     return handleFile(Readers, PdbPath.get(), Filename);
188*06c3fb27SDimitry Andric   }
189*06c3fb27SDimitry Andric 
190bdd1243dSDimitry Andric   Expected<std::unique_ptr<Binary>> BinOrErr = createBinary(Buffer);
191bdd1243dSDimitry Andric   if (errorToErrorCode(BinOrErr.takeError())) {
192bdd1243dSDimitry Andric     return createStringError(errc::not_supported,
193bdd1243dSDimitry Andric                              "Binary object format in '%s' is not supported.",
194bdd1243dSDimitry Andric                              Filename.str().c_str());
195bdd1243dSDimitry Andric   }
196bdd1243dSDimitry Andric   return handleObject(Readers, Filename, *BinOrErr.get());
197bdd1243dSDimitry Andric }
198bdd1243dSDimitry Andric 
199bdd1243dSDimitry Andric Error LVReaderHandler::handleFile(LVReaders &Readers, StringRef Filename,
200bdd1243dSDimitry Andric                                   StringRef ExePath) {
201bdd1243dSDimitry Andric   // Convert any Windows backslashes into forward slashes to get the path.
202bdd1243dSDimitry Andric   std::string ConvertedPath =
203bdd1243dSDimitry Andric       sys::path::convert_to_slash(Filename, sys::path::Style::windows);
204bdd1243dSDimitry Andric   ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
205bdd1243dSDimitry Andric       MemoryBuffer::getFileOrSTDIN(ConvertedPath);
206bdd1243dSDimitry Andric   if (BuffOrErr.getError()) {
207bdd1243dSDimitry Andric     return createStringError(errc::bad_file_descriptor,
208bdd1243dSDimitry Andric                              "File '%s' does not exist.",
209bdd1243dSDimitry Andric                              ConvertedPath.c_str());
210bdd1243dSDimitry Andric   }
211bdd1243dSDimitry Andric   std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
212bdd1243dSDimitry Andric   return handleBuffer(Readers, ConvertedPath, *Buffer, ExePath);
213bdd1243dSDimitry Andric }
214bdd1243dSDimitry Andric 
215bdd1243dSDimitry Andric Error LVReaderHandler::handleMach(LVReaders &Readers, StringRef Filename,
216bdd1243dSDimitry Andric                                   MachOUniversalBinary &Mach) {
217bdd1243dSDimitry Andric   for (const MachOUniversalBinary::ObjectForArch &ObjForArch : Mach.objects()) {
218bdd1243dSDimitry Andric     std::string ObjName = (Twine(Filename) + Twine("(") +
219bdd1243dSDimitry Andric                            Twine(ObjForArch.getArchFlagName()) + Twine(")"))
220bdd1243dSDimitry Andric                               .str();
221bdd1243dSDimitry Andric     if (Expected<std::unique_ptr<MachOObjectFile>> MachOOrErr =
222bdd1243dSDimitry Andric             ObjForArch.getAsObjectFile()) {
223bdd1243dSDimitry Andric       MachOObjectFile &Obj = **MachOOrErr;
224bdd1243dSDimitry Andric       PdbOrObj Input = &Obj;
225bdd1243dSDimitry Andric       if (Error Err =
226bdd1243dSDimitry Andric               createReader(Filename, Readers, Input, Obj.getFileFormatName()))
227bdd1243dSDimitry Andric         return Err;
228bdd1243dSDimitry Andric       continue;
229bdd1243dSDimitry Andric     } else
230bdd1243dSDimitry Andric       consumeError(MachOOrErr.takeError());
231bdd1243dSDimitry Andric     if (Expected<std::unique_ptr<Archive>> ArchiveOrErr =
232bdd1243dSDimitry Andric             ObjForArch.getAsArchive()) {
233bdd1243dSDimitry Andric       if (Error Err = handleArchive(Readers, ObjName, *ArchiveOrErr.get()))
234bdd1243dSDimitry Andric         return Err;
235bdd1243dSDimitry Andric       continue;
236bdd1243dSDimitry Andric     } else
237bdd1243dSDimitry Andric       consumeError(ArchiveOrErr.takeError());
238bdd1243dSDimitry Andric   }
239bdd1243dSDimitry Andric   return Error::success();
240bdd1243dSDimitry Andric }
241bdd1243dSDimitry Andric 
242bdd1243dSDimitry Andric Error LVReaderHandler::handleObject(LVReaders &Readers, StringRef Filename,
243bdd1243dSDimitry Andric                                     Binary &Binary) {
244bdd1243dSDimitry Andric   if (PdbOrObj Input = dyn_cast<ObjectFile>(&Binary))
245bdd1243dSDimitry Andric     return createReader(Filename, Readers, Input,
246*06c3fb27SDimitry Andric                         cast<ObjectFile *>(Input)->getFileFormatName());
247bdd1243dSDimitry Andric 
248bdd1243dSDimitry Andric   if (MachOUniversalBinary *Fat = dyn_cast<MachOUniversalBinary>(&Binary))
249bdd1243dSDimitry Andric     return handleMach(Readers, Filename, *Fat);
250bdd1243dSDimitry Andric 
251bdd1243dSDimitry Andric   if (Archive *Arch = dyn_cast<Archive>(&Binary))
252bdd1243dSDimitry Andric     return handleArchive(Readers, Filename, *Arch);
253bdd1243dSDimitry Andric 
254bdd1243dSDimitry Andric   return createStringError(errc::not_supported,
255bdd1243dSDimitry Andric                            "Binary object format in '%s' is not supported.",
256bdd1243dSDimitry Andric                            Filename.str().c_str());
257bdd1243dSDimitry Andric }
258bdd1243dSDimitry Andric 
259*06c3fb27SDimitry Andric Error LVReaderHandler::handleObject(LVReaders &Readers, StringRef Filename,
260*06c3fb27SDimitry Andric                                     StringRef Buffer, StringRef ExePath) {
261*06c3fb27SDimitry Andric   std::unique_ptr<IPDBSession> Session;
262*06c3fb27SDimitry Andric   if (Error Err = loadDataForPDB(PDB_ReaderType::Native, Filename, Session))
263*06c3fb27SDimitry Andric     return createStringError(errorToErrorCode(std::move(Err)), "%s",
264*06c3fb27SDimitry Andric                              Filename.str().c_str());
265*06c3fb27SDimitry Andric 
266*06c3fb27SDimitry Andric   std::unique_ptr<NativeSession> PdbSession;
267*06c3fb27SDimitry Andric   PdbSession.reset(static_cast<NativeSession *>(Session.release()));
268*06c3fb27SDimitry Andric   PdbOrObj Input = &PdbSession->getPDBFile();
269*06c3fb27SDimitry Andric   StringRef FileFormatName;
270*06c3fb27SDimitry Andric   size_t Pos = Buffer.find_first_of("\r\n");
271*06c3fb27SDimitry Andric   if (Pos)
272*06c3fb27SDimitry Andric     FileFormatName = Buffer.substr(0, Pos - 1);
273*06c3fb27SDimitry Andric   return createReader(Filename, Readers, Input, FileFormatName, ExePath);
274*06c3fb27SDimitry Andric }
275*06c3fb27SDimitry Andric 
276bdd1243dSDimitry Andric Error LVReaderHandler::createReaders() {
277bdd1243dSDimitry Andric   LLVM_DEBUG(dbgs() << "createReaders\n");
278bdd1243dSDimitry Andric   for (std::string &Object : Objects) {
279bdd1243dSDimitry Andric     LVReaders Readers;
280bdd1243dSDimitry Andric     if (Error Err = createReader(Object, Readers))
281bdd1243dSDimitry Andric       return Err;
282*06c3fb27SDimitry Andric     TheReaders.insert(TheReaders.end(),
283*06c3fb27SDimitry Andric                       std::make_move_iterator(Readers.begin()),
284*06c3fb27SDimitry Andric                       std::make_move_iterator(Readers.end()));
285bdd1243dSDimitry Andric   }
286bdd1243dSDimitry Andric 
287bdd1243dSDimitry Andric   return Error::success();
288bdd1243dSDimitry Andric }
289bdd1243dSDimitry Andric 
290bdd1243dSDimitry Andric Error LVReaderHandler::printReaders() {
291bdd1243dSDimitry Andric   LLVM_DEBUG(dbgs() << "printReaders\n");
292bdd1243dSDimitry Andric   if (options().getPrintExecute())
293*06c3fb27SDimitry Andric     for (const std::unique_ptr<LVReader> &Reader : TheReaders)
294bdd1243dSDimitry Andric       if (Error Err = Reader->doPrint())
295bdd1243dSDimitry Andric         return Err;
296bdd1243dSDimitry Andric 
297bdd1243dSDimitry Andric   return Error::success();
298bdd1243dSDimitry Andric }
299bdd1243dSDimitry Andric 
300bdd1243dSDimitry Andric Error LVReaderHandler::compareReaders() {
301bdd1243dSDimitry Andric   LLVM_DEBUG(dbgs() << "compareReaders\n");
302bdd1243dSDimitry Andric   size_t ReadersCount = TheReaders.size();
303bdd1243dSDimitry Andric   if (options().getCompareExecute() && ReadersCount >= 2) {
304bdd1243dSDimitry Andric     // If we have more than 2 readers, compare them by pairs.
305bdd1243dSDimitry Andric     size_t ViewPairs = ReadersCount / 2;
306bdd1243dSDimitry Andric     LVCompare Compare(OS);
307bdd1243dSDimitry Andric     for (size_t Pair = 0, Index = 0; Pair < ViewPairs; ++Pair) {
308*06c3fb27SDimitry Andric       if (Error Err = Compare.execute(TheReaders[Index].get(),
309*06c3fb27SDimitry Andric                                       TheReaders[Index + 1].get()))
310bdd1243dSDimitry Andric         return Err;
311bdd1243dSDimitry Andric       Index += 2;
312bdd1243dSDimitry Andric     }
313bdd1243dSDimitry Andric   }
314bdd1243dSDimitry Andric 
315bdd1243dSDimitry Andric   return Error::success();
316bdd1243dSDimitry Andric }
317bdd1243dSDimitry Andric 
318bdd1243dSDimitry Andric void LVReaderHandler::print(raw_ostream &OS) const { OS << "ReaderHandler\n"; }
319