1 //===- subzero/src/IceBrowserCompileServer.cpp - Browser compile server ---===// 2 // 3 // The Subzero Code Generator 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 /// 10 /// \file 11 /// \brief Defines the browser-based compile server. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 // Can only compile this with the NaCl compiler (needs irt.h, and the 16 // unsandboxed LLVM build using the trusted compiler does not have irt.h). 17 #include "IceBrowserCompileServer.h" 18 #include "IceRangeSpec.h" 19 20 #if PNACL_BROWSER_TRANSLATOR 21 22 // Headers which are not properly part of the SDK are included by their path in 23 // the NaCl tree. 24 #ifdef __pnacl__ 25 #include "native_client/src/untrusted/nacl/pnacl.h" 26 #endif // __pnacl__ 27 28 #include "llvm/Support/QueueStreamer.h" 29 30 #include <cstring> 31 #include <fstream> 32 #include <irt.h> 33 #include <irt_dev.h> 34 #include <pthread.h> 35 #include <thread> 36 37 namespace Ice { 38 39 // Create C wrappers around callback handlers for the IRT interface. 40 namespace { 41 42 BrowserCompileServer *gCompileServer; 43 struct nacl_irt_private_pnacl_translator_compile gIRTFuncs; 44 45 void getIRTInterfaces() { 46 size_t QueryResult = 47 nacl_interface_query(NACL_IRT_PRIVATE_PNACL_TRANSLATOR_COMPILE_v0_1, 48 &gIRTFuncs, sizeof(gIRTFuncs)); 49 if (QueryResult != sizeof(gIRTFuncs)) 50 llvm::report_fatal_error("Failed to get translator compile IRT interface"); 51 } 52 53 // Allow pnacl-sz arguments to be supplied externally, instead of coming from 54 // the browser. This is meant to be used for debugging. 55 // 56 // NOTE: This functionality is only enabled in non-MINIMAL Subzero builds, for 57 // security/safety reasons. 58 // 59 // If the SZARGFILE environment variable is set to a file name, arguments are 60 // read from that file, one argument per line. This requires setting 3 61 // environment variables before starting the browser: 62 // 63 // NACL_ENV_PASSTHROUGH=NACL_DANGEROUS_ENABLE_FILE_ACCESS,NACLENV_SZARGFILE 64 // NACL_DANGEROUS_ENABLE_FILE_ACCESS=1 65 // NACLENV_SZARGFILE=/path/to/myargs.txt 66 // 67 // In addition, Chrome needs to be launched with the "--no-sandbox" argument. 68 // 69 // If the SZARGLIST environment variable is set, arguments are extracted from 70 // that variable's value, separated by the '|' character (being careful to 71 // escape/quote special shell characters). This requires setting 2 environment 72 // variables before starting the browser: 73 // 74 // NACL_ENV_PASSTHROUGH=NACLENV_SZARGLIST 75 // NACLENV_SZARGLIST=arg 76 // 77 // This does not require the "--no-sandbox" argument, and is therefore much 78 // safer, but does require restarting the browser to change the arguments. 79 // 80 // If external arguments are supplied, the browser's NumThreads specification is 81 // ignored, to allow -threads to be specified as an external argument. Note 82 // that the browser normally supplies the "-O2" argument, so externally supplied 83 // arguments might want to provide an explicit -O argument. 84 // 85 // See Chrome's src/components/nacl/zygote/nacl_fork_delegate_linux.cc for the 86 // NACL_ENV_PASSTHROUGH mechanism. 87 // 88 // See NaCl's src/trusted/service_runtime/env_cleanser.c for the NACLENV_ 89 // mechanism. 90 std::vector<std::string> getExternalArgs() { 91 std::vector<std::string> ExternalArgs; 92 if (BuildDefs::minimal()) 93 return ExternalArgs; 94 char ArgsFileVar[] = "SZARGFILE"; 95 char ArgsListVar[] = "SZARGLIST"; 96 if (const char *ArgsFilename = getenv(ArgsFileVar)) { 97 std::ifstream ArgsStream(ArgsFilename); 98 std::string Arg; 99 while (ArgsStream >> std::ws, std::getline(ArgsStream, Arg)) { 100 if (!Arg.empty() && Arg[0] == '#') 101 continue; 102 ExternalArgs.emplace_back(Arg); 103 } 104 if (ExternalArgs.empty()) { 105 llvm::report_fatal_error("Failed to read arguments from file '" + 106 std::string(ArgsFilename) + "'"); 107 } 108 } else if (const char *ArgsList = getenv(ArgsListVar)) { 109 // Leverage the RangeSpec tokenizer. 110 auto Args = RangeSpec::tokenize(ArgsList, '|'); 111 ExternalArgs.insert(ExternalArgs.end(), Args.begin(), Args.end()); 112 } 113 return ExternalArgs; 114 } 115 116 char *onInitCallback(uint32_t NumThreads, int *ObjFileFDs, 117 size_t ObjFileFDCount, char **CLArgs, size_t CLArgsLen) { 118 if (ObjFileFDCount < 1) { 119 std::string Buffer; 120 llvm::raw_string_ostream StrBuf(Buffer); 121 StrBuf << "Invalid number of FDs for onInitCallback " << ObjFileFDCount 122 << "\n"; 123 return strdup(StrBuf.str().c_str()); 124 } 125 int ObjFileFD = ObjFileFDs[0]; 126 if (ObjFileFD < 0) { 127 std::string Buffer; 128 llvm::raw_string_ostream StrBuf(Buffer); 129 StrBuf << "Invalid FD given for onInitCallback " << ObjFileFD << "\n"; 130 return strdup(StrBuf.str().c_str()); 131 } 132 // CLArgs is almost an "argv", but is missing the argv[0] program name. 133 std::vector<const char *> Argv; 134 constexpr static char ProgramName[] = "pnacl-sz.nexe"; 135 Argv.reserve(CLArgsLen + 1); 136 Argv.push_back(ProgramName); 137 138 bool UseNumThreadsFromBrowser = true; 139 auto ExternalArgs = getExternalArgs(); 140 if (ExternalArgs.empty()) { 141 for (size_t i = 0; i < CLArgsLen; ++i) { 142 Argv.push_back(CLArgs[i]); 143 } 144 } else { 145 for (auto &Arg : ExternalArgs) { 146 Argv.emplace_back(Arg.c_str()); 147 } 148 UseNumThreadsFromBrowser = false; 149 } 150 // NOTE: strings pointed to by argv are owned by the caller, but we parse 151 // here before returning and don't store them. 152 gCompileServer->getParsedFlags(UseNumThreadsFromBrowser, NumThreads, 153 Argv.size(), Argv.data()); 154 gCompileServer->startCompileThread(ObjFileFD); 155 return nullptr; 156 } 157 158 int onDataCallback(const void *Data, size_t NumBytes) { 159 return gCompileServer->pushInputBytes(Data, NumBytes) ? 1 : 0; 160 } 161 162 char *onEndCallback() { 163 gCompileServer->endInputStream(); 164 gCompileServer->waitForCompileThread(); 165 // TODO(jvoung): Also return UMA data. 166 if (gCompileServer->getErrorCode().value()) { 167 const std::string Error = gCompileServer->getErrorStream().getContents(); 168 return strdup(Error.empty() ? "Some error occurred" : Error.c_str()); 169 } 170 return nullptr; 171 } 172 173 struct nacl_irt_pnacl_compile_funcs SubzeroCallbacks { 174 &onInitCallback, &onDataCallback, &onEndCallback 175 }; 176 177 std::unique_ptr<llvm::raw_fd_ostream> getOutputStream(int FD) { 178 if (FD <= 0) 179 llvm::report_fatal_error("Invalid output FD"); 180 constexpr bool CloseOnDtor = true; 181 constexpr bool Unbuffered = false; 182 return std::unique_ptr<llvm::raw_fd_ostream>( 183 new llvm::raw_fd_ostream(FD, CloseOnDtor, Unbuffered)); 184 } 185 186 void fatalErrorHandler(void *UserData, const std::string &Reason, 187 bool GenCrashDialog) { 188 (void)GenCrashDialog; 189 BrowserCompileServer *Server = 190 reinterpret_cast<BrowserCompileServer *>(UserData); 191 Server->setFatalError(Reason); 192 // Only kill the current thread instead of the whole process. We need the 193 // server thread to remain alive in order to respond with the error message. 194 // We could also try to pthread_kill all other worker threads, but 195 // pthread_kill / raising signals is not supported by NaCl. We'll have to 196 // assume that the worker/emitter threads will be well behaved after a fatal 197 // error in other threads, and either get stuck waiting on input from a 198 // previous stage, or also call report_fatal_error. 199 pthread_exit(0); 200 } 201 202 /// Adapted from pnacl-llc's AddDefaultCPU() in srpc_main.cpp. 203 TargetArch getTargetArch() { 204 #if defined(__pnacl__) 205 switch (__builtin_nacl_target_arch()) { 206 case PnaclTargetArchitectureX86_32: 207 case PnaclTargetArchitectureX86_32_NonSFI: 208 return Target_X8632; 209 case PnaclTargetArchitectureX86_64: 210 return Target_X8664; 211 case PnaclTargetArchitectureARM_32: 212 case PnaclTargetArchitectureARM_32_NonSFI: 213 return Target_ARM32; 214 case PnaclTargetArchitectureMips_32: 215 return Target_MIPS32; 216 default: 217 llvm::report_fatal_error("no target architecture match."); 218 } 219 #elif defined(__i386__) 220 return Target_X8632; 221 #elif defined(__x86_64__) 222 return Target_X8664; 223 #elif defined(__arm__) 224 return Target_ARM32; 225 #else 226 // TODO(stichnot): Add mips. 227 #error "Unknown architecture" 228 #endif 229 } 230 231 } // end of anonymous namespace 232 233 BrowserCompileServer::~BrowserCompileServer() = default; 234 235 void BrowserCompileServer::run() { 236 gCompileServer = this; 237 getIRTInterfaces(); 238 gIRTFuncs.serve_translate_request(&SubzeroCallbacks); 239 } 240 241 void BrowserCompileServer::getParsedFlags(bool UseNumThreadsFromBrowser, 242 uint32_t NumThreads, int argc, 243 const char *const *argv) { 244 ClFlags::parseFlags(argc, argv); 245 ClFlags::getParsedClFlags(ClFlags::Flags); 246 // Set some defaults which aren't specified via the argv string. 247 if (UseNumThreadsFromBrowser) 248 ClFlags::Flags.setNumTranslationThreads(NumThreads); 249 ClFlags::Flags.setUseSandboxing(true); 250 ClFlags::Flags.setOutFileType(FT_Elf); 251 ClFlags::Flags.setTargetArch(getTargetArch()); 252 ClFlags::Flags.setInputFileFormat(llvm::PNaClFormat); 253 } 254 255 bool BrowserCompileServer::pushInputBytes(const void *Data, size_t NumBytes) { 256 // If there was an earlier error, do not attempt to push bytes to the 257 // QueueStreamer. Otherwise the thread could become blocked. 258 if (HadError.load()) 259 return true; 260 return InputStream->PutBytes( 261 const_cast<unsigned char *>( 262 reinterpret_cast<const unsigned char *>(Data)), 263 NumBytes) != NumBytes; 264 } 265 266 void BrowserCompileServer::setFatalError(const std::string &Reason) { 267 HadError.store(true); 268 Ctx->getStrError() << Reason; 269 // Make sure that the QueueStreamer is not stuck by signaling an early end. 270 InputStream->SetDone(); 271 } 272 273 ErrorCode &BrowserCompileServer::getErrorCode() { 274 if (HadError.load()) { 275 // HadError means report_fatal_error is called. Make sure that the 276 // LastError is not EC_None. We don't know the type of error so just pick 277 // some error category. 278 LastError.assign(EC_Translation); 279 } 280 return LastError; 281 } 282 283 void BrowserCompileServer::endInputStream() { InputStream->SetDone(); } 284 285 void BrowserCompileServer::startCompileThread(int ObjFD) { 286 InputStream = new llvm::QueueStreamer(); 287 bool LogStreamFailure = false; 288 int LogFD = STDOUT_FILENO; 289 if (getFlags().getLogFilename() == "-") { 290 // Common case, do nothing. 291 } else if (getFlags().getLogFilename() == "/dev/stderr") { 292 LogFD = STDERR_FILENO; 293 } else { 294 LogStreamFailure = true; 295 } 296 LogStream = getOutputStream(LogFD); 297 LogStream->SetUnbuffered(); 298 if (LogStreamFailure) { 299 *LogStream 300 << "Warning: Log file name must be either '-' or '/dev/stderr'\n"; 301 } 302 EmitStream = getOutputStream(ObjFD); 303 EmitStream->SetBufferSize(1 << 14); 304 std::unique_ptr<StringStream> ErrStrm(new StringStream()); 305 ErrorStream = std::move(ErrStrm); 306 ELFStream.reset(new ELFFileStreamer(*EmitStream.get())); 307 Ctx.reset(new GlobalContext(LogStream.get(), EmitStream.get(), 308 &ErrorStream->getStream(), ELFStream.get())); 309 CompileThread = std::thread([this]() { 310 llvm::install_fatal_error_handler(fatalErrorHandler, this); 311 Ctx->initParserThread(); 312 this->getCompiler().run(ClFlags::Flags, *Ctx.get(), 313 // Retain original reference, but the compiler 314 // (LLVM's MemoryObject) wants to handle deletion. 315 std::unique_ptr<llvm::DataStreamer>(InputStream)); 316 }); 317 } 318 319 } // end of namespace Ice 320 321 #else // !PNACL_BROWSER_TRANSLATOR 322 323 #include "llvm/Support/ErrorHandling.h" 324 325 namespace Ice { 326 327 BrowserCompileServer::~BrowserCompileServer() {} 328 329 void BrowserCompileServer::run() { 330 llvm::report_fatal_error("no browser hookups"); 331 } 332 333 ErrorCode &BrowserCompileServer::getErrorCode() { 334 llvm::report_fatal_error("no browser hookups"); 335 } 336 337 } // end of namespace Ice 338 339 #endif // PNACL_BROWSER_TRANSLATOR 340