1258f055eSStefan Gränitz //===--- LLJITWithRemoteDebugging.cpp - LLJIT targeting a child process ---===//
2258f055eSStefan Gränitz //
3258f055eSStefan Gränitz // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4258f055eSStefan Gränitz // See https://llvm.org/LICENSE.txt for license information.
5258f055eSStefan Gränitz // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6258f055eSStefan Gränitz //
7258f055eSStefan Gränitz //===----------------------------------------------------------------------===//
8258f055eSStefan Gränitz //
9258f055eSStefan Gränitz // This example shows how to use LLJIT and JITLink for out-of-process execution
10258f055eSStefan Gränitz // with debug support. A few notes beforehand:
11258f055eSStefan Gränitz //
12258f055eSStefan Gränitz // * Debuggers must implement the GDB JIT interface (gdb, udb, lldb 12+).
13258f055eSStefan Gränitz // * Debug support is currently limited to ELF on x86-64 platforms that run
14258f055eSStefan Gränitz // Unix-like systems.
15258f055eSStefan Gränitz // * There is a test for this example and it ships an IR file that is prepared
16258f055eSStefan Gränitz // for the instructions below.
17258f055eSStefan Gränitz //
18258f055eSStefan Gränitz //
19258f055eSStefan Gränitz // The following command line session provides a complete walkthrough of the
20258f055eSStefan Gränitz // feature using LLDB 12:
21258f055eSStefan Gränitz //
22258f055eSStefan Gränitz // [Terminal 1] Prepare a debuggable out-of-process JIT session:
23258f055eSStefan Gränitz //
24258f055eSStefan Gränitz // > cd llvm-project/build
25258f055eSStefan Gränitz // > ninja LLJITWithRemoteDebugging llvm-jitlink-executor
26258f055eSStefan Gränitz // > cp ../llvm/test/Examples/OrcV2Examples/Inputs/argc_sub1_elf.ll .
27258f055eSStefan Gränitz // > bin/LLJITWithRemoteDebugging --wait-for-debugger argc_sub1_elf.ll
28258f055eSStefan Gränitz // Found out-of-process executor: bin/llvm-jitlink-executor
29258f055eSStefan Gränitz // Launched executor in subprocess: 65535
30258f055eSStefan Gränitz // Attach a debugger and press any key to continue.
31258f055eSStefan Gränitz //
32258f055eSStefan Gränitz //
33258f055eSStefan Gränitz // [Terminal 2] Attach a debugger to the child process:
34258f055eSStefan Gränitz //
35258f055eSStefan Gränitz // (lldb) log enable lldb jit
36258f055eSStefan Gränitz // (lldb) settings set plugin.jit-loader.gdb.enable on
37258f055eSStefan Gränitz // (lldb) settings set target.source-map Inputs/ \
38258f055eSStefan Gränitz // /path/to/llvm-project/llvm/test/Examples/OrcV2Examples/Inputs/
39258f055eSStefan Gränitz // (lldb) attach -p 65535
40258f055eSStefan Gränitz // JITLoaderGDB::SetJITBreakpoint looking for JIT register hook
41258f055eSStefan Gränitz // JITLoaderGDB::SetJITBreakpoint setting JIT breakpoint
42258f055eSStefan Gränitz // Process 65535 stopped
43258f055eSStefan Gränitz // (lldb) b sub1
44258f055eSStefan Gränitz // Breakpoint 1: no locations (pending).
45258f055eSStefan Gränitz // WARNING: Unable to resolve breakpoint to any actual locations.
46258f055eSStefan Gränitz // (lldb) c
47258f055eSStefan Gränitz // Process 65535 resuming
48258f055eSStefan Gränitz //
49258f055eSStefan Gränitz //
50258f055eSStefan Gränitz // [Terminal 1] Press a key to start code generation and execution:
51258f055eSStefan Gränitz //
52258f055eSStefan Gränitz // Parsed input IR code from: argc_sub1_elf.ll
53258f055eSStefan Gränitz // Initialized LLJIT for remote executor
54258f055eSStefan Gränitz // Running: argc_sub1_elf.ll
55258f055eSStefan Gränitz //
56258f055eSStefan Gränitz //
57258f055eSStefan Gränitz // [Terminal 2] Breakpoint hits; we change the argc value from 1 to 42:
58258f055eSStefan Gränitz //
59258f055eSStefan Gränitz // (lldb) JITLoaderGDB::JITDebugBreakpointHit hit JIT breakpoint
60258f055eSStefan Gränitz // JITLoaderGDB::ReadJITDescriptorImpl registering JIT entry at 0x106b34000
61258f055eSStefan Gränitz // 1 location added to breakpoint 1
62258f055eSStefan Gränitz // Process 65535 stopped
63258f055eSStefan Gränitz // * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
64258f055eSStefan Gränitz // frame #0: JIT(0x106b34000)`sub1(x=1) at argc_sub1.c:1:28
65258f055eSStefan Gränitz // -> 1 int sub1(int x) { return x - 1; }
66258f055eSStefan Gränitz // 2 int main(int argc, char **argv) { return sub1(argc); }
67258f055eSStefan Gränitz // (lldb) p x
68258f055eSStefan Gränitz // (int) $0 = 1
69258f055eSStefan Gränitz // (lldb) expr x = 42
70258f055eSStefan Gränitz // (int) $1 = 42
71258f055eSStefan Gränitz // (lldb) c
72258f055eSStefan Gränitz //
73258f055eSStefan Gränitz //
74258f055eSStefan Gränitz // [Terminal 1] Example output reflects the modified value:
75258f055eSStefan Gränitz //
76258f055eSStefan Gränitz // Exit code: 41
77258f055eSStefan Gränitz //
78258f055eSStefan Gränitz //===----------------------------------------------------------------------===//
79258f055eSStefan Gränitz
80258f055eSStefan Gränitz #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
81258f055eSStefan Gränitz #include "llvm/ExecutionEngine/Orc/LLJIT.h"
82ac2daacbSStefan Gränitz #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
83ac2daacbSStefan Gränitz #include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h"
84258f055eSStefan Gränitz #include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
85258f055eSStefan Gränitz #include "llvm/Support/CommandLine.h"
86258f055eSStefan Gränitz #include "llvm/Support/Error.h"
87258f055eSStefan Gränitz #include "llvm/Support/FormatVariadic.h"
88258f055eSStefan Gränitz #include "llvm/Support/InitLLVM.h"
89258f055eSStefan Gränitz #include "llvm/Support/TargetSelect.h"
90258f055eSStefan Gränitz #include "llvm/Support/raw_ostream.h"
91258f055eSStefan Gränitz
92258f055eSStefan Gränitz #include "../ExampleModules.h"
93258f055eSStefan Gränitz #include "RemoteJITUtils.h"
94258f055eSStefan Gränitz
95258f055eSStefan Gränitz #include <memory>
96258f055eSStefan Gränitz #include <string>
97258f055eSStefan Gränitz
98258f055eSStefan Gränitz using namespace llvm;
99258f055eSStefan Gränitz using namespace llvm::orc;
100258f055eSStefan Gränitz
101258f055eSStefan Gränitz // The LLVM IR file to run.
102258f055eSStefan Gränitz static cl::list<std::string> InputFiles(cl::Positional, cl::OneOrMore,
103258f055eSStefan Gränitz cl::desc("<input files>"));
104258f055eSStefan Gränitz
105258f055eSStefan Gränitz // Command line arguments to pass to the JITed main function.
106258f055eSStefan Gränitz static cl::list<std::string> InputArgv("args", cl::Positional,
107258f055eSStefan Gränitz cl::desc("<program arguments>..."),
108*95a13425SFangrui Song cl::PositionalEatsArgs);
109258f055eSStefan Gränitz
110258f055eSStefan Gränitz // Given paths must exist on the remote target.
111258f055eSStefan Gränitz static cl::list<std::string>
112258f055eSStefan Gränitz Dylibs("dlopen", cl::desc("Dynamic libraries to load before linking"),
113d0d1c416SFangrui Song cl::value_desc("filename"));
114258f055eSStefan Gränitz
115258f055eSStefan Gränitz // File path of the executable to launch for execution in a child process.
116258f055eSStefan Gränitz // Inter-process communication will go through stdin/stdout pipes.
117258f055eSStefan Gränitz static cl::opt<std::string>
118258f055eSStefan Gränitz OOPExecutor("executor", cl::desc("Set the out-of-process executor"),
119258f055eSStefan Gränitz cl::value_desc("filename"));
120258f055eSStefan Gränitz
121ac2daacbSStefan Gränitz // Network address of a running executor process that we can connect via TCP. It
122ac2daacbSStefan Gränitz // may run locally or on a remote machine.
123ac2daacbSStefan Gränitz static cl::opt<std::string> OOPExecutorConnectTCP(
124258f055eSStefan Gränitz "connect",
125258f055eSStefan Gränitz cl::desc("Connect to an out-of-process executor through a TCP socket"),
126258f055eSStefan Gränitz cl::value_desc("<hostname>:<port>"));
127258f055eSStefan Gränitz
128258f055eSStefan Gränitz // Give the user a chance to connect a debugger. Once we connected the executor
129258f055eSStefan Gränitz // process, wait for the user to press a key (and print out its PID if it's a
130258f055eSStefan Gränitz // child process).
131258f055eSStefan Gränitz static cl::opt<bool>
132258f055eSStefan Gränitz WaitForDebugger("wait-for-debugger",
133258f055eSStefan Gränitz cl::desc("Wait for user input before entering JITed code"),
134258f055eSStefan Gränitz cl::init(false));
135258f055eSStefan Gränitz
136258f055eSStefan Gränitz ExitOnError ExitOnErr;
137258f055eSStefan Gränitz
main(int argc,char * argv[])138258f055eSStefan Gränitz int main(int argc, char *argv[]) {
139258f055eSStefan Gränitz InitLLVM X(argc, argv);
140258f055eSStefan Gränitz
141258f055eSStefan Gränitz InitializeNativeTarget();
142258f055eSStefan Gränitz InitializeNativeTargetAsmPrinter();
143258f055eSStefan Gränitz
144258f055eSStefan Gränitz ExitOnErr.setBanner(std::string(argv[0]) + ": ");
145258f055eSStefan Gränitz cl::ParseCommandLineOptions(argc, argv, "LLJITWithRemoteDebugging");
146258f055eSStefan Gränitz
147ac2daacbSStefan Gränitz std::unique_ptr<SimpleRemoteEPC> EPC;
148ac2daacbSStefan Gränitz if (OOPExecutorConnectTCP.getNumOccurrences() > 0) {
149ac2daacbSStefan Gränitz // Connect to a running out-of-process executor through a TCP socket.
150ac2daacbSStefan Gränitz EPC = ExitOnErr(connectTCPSocket(OOPExecutorConnectTCP));
151ac2daacbSStefan Gränitz outs() << "Connected to executor at " << OOPExecutorConnectTCP << "\n";
152ac2daacbSStefan Gränitz } else {
153ac2daacbSStefan Gränitz // Launch an out-of-process executor locally in a child process.
154ac2daacbSStefan Gränitz std::string Path =
155ac2daacbSStefan Gränitz OOPExecutor.empty() ? findLocalExecutor(argv[0]) : OOPExecutor;
156ac2daacbSStefan Gränitz outs() << "Found out-of-process executor: " << Path << "\n";
157ac2daacbSStefan Gränitz
158ac2daacbSStefan Gränitz uint64_t PID;
159ac2daacbSStefan Gränitz std::tie(EPC, PID) = ExitOnErr(launchLocalExecutor(Path));
160ac2daacbSStefan Gränitz outs() << "Launched executor in subprocess: " << PID << "\n";
161ac2daacbSStefan Gränitz }
162ac2daacbSStefan Gränitz
163ac2daacbSStefan Gränitz if (WaitForDebugger) {
164ac2daacbSStefan Gränitz outs() << "Attach a debugger and press any key to continue.\n";
165ac2daacbSStefan Gränitz fflush(stdin);
166ac2daacbSStefan Gränitz getchar();
167ac2daacbSStefan Gränitz }
168258f055eSStefan Gränitz
169258f055eSStefan Gränitz // Load the given IR files.
170258f055eSStefan Gränitz std::vector<ThreadSafeModule> TSMs;
171258f055eSStefan Gränitz for (const std::string &Path : InputFiles) {
172258f055eSStefan Gränitz outs() << "Parsing input IR code from: " << Path << "\n";
173258f055eSStefan Gränitz TSMs.push_back(ExitOnErr(parseExampleModuleFromFile(Path)));
174258f055eSStefan Gränitz }
175258f055eSStefan Gränitz
176258f055eSStefan Gränitz StringRef TT;
177258f055eSStefan Gränitz StringRef MainModuleName;
178258f055eSStefan Gränitz TSMs.front().withModuleDo([&MainModuleName, &TT](Module &M) {
179258f055eSStefan Gränitz MainModuleName = M.getName();
180258f055eSStefan Gränitz TT = M.getTargetTriple();
181258f055eSStefan Gränitz });
182258f055eSStefan Gränitz
183258f055eSStefan Gränitz for (const ThreadSafeModule &TSM : TSMs)
184258f055eSStefan Gränitz ExitOnErr(TSM.withModuleDo([TT, MainModuleName](Module &M) -> Error {
185258f055eSStefan Gränitz if (M.getTargetTriple() != TT)
186258f055eSStefan Gränitz return make_error<StringError>(
187258f055eSStefan Gränitz formatv("Different target triples in input files:\n"
188258f055eSStefan Gränitz " '{0}' in '{1}'\n '{2}' in '{3}'",
189258f055eSStefan Gränitz TT, MainModuleName, M.getTargetTriple(), M.getName()),
190258f055eSStefan Gränitz inconvertibleErrorCode());
191258f055eSStefan Gränitz return Error::success();
192258f055eSStefan Gränitz }));
193258f055eSStefan Gränitz
194258f055eSStefan Gränitz // Create a target machine that matches the input triple.
195258f055eSStefan Gränitz JITTargetMachineBuilder JTMB((Triple(TT)));
196258f055eSStefan Gränitz JTMB.setCodeModel(CodeModel::Small);
197258f055eSStefan Gränitz JTMB.setRelocationModel(Reloc::PIC_);
198258f055eSStefan Gränitz
199258f055eSStefan Gränitz // Create LLJIT and destroy it before disconnecting the target process.
200258f055eSStefan Gränitz outs() << "Initializing LLJIT for remote executor\n";
201258f055eSStefan Gränitz auto J = ExitOnErr(LLJITBuilder()
202ac2daacbSStefan Gränitz .setExecutorProcessControl(std::move(EPC))
203258f055eSStefan Gränitz .setJITTargetMachineBuilder(std::move(JTMB))
204ac2daacbSStefan Gränitz .setObjectLinkingLayerCreator([&](auto &ES, const auto &TT) {
205ac2daacbSStefan Gränitz return std::make_unique<ObjectLinkingLayer>(ES);
206ac2daacbSStefan Gränitz })
207258f055eSStefan Gränitz .create());
208258f055eSStefan Gränitz
209258f055eSStefan Gränitz // Add plugin for debug support.
210ac2daacbSStefan Gränitz ExitOnErr(addDebugSupport(J->getObjLinkingLayer()));
211258f055eSStefan Gränitz
212258f055eSStefan Gränitz // Load required shared libraries on the remote target and add a generator
213258f055eSStefan Gränitz // for each of it, so the compiler can lookup their symbols.
214258f055eSStefan Gränitz for (const std::string &Path : Dylibs)
215ac2daacbSStefan Gränitz J->getMainJITDylib().addGenerator(
216ac2daacbSStefan Gränitz ExitOnErr(loadDylib(J->getExecutionSession(), Path)));
217258f055eSStefan Gränitz
218258f055eSStefan Gränitz // Add the loaded IR module to the JIT. This will set up symbol tables and
219258f055eSStefan Gränitz // prepare for materialization.
220258f055eSStefan Gränitz for (ThreadSafeModule &TSM : TSMs)
221258f055eSStefan Gränitz ExitOnErr(J->addIRModule(std::move(TSM)));
222258f055eSStefan Gränitz
223258f055eSStefan Gränitz // The example uses a non-lazy JIT for simplicity. Thus, looking up the main
224258f055eSStefan Gränitz // function will materialize all reachable code. It also triggers debug
225258f055eSStefan Gränitz // registration in the remote target process.
22616dcbb53SLang Hames auto MainAddr = ExitOnErr(J->lookup("main"));
227258f055eSStefan Gränitz
228258f055eSStefan Gränitz outs() << "Running: main(";
229258f055eSStefan Gränitz int Pos = 0;
230ac2daacbSStefan Gränitz std::vector<std::string> ActualArgv{"LLJITWithRemoteDebugging"};
231ac2daacbSStefan Gränitz for (const std::string &Arg : InputArgv) {
232792ee5beSStefan Gränitz outs() << (Pos++ == 0 ? "" : ", ") << "\"" << Arg << "\"";
233ac2daacbSStefan Gränitz ActualArgv.push_back(Arg);
234ac2daacbSStefan Gränitz }
235258f055eSStefan Gränitz outs() << ")\n";
236258f055eSStefan Gränitz
237258f055eSStefan Gränitz // Execute the code in the remote target process and dump the result. With
238258f055eSStefan Gränitz // the debugger attached to the target, it should be possible to inspect the
239258f055eSStefan Gränitz // JITed code as if it was compiled statically.
240ac2daacbSStefan Gränitz {
241ac2daacbSStefan Gränitz ExecutorProcessControl &EPC =
242ac2daacbSStefan Gränitz J->getExecutionSession().getExecutorProcessControl();
24316dcbb53SLang Hames int Result = ExitOnErr(EPC.runAsMain(MainAddr, ActualArgv));
244258f055eSStefan Gränitz outs() << "Exit code: " << Result << "\n";
245258f055eSStefan Gränitz }
246258f055eSStefan Gränitz
247258f055eSStefan Gränitz return 0;
248258f055eSStefan Gränitz }
249