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