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