| 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
60 Result Debugger::terminate() {
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
77 Result Debugger::processContinue() {
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 }
93 Result Debugger::pause() {
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
138 bool Debugger::removeBreakpoint(NodeInstance& node) {
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 {
159 res = module().context().compileModule(module(), CompileSettings::Default, &mod);
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
282 unsigned lineNumberFromNode(NodeInstance& inst) {
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
chi::Debugger::start
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
LLVMVersion.hpp
chi::Debugger::isAttached
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
chi::Debugger::module
GraphModule & module() const
Get the module that&#39;s being debugged by this debugger.
Definition: Debugger.hpp:111
chi::Debugger::processContinue
Result processContinue()
Continue the execution of the process.
Definition: Debugger.cpp:77
chi::Debugger::setBreakpoint
Result setBreakpoint(NodeInstance &node, lldb::SBBreakpoint *bp=nullptr)
Set a breakpoint on a given node.
Definition: Debugger.cpp:110
chi::Context::moduleByFullName
ChiModule * moduleByFullName(const boost::filesystem::path &fullModuleName) const noexcept
Gets the module by the full name.
Definition: Context.cpp:48
chi::GraphFunction
this is an AST-like representation of a function in a graph It is used for IDE-like behavior...
Definition: GraphFunction.hpp:23
chi::Result::addEntry
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
chi::NodeInstance::stringId
std::string stringId() const
Get the ID as a string.
Definition: NodeInstance.hpp:68
chi::Debugger::pause
Result pause()
Pause the execution state.
Definition: Debugger.cpp:93
chi::Debugger::terminate
Result terminate()
Terminate the inferior process.
Definition: Debugger.cpp:60
chi::GraphModule::createLineNumberAssoc
boost::bimap< unsigned, NodeInstance * > createLineNumberAssoc() const
Create the associations from line number and function in debug info.
Definition: GraphModule.cpp:794
Result.hpp
Defines the Result class and related functions.
chi::CompileSettings::Default
Default, which is both.
chi::GraphModule::functionFromName
GraphFunction * functionFromName(boost::string_view name) const
Get a function from the name.
Definition: GraphModule.cpp:598
chi::NodeInstance::outputDataConnections
std::vector< std::vector< std::pair< NodeInstance *, size_t > > > outputDataConnections
The connections that lead out of this node, data.
Definition: NodeInstance.hpp:83
chi::Debugger::lldbProcess
lldb::SBProcess lldbProcess() const
Get the process This will only be valid if there&#39;s an attached process.
Definition: Debugger.hpp:121
chi::NodeInstance
An instance of a node.
Definition: NodeInstance.hpp:19
chi::NodeInstance::module
GraphModule & module() const
Get the containing GraphModule.
Definition: NodeInstance.hpp:95
NameMangler.hpp
Definitions for mangling functions.
chi::GraphFunction::module
GraphModule & module() const
Get the GraphModule that contains this GraphFunction.
Definition: GraphFunction.hpp:328
chi::Debugger::inspectNodeOutput
lldb::SBValue inspectNodeOutput(const NodeInstance &inst, size_t id, lldb::SBFrame frame={})
Get the output of a node.
Definition: Debugger.cpp:223
chi::lineNumberFromNode
unsigned lineNumberFromNode(NodeInstance &inst)
Get the mapped line number from a node.
Definition: Debugger.cpp:282
chi::Debugger::nodeFromFrame
NodeInstance * nodeFromFrame(lldb::SBFrame frame={})
Get a NodeInstance from a frame.
Definition: Debugger.cpp:244
chi::GraphModule::sourceFilePath
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...
Definition: GraphModule.cpp:862
chi::Debugger::listBreakpoints
std::vector< const NodeInstance * > listBreakpoints() const
List the curretnly set breakpoints.
Definition: Debugger.cpp:216
chi::unmangleFunctionName
std::pair< std::string, std::string > unmangleFunctionName(std::string mangled)
Unmangle a function name.
Definition: NameMangler.cpp:45
chi::NodeInstance::function
GraphFunction & function() const
Get the containing GraphFunction.
Definition: NodeInstance.hpp:91
chi::Debugger::Debugger
Debugger(const char *pathToChig, GraphModule &mod)
Default constructor.
Definition: Debugger.cpp:33
chi::Debugger::removeBreakpoint
bool removeBreakpoint(NodeInstance &node)
Remove a breakpoint from a node.
Definition: Debugger.cpp:138
chi::Context::compileModule
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
chi::GraphModule
Module that holds graph functions.
Definition: GraphModule.hpp:16
chi
The namespace where chigraph lives.
Definition: BitcodeParser.hpp:12
chi::ChiModule::context
Context & context() const
Get the Context that this module belongs to.
Definition: ChiModule.hpp:68
chi::Debugger::~Debugger
~Debugger()
Destructor.
Definition: Debugger.cpp:58
Context.hpp
Defines the Context class and related functions.
chi::Result
The result object, used for identifiying errors with good diagnostics.
Definition: Result.hpp:72
NodeInstance.hpp
Defines the NodeInstance class and related functions.