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