chigraph  master
Systems programming language written for beginners in LLVM
Debugger.cpp
Go to the documentation of this file.
1 
3 #include "chi/Debugger/Debugger.hpp"
4 
5 #include <chi/Context.hpp>
6 #include <chi/LLVMVersion.hpp>
7 #include <chi/NameMangler.hpp>
8 #include <chi/NodeInstance.hpp>
9 #include <chi/Support/Result.hpp>
10 
11 #include <boost/filesystem.hpp>
12 #include <boost/uuid/uuid_io.hpp>
13 
14 #if LLVM_VERSION_LESS_EQUAL(3, 9)
15 #include <llvm/Bitcode/ReaderWriter.h>
16 #else
17 #include <llvm/Bitcode/BitcodeWriter.h>
18 #endif
19 
20 #include <llvm/IR/Module.h>
21 #include <llvm/Support/FileSystem.h>
22 #include <llvm/Support/raw_ostream.h>
23 
24 #include <lldb/API/SBListener.h>
25 #include <lldb/API/SBThread.h>
26 
27 #include <stdlib.h> // for setenv
28 
29 namespace fs = boost::filesystem;
30 
31 namespace chi {
32 
33 Debugger::Debugger(const char* pathToChig, GraphModule& mod) : mModule{&mod} {
34 // point it to lldb-server
35 #if __linux__
36  auto lldbServerPath = fs::path(pathToChig).parent_path() / "lldb-server";
37  setenv("LLDB_DEBUGSERVER_PATH", lldbServerPath.c_str(), 1);
38 #endif
39 
40  lldb::SBDebugger::Initialize();
41  mDebugger = lldb::SBDebugger::Create();
42 
43  // set the logger to stderr for testing
44  mDebugger.SetLoggingCallback(
45  [](const char* msg, void* dbg) {
46  std::cerr << msg;
47  std::cerr.flush();
48  },
49  this);
50 
51  const char* val[] = {"api", nullptr};
52  mDebugger.EnableLog("lldb", val);
53 
54  // create target
55  mTarget = mDebugger.CreateTarget(pathToChig);
56 }
57 
58 Debugger::~Debugger() { lldb::SBDebugger::Terminate(); }
59 
61  Result res;
62 
63  if (isAttached()) {
64  auto err = mProcess.Kill();
65 
66  if (err.Fail()) {
67  res.addEntry("EUKN", "Failed to terminate process",
68  {{"Error Code", err.GetError()}, {"Error String", err.GetCString()}});
69 
70  return res;
71  }
72  }
73 
74  return res;
75 }
76 
78  Result res;
79 
80  if (isAttached()) {
81  auto err = mProcess.Continue();
82 
83  if (err.Fail()) {
84  res.addEntry("EUKN", "Failed to continue process",
85  {{"Error Code", err.GetError()}, {"Error String", err.GetCString()}});
86 
87  return res;
88  }
89  }
90 
91  return res;
92 }
94  Result res;
95 
96  if (isAttached()) {
97  auto err = mProcess.Stop();
98 
99  if (err.Fail()) {
100  res.addEntry("EUKN", "Failed to pause process",
101  {{"Error Code", err.GetError()}, {"Error String", err.GetCString()}});
102 
103  return res;
104  }
105  }
106 
107  return res;
108 }
109 
110 Result Debugger::setBreakpoint(NodeInstance& node, lldb::SBBreakpoint* bp) {
111  Result res;
112 
113  int linenum = lineNumberFromNode(node);
114 
115  // create the breakpoint
116  auto breakpoint = mTarget.BreakpointCreateByLocation(
117  node.module().sourceFilePath().string().c_str(), linenum);
118 
119  // make sure that it's good
120  if (!breakpoint.IsValid()) {
121  res.addEntry("EUKN", "Could not set breakpoint on node",
122  {{"nodeid", node.stringId()},
123  {"File Name", node.module().sourceFilePath().string()},
124  {"Line Number", linenum}});
125 
126  return res;
127  }
128 
129  mBreakpoints[&node] = breakpoint;
130 
131  if (bp != nullptr) { *bp = breakpoint; }
132 
133  breakpoint.SetEnabled(true);
134 
135  return res;
136 }
137 
139  auto iter = mBreakpoints.find(&node);
140  if (iter == mBreakpoints.end()) { return false; }
141 
142  return mTarget.BreakpointDelete(iter->second.GetID());
143 }
144 
145 Result Debugger::start(const char** argv, const char** envp,
146  const boost::filesystem::path& workingDirectory) {
147  assert(boost::filesystem::is_directory(workingDirectory));
148 
149  Result res;
150 
151  if (!mTarget.IsValid()) {
152  res.addEntry("EUKN", "Cannot start a debugger process with an invalid target", {});
153  return res;
154  }
155 
156  // generate IR
157  std::unique_ptr<llvm::Module> mod;
158  {
160  if (!res) { return res; }
161  }
162 
163  // write it to a file
164  fs::path tmpIRPath;
165  {
166  tmpIRPath = boost::filesystem::temp_directory_path() / fs::unique_path();
167  std::error_code ec; // TODO: use ec
168  std::string errorString; // only for LLVM 3.5
169  llvm::raw_fd_ostream file {
170  tmpIRPath.string().c_str(),
171 #if LLVM_VERSION_LESS_EQUAL(3, 5)
172  errorString,
173 #else
174  ec,
175 #endif
176  llvm::sys::fs::F_RW
177  };
178  llvm::WriteBitcodeToFile(mod.get(), file);
179  }
180 
181  // create args
182  std::vector<const char*> args;
183  std::string tmpIRString = tmpIRPath.string();
184  {
185  args.push_back("interpret");
186 
187  args.push_back("-i");
188  args.push_back(tmpIRString.c_str());
189 
190  args.push_back("-O");
191  args.push_back("0");
192 
193  if (argv != nullptr) {
194  for (; *argv != nullptr; ++argv) { args.push_back(*argv); }
195  }
196 
197  args.push_back(nullptr);
198  }
199 
200  // start the process
201  {
202  lldb::SBError err;
203  lldb::SBListener invalidListener;
204  mProcess =
205  mTarget.Launch(invalidListener, args.data(), envp, nullptr, nullptr, nullptr,
206  workingDirectory.string().c_str(), lldb::eLaunchFlagDebug, false, err);
207 
208  if (err.Fail()) {
209  res.addEntry("EUKN", "Failed to launch process", {{"Error Message", err.GetCString()}});
210  }
211  }
212 
213  return res;
214 }
215 
216 std::vector<const NodeInstance*> Debugger::listBreakpoints() const {
217  std::vector<const NodeInstance*> ret;
218  ret.reserve(mBreakpoints.size());
219  for (const auto& bpts : mBreakpoints) { ret.push_back(bpts.first); }
220  return ret;
221 }
222 
223 lldb::SBValue Debugger::inspectNodeOutput(const NodeInstance& inst, size_t id,
224  lldb::SBFrame frame) {
225  assert(id < inst.outputDataConnections.size());
226 
227  // if frame isn't valid, use the default
228  if (!frame.IsValid()) {
229  auto thread = lldbProcess().GetSelectedThread();
230  if (!thread.IsValid()) { return {}; }
231  frame = thread.GetSelectedFrame();
232  }
233 
234  if (!frame.IsValid()) { return {}; }
235 
236  // make sure it's in scope
237  auto func = &nodeFromFrame(frame)->function();
238  if (func != &inst.function()) { return {}; }
239 
240  auto variableName = inst.stringId() + "__" + std::to_string(id);
241  return frame.FindVariable(variableName.c_str());
242 }
243 
244 NodeInstance* Debugger::nodeFromFrame(lldb::SBFrame frame) {
245  using namespace std::string_literals;
246 
247  // if frame isn't valid, use the default
248  if (!frame.IsValid()) {
249  auto thread = lldbProcess().GetSelectedThread();
250  if (!thread.IsValid()) { return {}; }
251  frame = thread.GetSelectedFrame();
252  }
253 
254  if (!frame.IsValid()) { return {}; }
255 
256  auto mangledFunctionName = frame.GetFunctionName();
257  // demangle that
258  std::string moduleName, functionName;
259  std::tie(moduleName, functionName) = unmangleFunctionName(mangledFunctionName);
260 
261  GraphFunction* func = nullptr;
262 
263  if (mangledFunctionName == "main"s) { func = module().functionFromName("main"); }
264 
265  if (func == nullptr) {
266  auto mod = static_cast<GraphModule*>(module().context().moduleByFullName(moduleName));
267  if (!mod) { return nullptr; }
268  func = mod->functionFromName(functionName);
269  }
270 
271  unsigned lineNo = frame.GetLineEntry().GetLine();
272 
273  // create assoc TODO: cache these
274  auto assoc = func->module().createLineNumberAssoc();
275 
276  auto nodeIter = assoc.left.find(lineNo);
277  if (nodeIter == assoc.left.end()) { return nullptr; }
278 
279  return nodeIter->second;
280 }
281 
283  // TODO: cache these, they're kinda expensive to make
284  auto lineAssoc = inst.module().createLineNumberAssoc();
285  auto lineNumberIter = lineAssoc.right.find(&inst);
286  if (lineNumberIter == lineAssoc.right.end()) { return -1; }
287 
288  return lineNumberIter->second;
289 }
290 
291 } // namespace chi
Result start(const char **argv=nullptr, const char **envp=nullptr, const boost::filesystem::path &workingDirectory=boost::filesystem::current_path())
Start debugging the process.
Definition: Debugger.cpp:145
bool isAttached() const
Check if this debugger is attached to a process It&#39;s true if the process is stopped or if it&#39;s curret...
Definition: Debugger.hpp:57
GraphModule & module() const
Get the module that&#39;s being debugged by this debugger.
Definition: Debugger.hpp:111
Result processContinue()
Continue the execution of the process.
Definition: Debugger.cpp:77
Result setBreakpoint(NodeInstance &node, lldb::SBBreakpoint *bp=nullptr)
Set a breakpoint on a given node.
Definition: Debugger.cpp:110
ChiModule * moduleByFullName(const boost::filesystem::path &fullModuleName) const noexcept
Gets the module by the full name.
Definition: Context.cpp:48
this is an AST-like representation of a function in a graph It is used for IDE-like behavior...
void addEntry(const char *ec, const char *overview, nlohmann::json data)
Add a entry to the result, either a warning or an error.
Definition: Result.cpp:52
std::string stringId() const
Get the ID as a string.
Result pause()
Pause the execution state.
Definition: Debugger.cpp:93
Result terminate()
Terminate the inferior process.
Definition: Debugger.cpp:60
boost::bimap< unsigned, NodeInstance * > createLineNumberAssoc() const
Create the associations from line number and function in debug info.
Defines the Result class and related functions.
Default, which is both.
GraphFunction * functionFromName(boost::string_view name) const
Get a function from the name.
std::vector< std::vector< std::pair< NodeInstance *, size_t > > > outputDataConnections
The connections that lead out of this node, data.
lldb::SBProcess lldbProcess() const
Get the process This will only be valid if there&#39;s an attached process.
Definition: Debugger.hpp:121
An instance of a node.
GraphModule & module() const
Get the containing GraphModule.
Definitions for mangling functions.
GraphModule & module() const
Get the GraphModule that contains this GraphFunction.
lldb::SBValue inspectNodeOutput(const NodeInstance &inst, size_t id, lldb::SBFrame frame={})
Get the output of a node.
Definition: Debugger.cpp:223
unsigned lineNumberFromNode(NodeInstance &inst)
Get the mapped line number from a node.
Definition: Debugger.cpp:282
NodeInstance * nodeFromFrame(lldb::SBFrame frame={})
Get a NodeInstance from a frame.
Definition: Debugger.cpp:244
boost::filesystem::path sourceFilePath() const
Get the path to the source file It&#39;s not garunteed to exist, because it could have not been saved...
std::vector< const NodeInstance * > listBreakpoints() const
List the curretnly set breakpoints.
Definition: Debugger.cpp:216
std::pair< std::string, std::string > unmangleFunctionName(std::string mangled)
Unmangle a function name.
Definition: NameMangler.cpp:45
GraphFunction & function() const
Get the containing GraphFunction.
Debugger(const char *pathToChig, GraphModule &mod)
Default constructor.
Definition: Debugger.cpp:33
bool removeBreakpoint(NodeInstance &node)
Remove a breakpoint from a node.
Definition: Debugger.cpp:138
Result compileModule(const boost::filesystem::path &fullName, Flags< CompileSettings > settings, std::unique_ptr< llvm::Module > *toFill)
Compile a module to a llvm::Module.
Definition: Context.cpp:284
Module that holds graph functions.
Definition: GraphModule.hpp:16
The namespace where chigraph lives.
Context & context() const
Get the Context that this module belongs to.
Definition: ChiModule.hpp:68
~Debugger()
Destructor.
Definition: Debugger.cpp:58
Defines the Context class and related functions.
The result object, used for identifiying errors with good diagnostics.
Definition: Result.hpp:72
Defines the NodeInstance class and related functions.