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/Debugging/DebuggerSupport.h" |
81 | #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" |
82 | #include "llvm/ExecutionEngine/Orc/LLJIT.h" |
83 | #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" |
84 | #include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" |
85 | #include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h" |
86 | #include "llvm/Support/CommandLine.h" |
87 | #include "llvm/Support/Error.h" |
88 | #include "llvm/Support/FormatVariadic.h" |
89 | #include "llvm/Support/InitLLVM.h" |
90 | #include "llvm/Support/TargetSelect.h" |
91 | #include "llvm/Support/raw_ostream.h" |
92 | #include "llvm/TargetParser/Host.h" |
93 | |
94 | #include "../ExampleModules.h" |
95 | #include "RemoteJITUtils.h" |
96 | |
97 | #include <memory> |
98 | #include <string> |
99 | |
100 | using namespace llvm; |
101 | using namespace llvm::orc; |
102 | |
103 | // The LLVM IR file to run. |
104 | static cl::list<std::string> InputFiles(cl::Positional, cl::OneOrMore, |
105 | cl::desc("<input files>" )); |
106 | |
107 | // Command line arguments to pass to the JITed main function. |
108 | static cl::list<std::string> InputArgv("args" , cl::Positional, |
109 | cl::desc("<program arguments>..." ), |
110 | cl::PositionalEatsArgs); |
111 | |
112 | // Given paths must exist on the remote target. |
113 | static cl::list<std::string> |
114 | Dylibs("dlopen" , cl::desc("Dynamic libraries to load before linking" ), |
115 | cl::value_desc("filename" )); |
116 | |
117 | // File path of the executable to launch for execution in a child process. |
118 | // Inter-process communication will go through stdin/stdout pipes. |
119 | static cl::opt<std::string> |
120 | OOPExecutor("executor" , cl::desc("Set the out-of-process executor" ), |
121 | cl::value_desc("filename" )); |
122 | |
123 | // Network address of a running executor process that we can connect via TCP. It |
124 | // may run locally or on a remote machine. |
125 | static cl::opt<std::string> OOPExecutorConnectTCP( |
126 | "connect" , |
127 | cl::desc("Connect to an out-of-process executor through a TCP socket" ), |
128 | cl::value_desc("<hostname>:<port>" )); |
129 | |
130 | // Give the user a chance to connect a debugger. Once we connected the executor |
131 | // process, wait for the user to press a key (and print out its PID if it's a |
132 | // child process). |
133 | static cl::opt<bool> |
134 | WaitForDebugger("wait-for-debugger" , |
135 | cl::desc("Wait for user input before entering JITed code" ), |
136 | cl::init(Val: false)); |
137 | |
138 | ExitOnError ExitOnErr; |
139 | |
140 | int main(int argc, char *argv[]) { |
141 | InitLLVM X(argc, argv); |
142 | |
143 | InitializeNativeTarget(); |
144 | InitializeNativeTargetAsmPrinter(); |
145 | |
146 | ExitOnErr.setBanner(std::string(argv[0]) + ": " ); |
147 | cl::ParseCommandLineOptions(argc, argv, Overview: "LLJITWithRemoteDebugging" ); |
148 | |
149 | std::unique_ptr<SimpleRemoteEPC> EPC; |
150 | if (OOPExecutorConnectTCP.getNumOccurrences() > 0) { |
151 | // Connect to a running out-of-process executor through a TCP socket. |
152 | EPC = ExitOnErr(connectTCPSocket(NetworkAddress: OOPExecutorConnectTCP)); |
153 | outs() << "Connected to executor at " << OOPExecutorConnectTCP << "\n" ; |
154 | } else { |
155 | // Launch an out-of-process executor locally in a child process. |
156 | std::string Path = |
157 | OOPExecutor.empty() ? findLocalExecutor(HostArgv0: argv[0]) : OOPExecutor; |
158 | outs() << "Found out-of-process executor: " << Path << "\n" ; |
159 | |
160 | uint64_t PID; |
161 | std::tie(args&: EPC, args&: PID) = ExitOnErr(launchLocalExecutor(ExecutablePath: Path)); |
162 | outs() << "Launched executor in subprocess: " << PID << "\n" ; |
163 | } |
164 | |
165 | if (WaitForDebugger) { |
166 | outs() << "Attach a debugger and press any key to continue.\n" ; |
167 | fflush(stdin); |
168 | getchar(); |
169 | } |
170 | |
171 | // Load the given IR files. |
172 | std::vector<ThreadSafeModule> TSMs; |
173 | for (const std::string &Path : InputFiles) { |
174 | outs() << "Parsing input IR code from: " << Path << "\n" ; |
175 | TSMs.push_back(x: ExitOnErr(parseExampleModuleFromFile(FileName: Path))); |
176 | } |
177 | |
178 | // Create LLJIT and destroy it before disconnecting the target process. |
179 | outs() << "Initializing LLJIT for remote executor\n" ; |
180 | auto J = ExitOnErr( |
181 | LLJITBuilder().setExecutorProcessControl(std::move(EPC)).create()); |
182 | |
183 | // Add plugin for debug support. |
184 | ExitOnErr(enableDebuggerSupport(J&: *J)); |
185 | |
186 | // Load required shared libraries on the remote target and add a generator |
187 | // for each of it, so the compiler can lookup their symbols. |
188 | for (const std::string &Path : Dylibs) |
189 | J->getMainJITDylib().addGenerator( |
190 | DefGenerator: ExitOnErr(loadDylib(ES&: J->getExecutionSession(), RemotePath: Path))); |
191 | |
192 | // Add the loaded IR module to the JIT. This will set up symbol tables and |
193 | // prepare for materialization. |
194 | for (ThreadSafeModule &TSM : TSMs) |
195 | ExitOnErr(J->addIRModule(TSM: std::move(TSM))); |
196 | |
197 | // The example uses a non-lazy JIT for simplicity. Thus, looking up the main |
198 | // function will materialize all reachable code. It also triggers debug |
199 | // registration in the remote target process. |
200 | auto MainAddr = ExitOnErr(J->lookup(UnmangledName: "main" )); |
201 | |
202 | outs() << "Running: main(" ; |
203 | int Pos = 0; |
204 | std::vector<std::string> ActualArgv{"LLJITWithRemoteDebugging" }; |
205 | for (const std::string &Arg : InputArgv) { |
206 | outs() << (Pos++ == 0 ? "" : ", " ) << "\"" << Arg << "\"" ; |
207 | ActualArgv.push_back(x: Arg); |
208 | } |
209 | outs() << ")\n" ; |
210 | |
211 | // Execute the code in the remote target process and dump the result. With |
212 | // the debugger attached to the target, it should be possible to inspect the |
213 | // JITed code as if it was compiled statically. |
214 | { |
215 | ExecutorProcessControl &EPC = |
216 | J->getExecutionSession().getExecutorProcessControl(); |
217 | int Result = ExitOnErr(EPC.runAsMain(MainFnAddr: MainAddr, Args: ActualArgv)); |
218 | outs() << "Exit code: " << Result << "\n" ; |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | |