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>..."), 108258f055eSStefan Gränitz cl::ZeroOrMore, 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"), 113258f055eSStefan Gränitz cl::value_desc("filename"), cl::ZeroOrMore); 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 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. 226*16dcbb53SLang 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(); 243*16dcbb53SLang 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