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
100using namespace llvm;
101using namespace llvm::orc;
102
103// The LLVM IR file to run.
104static 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.
108static 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.
113static 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.
119static 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.
125static 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).
133static 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
138ExitOnError ExitOnErr;
139
140int 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

source code of llvm/examples/OrcV2Examples/LLJITWithRemoteDebugging/LLJITWithRemoteDebugging.cpp