forked from llvm/llvm-project
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLLJITWithRemoteDebugging.cpp
249 lines (220 loc) · 9.44 KB
/
LLJITWithRemoteDebugging.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//===--- LLJITWithRemoteDebugging.cpp - LLJIT targeting a child process ---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This example shows how to use LLJIT and JITLink for out-of-process execution
// with debug support. A few notes beforehand:
//
// * Debuggers must implement the GDB JIT interface (gdb, udb, lldb 12+).
// * Debug support is currently limited to ELF on x86-64 platforms that run
// Unix-like systems.
// * There is a test for this example and it ships an IR file that is prepared
// for the instructions below.
//
//
// The following command line session provides a complete walkthrough of the
// feature using LLDB 12:
//
// [Terminal 1] Prepare a debuggable out-of-process JIT session:
//
// > cd llvm-project/build
// > ninja LLJITWithRemoteDebugging llvm-jitlink-executor
// > cp ../llvm/test/Examples/OrcV2Examples/Inputs/argc_sub1_elf.ll .
// > bin/LLJITWithRemoteDebugging --wait-for-debugger argc_sub1_elf.ll
// Found out-of-process executor: bin/llvm-jitlink-executor
// Launched executor in subprocess: 65535
// Attach a debugger and press any key to continue.
//
//
// [Terminal 2] Attach a debugger to the child process:
//
// (lldb) log enable lldb jit
// (lldb) settings set plugin.jit-loader.gdb.enable on
// (lldb) settings set target.source-map Inputs/ \
// /path/to/llvm-project/llvm/test/Examples/OrcV2Examples/Inputs/
// (lldb) attach -p 65535
// JITLoaderGDB::SetJITBreakpoint looking for JIT register hook
// JITLoaderGDB::SetJITBreakpoint setting JIT breakpoint
// Process 65535 stopped
// (lldb) b sub1
// Breakpoint 1: no locations (pending).
// WARNING: Unable to resolve breakpoint to any actual locations.
// (lldb) c
// Process 65535 resuming
//
//
// [Terminal 1] Press a key to start code generation and execution:
//
// Parsed input IR code from: argc_sub1_elf.ll
// Initialized LLJIT for remote executor
// Running: argc_sub1_elf.ll
//
//
// [Terminal 2] Breakpoint hits; we change the argc value from 1 to 42:
//
// (lldb) JITLoaderGDB::JITDebugBreakpointHit hit JIT breakpoint
// JITLoaderGDB::ReadJITDescriptorImpl registering JIT entry at 0x106b34000
// 1 location added to breakpoint 1
// Process 65535 stopped
// * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
// frame #0: JIT(0x106b34000)`sub1(x=1) at argc_sub1.c:1:28
// -> 1 int sub1(int x) { return x - 1; }
// 2 int main(int argc, char **argv) { return sub1(argc); }
// (lldb) p x
// (int) $0 = 1
// (lldb) expr x = 42
// (int) $1 = 42
// (lldb) c
//
//
// [Terminal 1] Example output reflects the modified value:
//
// Exit code: 41
//
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h"
#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "../ExampleModules.h"
#include "RemoteJITUtils.h"
#include <memory>
#include <string>
using namespace llvm;
using namespace llvm::orc;
// The LLVM IR file to run.
static cl::list<std::string> InputFiles(cl::Positional, cl::OneOrMore,
cl::desc("<input files>"));
// Command line arguments to pass to the JITed main function.
static cl::list<std::string> InputArgv("args", cl::Positional,
cl::desc("<program arguments>..."),
cl::ZeroOrMore, cl::PositionalEatsArgs);
// Given paths must exist on the remote target.
static cl::list<std::string>
Dylibs("dlopen", cl::desc("Dynamic libraries to load before linking"),
cl::value_desc("filename"), cl::ZeroOrMore);
// File path of the executable to launch for execution in a child process.
// Inter-process communication will go through stdin/stdout pipes.
static cl::opt<std::string>
OOPExecutor("executor", cl::desc("Set the out-of-process executor"),
cl::value_desc("filename"));
// Network address of a running executor process that we can connect via TCP. It
// may run locally or on a remote machine.
static cl::opt<std::string> OOPExecutorConnectTCP(
"connect",
cl::desc("Connect to an out-of-process executor through a TCP socket"),
cl::value_desc("<hostname>:<port>"));
// Give the user a chance to connect a debugger. Once we connected the executor
// process, wait for the user to press a key (and print out its PID if it's a
// child process).
static cl::opt<bool>
WaitForDebugger("wait-for-debugger",
cl::desc("Wait for user input before entering JITed code"),
cl::init(false));
ExitOnError ExitOnErr;
int main(int argc, char *argv[]) {
InitLLVM X(argc, argv);
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
ExitOnErr.setBanner(std::string(argv[0]) + ": ");
cl::ParseCommandLineOptions(argc, argv, "LLJITWithRemoteDebugging");
std::unique_ptr<SimpleRemoteEPC> EPC;
if (OOPExecutorConnectTCP.getNumOccurrences() > 0) {
// Connect to a running out-of-process executor through a TCP socket.
EPC = ExitOnErr(connectTCPSocket(OOPExecutorConnectTCP));
outs() << "Connected to executor at " << OOPExecutorConnectTCP << "\n";
} else {
// Launch an out-of-process executor locally in a child process.
std::string Path =
OOPExecutor.empty() ? findLocalExecutor(argv[0]) : OOPExecutor;
outs() << "Found out-of-process executor: " << Path << "\n";
uint64_t PID;
std::tie(EPC, PID) = ExitOnErr(launchLocalExecutor(Path));
outs() << "Launched executor in subprocess: " << PID << "\n";
}
if (WaitForDebugger) {
outs() << "Attach a debugger and press any key to continue.\n";
fflush(stdin);
getchar();
}
// Load the given IR files.
std::vector<ThreadSafeModule> TSMs;
for (const std::string &Path : InputFiles) {
outs() << "Parsing input IR code from: " << Path << "\n";
TSMs.push_back(ExitOnErr(parseExampleModuleFromFile(Path)));
}
StringRef TT;
StringRef MainModuleName;
TSMs.front().withModuleDo([&MainModuleName, &TT](Module &M) {
MainModuleName = M.getName();
TT = M.getTargetTriple();
});
for (const ThreadSafeModule &TSM : TSMs)
ExitOnErr(TSM.withModuleDo([TT, MainModuleName](Module &M) -> Error {
if (M.getTargetTriple() != TT)
return make_error<StringError>(
formatv("Different target triples in input files:\n"
" '{0}' in '{1}'\n '{2}' in '{3}'",
TT, MainModuleName, M.getTargetTriple(), M.getName()),
inconvertibleErrorCode());
return Error::success();
}));
// Create a target machine that matches the input triple.
JITTargetMachineBuilder JTMB((Triple(TT)));
JTMB.setCodeModel(CodeModel::Small);
JTMB.setRelocationModel(Reloc::PIC_);
// Create LLJIT and destroy it before disconnecting the target process.
outs() << "Initializing LLJIT for remote executor\n";
auto J = ExitOnErr(LLJITBuilder()
.setExecutorProcessControl(std::move(EPC))
.setJITTargetMachineBuilder(std::move(JTMB))
.setObjectLinkingLayerCreator([&](auto &ES, const auto &TT) {
return std::make_unique<ObjectLinkingLayer>(ES);
})
.create());
// Add plugin for debug support.
ExitOnErr(addDebugSupport(J->getObjLinkingLayer()));
// Load required shared libraries on the remote target and add a generator
// for each of it, so the compiler can lookup their symbols.
for (const std::string &Path : Dylibs)
J->getMainJITDylib().addGenerator(
ExitOnErr(loadDylib(J->getExecutionSession(), Path)));
// Add the loaded IR module to the JIT. This will set up symbol tables and
// prepare for materialization.
for (ThreadSafeModule &TSM : TSMs)
ExitOnErr(J->addIRModule(std::move(TSM)));
// The example uses a non-lazy JIT for simplicity. Thus, looking up the main
// function will materialize all reachable code. It also triggers debug
// registration in the remote target process.
JITEvaluatedSymbol MainFn = ExitOnErr(J->lookup("main"));
outs() << "Running: main(";
int Pos = 0;
std::vector<std::string> ActualArgv{"LLJITWithRemoteDebugging"};
for (const std::string &Arg : InputArgv) {
outs() << (Pos++ == 0 ? "" : ", ") << "\"" << Arg << "\"";
ActualArgv.push_back(Arg);
}
outs() << ")\n";
// Execute the code in the remote target process and dump the result. With
// the debugger attached to the target, it should be possible to inspect the
// JITed code as if it was compiled statically.
{
JITTargetAddress MainFnAddr = MainFn.getAddress();
ExecutorProcessControl &EPC =
J->getExecutionSession().getExecutorProcessControl();
int Result = ExitOnErr(EPC.runAsMain(ExecutorAddr(MainFnAddr), ActualArgv));
outs() << "Exit code: " << Result << "\n";
}
return 0;
}