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