1 | //===- llvm-jitlink-executor.cpp - Out-of-proc executor for llvm-jitlink -===// |
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 | // Simple out-of-process executor for llvm-jitlink. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "llvm/ADT/StringRef.h" |
14 | #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX, LLVM_ENABLE_THREADS |
15 | #include "llvm/ExecutionEngine/Orc/TargetProcess/DefaultHostBootstrapValues.h" |
16 | #include "llvm/ExecutionEngine/Orc/TargetProcess/ExecutorSharedMemoryMapperService.h" |
17 | #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" |
18 | #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" |
19 | #include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleExecutorMemoryManager.h" |
20 | #include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleRemoteEPCServer.h" |
21 | #include "llvm/ExecutionEngine/Orc/TargetProcess/UnwindInfoManager.h" |
22 | #include "llvm/Support/Compiler.h" |
23 | #include "llvm/Support/Debug.h" |
24 | #include "llvm/Support/DynamicLibrary.h" |
25 | #include "llvm/Support/Error.h" |
26 | #include "llvm/Support/MathExtras.h" |
27 | #include "llvm/Support/raw_ostream.h" |
28 | #include <cstring> |
29 | #include <sstream> |
30 | |
31 | #ifdef LLVM_ON_UNIX |
32 | |
33 | #include <netdb.h> |
34 | #include <netinet/in.h> |
35 | #include <sys/socket.h> |
36 | |
37 | #endif |
38 | |
39 | using namespace llvm; |
40 | using namespace llvm::orc; |
41 | |
42 | ExitOnError ExitOnErr; |
43 | |
44 | LLVM_ATTRIBUTE_USED void linkComponents() { |
45 | errs() << (void *)&llvm_orc_registerEHFrameSectionAllocAction |
46 | << (void *)&llvm_orc_deregisterEHFrameSectionAllocAction |
47 | << (void *)&llvm_orc_registerJITLoaderGDBWrapper |
48 | << (void *)&llvm_orc_registerJITLoaderGDBAllocAction; |
49 | } |
50 | |
51 | void printErrorAndExit(Twine ErrMsg) { |
52 | #ifndef NDEBUG |
53 | const char *DebugOption = "[debug] " ; |
54 | #else |
55 | const char *DebugOption = "" ; |
56 | #endif |
57 | |
58 | errs() << "error: " << ErrMsg.str() << "\n\n" |
59 | << "Usage:\n" |
60 | << " llvm-jitlink-executor " << DebugOption |
61 | << "[test-jitloadergdb] filedescs=<infd>,<outfd> [args...]\n" |
62 | << " llvm-jitlink-executor " << DebugOption |
63 | << "[test-jitloadergdb] listen=<host>:<port> [args...]\n" ; |
64 | exit(status: 1); |
65 | } |
66 | |
67 | int openListener(std::string Host, std::string PortStr) { |
68 | #ifndef LLVM_ON_UNIX |
69 | // FIXME: Add TCP support for Windows. |
70 | printErrorAndExit("listen option not supported" ); |
71 | return 0; |
72 | #else |
73 | addrinfo Hints{}; |
74 | Hints.ai_family = AF_INET; |
75 | Hints.ai_socktype = SOCK_STREAM; |
76 | Hints.ai_flags = AI_PASSIVE; |
77 | |
78 | addrinfo *AI; |
79 | if (int EC = getaddrinfo(name: nullptr, service: PortStr.c_str(), req: &Hints, pai: &AI)) { |
80 | errs() << "Error setting up bind address: " << gai_strerror(ecode: EC) << "\n" ; |
81 | exit(status: 1); |
82 | } |
83 | |
84 | // Create a socket from first addrinfo structure returned by getaddrinfo. |
85 | int SockFD; |
86 | if ((SockFD = socket(domain: AI->ai_family, type: AI->ai_socktype, protocol: AI->ai_protocol)) < 0) { |
87 | errs() << "Error creating socket: " << std::strerror(errno) << "\n" ; |
88 | exit(status: 1); |
89 | } |
90 | |
91 | // Avoid "Address already in use" errors. |
92 | const int Yes = 1; |
93 | if (setsockopt(fd: SockFD, SOL_SOCKET, SO_REUSEADDR, optval: &Yes, optlen: sizeof(int)) == -1) { |
94 | errs() << "Error calling setsockopt: " << std::strerror(errno) << "\n" ; |
95 | exit(status: 1); |
96 | } |
97 | |
98 | // Bind the socket to the desired port. |
99 | if (bind(fd: SockFD, addr: AI->ai_addr, len: AI->ai_addrlen) < 0) { |
100 | errs() << "Error on binding: " << std::strerror(errno) << "\n" ; |
101 | exit(status: 1); |
102 | } |
103 | |
104 | // Listen for incomming connections. |
105 | static constexpr int ConnectionQueueLen = 1; |
106 | listen(fd: SockFD, n: ConnectionQueueLen); |
107 | |
108 | #if defined(_AIX) |
109 | assert(Hi_32(AI->ai_addrlen) == 0 && "Field is a size_t on 64-bit AIX" ); |
110 | socklen_t AddrLen = Lo_32(AI->ai_addrlen); |
111 | return accept(SockFD, AI->ai_addr, &AddrLen); |
112 | #else |
113 | return accept(fd: SockFD, addr: AI->ai_addr, addr_len: &AI->ai_addrlen); |
114 | #endif |
115 | |
116 | #endif // LLVM_ON_UNIX |
117 | } |
118 | |
119 | #if LLVM_ENABLE_THREADS |
120 | |
121 | // JITLink debug support plugins put information about JITed code in this GDB |
122 | // JIT Interface global from OrcTargetProcess. |
123 | extern "C" LLVM_ABI struct jit_descriptor __jit_debug_descriptor; |
124 | |
125 | static void *findLastDebugDescriptorEntryPtr() { |
126 | struct jit_code_entry *Last = __jit_debug_descriptor.first_entry; |
127 | while (Last && Last->next_entry) |
128 | Last = Last->next_entry; |
129 | return Last; |
130 | } |
131 | |
132 | #endif |
133 | |
134 | int main(int argc, char *argv[]) { |
135 | #if LLVM_ENABLE_THREADS |
136 | |
137 | ExitOnErr.setBanner(std::string(argv[0]) + ": " ); |
138 | |
139 | unsigned FirstProgramArg = 1; |
140 | int InFD = 0; |
141 | int OutFD = 0; |
142 | |
143 | if (argc < 2) |
144 | printErrorAndExit(ErrMsg: "insufficient arguments" ); |
145 | |
146 | StringRef NextArg = argv[FirstProgramArg++]; |
147 | #ifndef NDEBUG |
148 | if (NextArg == "debug" ) { |
149 | DebugFlag = true; |
150 | NextArg = argv[FirstProgramArg++]; |
151 | } |
152 | #endif |
153 | |
154 | std::vector<StringRef> TestOutputFlags; |
155 | while (NextArg.starts_with(Prefix: "test-" )) { |
156 | TestOutputFlags.push_back(x: NextArg); |
157 | NextArg = argv[FirstProgramArg++]; |
158 | } |
159 | |
160 | if (llvm::is_contained(Range&: TestOutputFlags, Element: "test-jitloadergdb" )) |
161 | fprintf(stderr, format: "__jit_debug_descriptor.last_entry = 0x%016" PRIx64 "\n" , |
162 | pointerToJITTargetAddress(Ptr: findLastDebugDescriptorEntryPtr())); |
163 | |
164 | StringRef SpecifierType, Specifier; |
165 | std::tie(args&: SpecifierType, args&: Specifier) = NextArg.split(Separator: '='); |
166 | if (SpecifierType == "filedescs" ) { |
167 | StringRef FD1Str, FD2Str; |
168 | std::tie(args&: FD1Str, args&: FD2Str) = Specifier.split(Separator: ','); |
169 | if (FD1Str.getAsInteger(Radix: 10, Result&: InFD)) |
170 | printErrorAndExit(ErrMsg: FD1Str + " is not a valid file descriptor" ); |
171 | if (FD2Str.getAsInteger(Radix: 10, Result&: OutFD)) |
172 | printErrorAndExit(ErrMsg: FD2Str + " is not a valid file descriptor" ); |
173 | } else if (SpecifierType == "listen" ) { |
174 | StringRef Host, PortStr; |
175 | std::tie(args&: Host, args&: PortStr) = Specifier.split(Separator: ':'); |
176 | |
177 | int Port = 0; |
178 | if (PortStr.getAsInteger(Radix: 10, Result&: Port)) |
179 | printErrorAndExit(ErrMsg: "port number '" + PortStr + "' is not a valid integer" ); |
180 | |
181 | InFD = OutFD = openListener(Host: Host.str(), PortStr: PortStr.str()); |
182 | } else |
183 | printErrorAndExit(ErrMsg: "invalid specifier type \"" + SpecifierType + "\"" ); |
184 | |
185 | auto Server = |
186 | ExitOnErr(SimpleRemoteEPCServer::Create<FDSimpleRemoteEPCTransport>( |
187 | SetupFunction: [](SimpleRemoteEPCServer::Setup &S) -> Error { |
188 | S.setDispatcher( |
189 | std::make_unique<SimpleRemoteEPCServer::ThreadDispatcher>()); |
190 | S.bootstrapSymbols() = |
191 | SimpleRemoteEPCServer::defaultBootstrapSymbols(); |
192 | addDefaultBootstrapValuesForHostProcess(BootstrapMap&: S.bootstrapMap(), |
193 | BootstrapSymbols&: S.bootstrapSymbols()); |
194 | #ifdef __APPLE__ |
195 | if (UnwindInfoManager::TryEnable()) |
196 | UnwindInfoManager::addBootstrapSymbols(S.bootstrapSymbols()); |
197 | #endif // __APPLE__ |
198 | S.services().push_back( |
199 | x: std::make_unique<rt_bootstrap::SimpleExecutorMemoryManager>()); |
200 | S.services().push_back( |
201 | x: std::make_unique< |
202 | rt_bootstrap::ExecutorSharedMemoryMapperService>()); |
203 | return Error::success(); |
204 | }, |
205 | TransportTCtorArgs&: InFD, TransportTCtorArgs&: OutFD)); |
206 | |
207 | ExitOnErr(Server->waitForDisconnect()); |
208 | |
209 | if (llvm::is_contained(Range&: TestOutputFlags, Element: "test-jitloadergdb" )) |
210 | fprintf(stderr, format: "__jit_debug_descriptor.last_entry = 0x%016" PRIx64 "\n" , |
211 | pointerToJITTargetAddress(Ptr: findLastDebugDescriptorEntryPtr())); |
212 | |
213 | return 0; |
214 | |
215 | #else |
216 | errs() << argv[0] |
217 | << " error: this tool requires threads, but LLVM was " |
218 | "built with LLVM_ENABLE_THREADS=Off\n" ; |
219 | return 1; |
220 | #endif |
221 | } |
222 | |