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