forked from OSchip/llvm-project
247 lines
8.3 KiB
C++
247 lines
8.3 KiB
C++
|
//===--- DebugIR.cpp - Transform debug metadata to allow debugging IR -----===//
|
||
|
//
|
||
|
// The LLVM Compiler Infrastructure
|
||
|
//
|
||
|
// This file is distributed under the University of Illinois Open Source
|
||
|
// License. See LICENSE.TXT for details.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// A Module transform pass that emits a succinct version of the IR and replaces
|
||
|
// the source file metadata to allow debuggers to step through the IR.
|
||
|
//
|
||
|
// The location where the IR file is emitted is the same as the directory
|
||
|
// operand of the !llvm.dbg.cu metadata node present in the input module. The
|
||
|
// file name is constructed from the original file name by stripping the
|
||
|
// extension and replacing it with "-debug.ll" or the Postfix string specified
|
||
|
// at construction.
|
||
|
//
|
||
|
// FIXME: instead of replacing debug metadata, additional metadata should be
|
||
|
// used to point capable debuggers to the IR file without destroying the
|
||
|
// mapping to the original source file.
|
||
|
//
|
||
|
// FIXME: this pass should not depend on the existance of debug metadata in
|
||
|
// the module as it does now. Instead, it should use DIBuilder to create the
|
||
|
// required metadata.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include <string>
|
||
|
|
||
|
#include "llvm/ADT/ArrayRef.h"
|
||
|
#include "llvm/ADT/SmallSet.h"
|
||
|
#include "llvm/DebugInfo.h"
|
||
|
#include "llvm/DIBuilder.h"
|
||
|
#include "llvm/IR/AsmWriter.h"
|
||
|
#include "llvm/IR/Instruction.h"
|
||
|
#include "llvm/IR/IntrinsicInst.h"
|
||
|
#include "llvm/IR/Module.h"
|
||
|
#include "llvm/Pass.h"
|
||
|
#include "llvm/Transforms/Instrumentation.h"
|
||
|
#include "llvm/Support/Debug.h"
|
||
|
#include "llvm/Support/ToolOutputFile.h"
|
||
|
#include "llvm/Support/raw_ostream.h"
|
||
|
using namespace llvm;
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
/// Returns true if Node's name contains the string "llvm.dbg"
|
||
|
bool isDebugNamedMetadata(const NamedMDNode *Node) {
|
||
|
return Node->getName().str().find("llvm.dbg") != std::string::npos;
|
||
|
}
|
||
|
|
||
|
/// Returns true if Inst is a call to llvm.dbg.value or llvm.dbg.declare
|
||
|
bool isDebugIntrinsic(const IntrinsicInst *Inst) {
|
||
|
Intrinsic::ID id = Inst->getIntrinsicID();
|
||
|
return id == Intrinsic::dbg_value || id == Intrinsic::dbg_declare;
|
||
|
}
|
||
|
|
||
|
/// An AssemblyWriter which generates a succinct representation of the module
|
||
|
/// (without debug intrinsics or metadata) suitable for debugging. As IR
|
||
|
/// instructions are printed, !dbg metadata nodes are added (or updated)
|
||
|
/// to point to the corresponding line in the generated IR file instead
|
||
|
/// of the original source file. The input module must have been constructed
|
||
|
/// with debug metadata (i.e. clang -g).
|
||
|
class IRDebugInfoHelper : public llvm::AssemblyWriter {
|
||
|
/// Directory of debug metadata
|
||
|
const DebugInfoFinder &Finder;
|
||
|
|
||
|
/// Flags to control the verbosity of the generated IR file
|
||
|
bool hideDebugIntrinsics;
|
||
|
bool hideDebugMetadata;
|
||
|
|
||
|
/// Set to track metadata nodes to be printed (used only when
|
||
|
/// printDebugMetadata == false)
|
||
|
SmallSet<const MDNode *, 4> NonDebugNodes;
|
||
|
|
||
|
public:
|
||
|
IRDebugInfoHelper(
|
||
|
formatted_raw_ostream &o, const Module *M,
|
||
|
AssemblyAnnotationWriter *AAW, const DebugInfoFinder &Finder,
|
||
|
bool hideDebugIntrinsics = true, bool hideDebugMetadata = true)
|
||
|
: AssemblyWriter(o, M, AAW), Finder(Finder),
|
||
|
hideDebugIntrinsics(hideDebugIntrinsics),
|
||
|
hideDebugMetadata(hideDebugMetadata) {}
|
||
|
|
||
|
private:
|
||
|
virtual void printInstruction(const Instruction &I) {
|
||
|
DebugLoc Loc(I.getDebugLoc());
|
||
|
|
||
|
if (hideDebugMetadata)
|
||
|
removeDebugMetadata(const_cast<Instruction &>(I));
|
||
|
|
||
|
AssemblyWriter::printInstruction(I);
|
||
|
Out.flush();
|
||
|
// Adjust line number by 1 because we have not yet printed the \n
|
||
|
unsigned Line = Out.getLine() + 1;
|
||
|
|
||
|
DebugLoc NewLoc;
|
||
|
if (!Loc.isUnknown())
|
||
|
// I had a previous debug location: re-use the DebugLoc
|
||
|
NewLoc = DebugLoc::get(Line, /* FIXME: support columns */ 0,
|
||
|
Loc.getScope(I.getContext()),
|
||
|
Loc.getInlinedAt(I.getContext()));
|
||
|
else if (MDNode *scope = findFunctionMD(I.getParent()->getParent()))
|
||
|
// I had no previous debug location, but M has some debug information
|
||
|
NewLoc = DebugLoc::get(Line, 0, scope, /*FIXME: inlined instructions*/ 0);
|
||
|
else
|
||
|
// Neither I nor M has any debug information -- nothing to do here.
|
||
|
// FIXME: support debugging of undecorated IR (generated by clang without
|
||
|
// the -g option)
|
||
|
return;
|
||
|
|
||
|
if (hideDebugMetadata)
|
||
|
saveNonDebugMetadata(I);
|
||
|
|
||
|
addDebugLocation(const_cast<Instruction &>(I), NewLoc);
|
||
|
}
|
||
|
|
||
|
virtual void printInstructionLine(const Instruction &I) {
|
||
|
if (hideDebugIntrinsics)
|
||
|
if (const IntrinsicInst *II = dyn_cast<IntrinsicInst>(&I))
|
||
|
if (isDebugIntrinsic(II))
|
||
|
return;
|
||
|
AssemblyWriter::printInstructionLine(I);
|
||
|
}
|
||
|
|
||
|
virtual void writeMDNode(unsigned Slot, const MDNode *Node) {
|
||
|
if (hideDebugMetadata == false || isDebugMetadata(Node) == false)
|
||
|
AssemblyWriter::writeMDNode(Slot, Node);
|
||
|
}
|
||
|
|
||
|
virtual void printNamedMDNode(const NamedMDNode *NMD) {
|
||
|
if (hideDebugMetadata == false || isDebugNamedMetadata(NMD) == false)
|
||
|
AssemblyWriter::printNamedMDNode(NMD);
|
||
|
}
|
||
|
|
||
|
/// Returns the MDNode that corresponds with F
|
||
|
MDNode *findFunctionMD(const Function *F) {
|
||
|
for (DebugInfoFinder::iterator i = Finder.subprogram_begin(),
|
||
|
e = Finder.subprogram_end();
|
||
|
i != e; ++i) {
|
||
|
DISubprogram S(*i);
|
||
|
if (S.getFunction() == F)
|
||
|
return *i;
|
||
|
}
|
||
|
// cannot find F -- likely means there is no debug information
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// Saves all non-debug metadata attached to I
|
||
|
void saveNonDebugMetadata(const Instruction &I) {
|
||
|
typedef SmallVector<std::pair<unsigned, MDNode *>, 4> MDNodeVector;
|
||
|
MDNodeVector Others;
|
||
|
I.getAllMetadataOtherThanDebugLoc(Others);
|
||
|
for (MDNodeVector::iterator i = Others.begin(), e = Others.end(); i != e;
|
||
|
++i)
|
||
|
NonDebugNodes.insert(i->second);
|
||
|
}
|
||
|
|
||
|
/// Returns true if Node was not saved as non-debug metadata with
|
||
|
/// saveNonDebugMetadata(), false otherwise.
|
||
|
bool isDebugMetadata(const MDNode *Node) {
|
||
|
return NonDebugNodes.count(Node) == 0;
|
||
|
}
|
||
|
|
||
|
void removeDebugMetadata(Instruction &I) {
|
||
|
if (I.getMetadata(LLVMContext::MD_dbg))
|
||
|
I.setMetadata(LLVMContext::MD_dbg, 0);
|
||
|
}
|
||
|
|
||
|
void addDebugLocation(Instruction &I, DebugLoc Loc) {
|
||
|
MDNode *MD = Loc.getAsMDNode(I.getContext());
|
||
|
I.setMetadata(LLVMContext::MD_dbg, MD);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class DebugIR : public ModulePass {
|
||
|
std::string Postfix;
|
||
|
std::string Filename;
|
||
|
DebugInfoFinder Finder;
|
||
|
|
||
|
public:
|
||
|
static char ID;
|
||
|
|
||
|
DebugIR() : ModulePass(ID), Postfix("-debug.ll") {}
|
||
|
|
||
|
/// Customize the postfix string used to replace the extension of the
|
||
|
/// original filename that appears in the !llvm.dbg.cu metadata node.
|
||
|
DebugIR(StringRef postfix) : ModulePass(ID), Postfix(postfix) {}
|
||
|
|
||
|
private:
|
||
|
// Modify the filename embedded in the Compilation-Unit debug information of M
|
||
|
bool replaceFilename(Module &M) {
|
||
|
bool changed = false;
|
||
|
|
||
|
// Sanity check -- if llvm.dbg.cu node exists, the DebugInfoFinder
|
||
|
// better have found at least one CU!
|
||
|
if (M.getNamedMetadata("llvm.dbg.cu"))
|
||
|
assert(Finder.compile_unit_count() > 0 &&
|
||
|
"Found no compile units but llvm.dbg.cu node exists");
|
||
|
|
||
|
for (DebugInfoFinder::iterator i = Finder.compile_unit_begin(),
|
||
|
e = Finder.compile_unit_end();
|
||
|
i != e; ++i) {
|
||
|
DICompileUnit CU(*i);
|
||
|
Filename = CU.getFilename();
|
||
|
|
||
|
// Replace extension with postfix
|
||
|
size_t dot = Filename.find_last_of(".");
|
||
|
if (dot != std::string::npos)
|
||
|
Filename.erase(dot);
|
||
|
Filename += Postfix;
|
||
|
|
||
|
CU.setFilename(Filename, M.getContext());
|
||
|
changed = true;
|
||
|
}
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
void writeAndUpdateDebugIRFile(Module *M) {
|
||
|
std::string error;
|
||
|
tool_output_file OutFile(Filename.c_str(), error);
|
||
|
OutFile.keep();
|
||
|
formatted_raw_ostream OS;
|
||
|
OS.setStream(OutFile.os(), false);
|
||
|
|
||
|
IRDebugInfoHelper W(OS, M, 0, Finder);
|
||
|
W.printModule(M);
|
||
|
}
|
||
|
|
||
|
bool runOnModule(Module &M) {
|
||
|
Finder.processModule(M);
|
||
|
bool changed = replaceFilename(M);
|
||
|
if (changed)
|
||
|
writeAndUpdateDebugIRFile(&M);
|
||
|
return changed;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // anonymous namespace
|
||
|
|
||
|
char DebugIR::ID = 0;
|
||
|
INITIALIZE_PASS(DebugIR, "debug-ir", "Enable debugging IR", false, false)
|
||
|
ModulePass *llvm::createDebugIRPass(StringRef FilenamePostfix) {
|
||
|
return new DebugIR(FilenamePostfix);
|
||
|
}
|