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