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