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