// Copyright (c) 2013 Adobe Systems Inc // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "plugin-api.h" #include #include #include #include #include #include #include #include #include #include #include #include // htonl #include #include "SetAlchemySDKLocation.c" int execjava(std::vector &args) { char ** newargs = (char **)malloc((args.size()+1) * sizeof(char*)); newargs[args.size()] = NULL; for(int i=0; i syms; }; #ifndef DEFTMPDIR #define DEFTMPDIR "/tmp" #endif std::string output_name = ""; std::string tmpdir = DEFTMPDIR; std::string sysroot = ""; std::string swfpreloader = ""; std::vector jvmopts; std::list abcs; std::vector appended_abcs; bool enablevmargs, appendabcs, make_projector, make_swc, swf_ns, all_symbols_read = false; std::string swc_namespace, swf_namespace; std::map symbolclass_entries; bool enable_debugger = false, disable_telemetry = false, use_pthreads = false; bool run_swfresolve = true; std::vector vmargs; uint32_t swf_width = 1024, swf_height = 512, swf_version = 18; } namespace options { // Additional options to pass into the code generator. // Note: This array will contain all plugin options which are not claimed // as plugin exclusive to pass to the code generator. // For example, "generate-api-file" and "as"options are for the plugin // use only and will not be passed. static std::vector extra; static void process_plugin_option(const char* opt_) { if (opt_ == NULL) { return; } static bool saw_appendabc = false; static bool saw_preloader = false; std::string opt(opt_); if (opt.find("sysroot=") == 0) { sysroot = opt.substr(strlen("sysroot=")); } else if (opt == "disablevmargs") { enablevmargs = false; } else if (opt.find("vmarg=") == 0) { vmargs.push_back(opt.substr(6)); } else if (opt == "enabledebugger") { enable_debugger = true; } else if (opt == "disable-telemetry") { disable_telemetry = true; } else if (opt.find("jvmopt=") == 0) { jvmopts.push_back(opt.substr(strlen("jvmopt="))); } else if (opt == "swfpreloader") { saw_preloader = true; } else if (opt == "pthread") { use_pthreads = true; } else if (opt == "disableprojector") { make_projector = false; } else if (opt.find("emit-swc=") == 0) { make_swc = true; swc_namespace = opt.substr(strlen("emit-swc=")); } else if (opt.find("swf-ns=") == 0) { swf_ns = true; swf_namespace = opt.substr(strlen("swf-ns=")); } else if (opt.find("swf-version=") == 0) { swf_version = atoi(opt.substr(strlen("swf-version=")).c_str()); } else if (opt.find("symbolclass=") == 0) { std::string spec = opt.substr(strlen("symbolclass=")); size_t colon = spec.find(':'); if(colon != std::string::npos && colon > 0) { unsigned tagID = (unsigned)atoi(spec.substr(0, colon).c_str()); uint16_t tagID16 = (uint16_t)tagID; if(tagID != tagID16) { (*message)(LDPL_WARNING, "symbolclass tag ID %d will be truncated", tagID); } if(symbolclass_entries.find(tagID16) != symbolclass_entries.end()) { (*message)(LDPL_WARNING, "symbolclass tag ID %d redefined", tagID); } std::string clazz = spec.substr(colon+1); symbolclass_entries.insert(std::make_pair(tagID16, clazz)); } else { (*message)(LDPL_WARNING, "Couldn't parse symbolclass specifier: '%s'", spec.c_str()); } } else if (opt.find("swf-size=") == 0) { std::string spec = opt.substr(strlen("swf-size=")); size_t xpos = spec.find('x'); if(xpos != std::string::npos && xpos > 0) { swf_width = (unsigned)atoi(spec.substr(0, xpos).c_str()); swf_height = (unsigned)atoi(spec.substr(xpos+1).c_str()); } else { (*message)(LDPL_WARNING, "Couldn't parse swf size specifier: '%s'", spec.c_str()); } } else if (opt.find("appendabc") == 0) { saw_appendabc = true; } else if (saw_appendabc) { appended_abcs.push_back(opt); saw_appendabc = false; } else if (saw_preloader) { swfpreloader = opt; saw_preloader = false; } } } static ld_plugin_status claim_file_hook(const ld_plugin_input_file *file, int *claimed); static ld_plugin_status all_symbols_read_hook(void); static ld_plugin_status cleanup_hook(void); extern "C" ld_plugin_status onload(ld_plugin_tv *tv); ld_plugin_status onload(ld_plugin_tv *tv) { // We're given a pointer to the first transfer vector. We read through them // until we find one where tv_tag == LDPT_NULL. The REGISTER_* tagged values // contain pointers to functions that we need to call to register our own // hooks. The others are addresses of functions we can use to call into gold // for services. if(sysroot.length() == 0) { sysroot = getenv("FLASCC_INTERNAL_SDK_ROOT"); } enablevmargs = true; appendabcs = false; make_projector = true; make_swc = false; swf_ns = false; bool registeredClaimFile = false; for (; tv->tv_tag != LDPT_NULL; ++tv) { switch (tv->tv_tag) { case LDPT_API_VERSION: api_version = tv->tv_u.tv_val; break; case LDPT_GOLD_VERSION: // major * 100 + minor gold_version = tv->tv_u.tv_val; break; case LDPT_OUTPUT_NAME: output_name = tv->tv_u.tv_string; break; case LDPT_LINKER_OUTPUT: switch (tv->tv_u.tv_val) { case LDPO_REL: // .o case LDPO_DYN: // .so break; case LDPO_EXEC: // .exe break; default: (*message)(LDPL_ERROR, "Unknown output file type %d", tv->tv_u.tv_val); return LDPS_ERR; } // TODO: add an option to disable PIC. //output_type = LTO_CODEGEN_PIC_MODEL_DYNAMIC_NO_PIC; break; case LDPT_OPTION: options::process_plugin_option(tv->tv_u.tv_string); break; case LDPT_REGISTER_CLAIM_FILE_HOOK: { ld_plugin_register_claim_file callback; callback = tv->tv_u.tv_register_claim_file; if ((*callback)(claim_file_hook) != LDPS_OK) { return LDPS_ERR; } registeredClaimFile = true; } break; case LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK: { ld_plugin_register_all_symbols_read callback; callback = tv->tv_u.tv_register_all_symbols_read; if ((*callback)(all_symbols_read_hook) != LDPS_OK) { return LDPS_ERR; } } break; case LDPT_REGISTER_CLEANUP_HOOK: { ld_plugin_register_cleanup callback; callback = tv->tv_u.tv_register_cleanup; if ((*callback)(cleanup_hook) != LDPS_OK) { return LDPS_ERR; } } break; case LDPT_ADD_SYMBOLS: add_symbols = tv->tv_u.tv_add_symbols; break; case LDPT_GET_SYMBOLS: get_symbols = tv->tv_u.tv_get_symbols; break; case LDPT_ADD_INPUT_FILE: add_input_file = tv->tv_u.tv_add_input_file; break; case LDPT_ADD_INPUT_LIBRARY: add_input_library = tv->tv_u.tv_add_input_file; break; case LDPT_SET_EXTRA_LIBRARY_PATH: set_extra_library_path = tv->tv_u.tv_set_extra_library_path; break; case LDPT_SET_ALWAYS_RESCAN: tv->tv_u.tv_set_always_rescan(); // call the function to set the flag break; case LDPT_MESSAGE: message = tv->tv_u.tv_message; break; default: break; } } if(use_pthreads && swf_version < 18) { (*message)(LDPL_WARNING, "SWF version must be at least 18 to use pthreads, your SWF version will be forced to 18."); swf_version = 18; } if (!registeredClaimFile) { (*message)(LDPL_ERROR, "register_claim_file not passed to %s.", PLUGIN_NAME); return LDPS_ERR; } if (!add_symbols) { (*message)(LDPL_ERROR, "add_symbols not passed to %s.", PLUGIN_NAME); return LDPS_ERR; } return LDPS_OK; } // state for abcnm process running as a "server" -- handling multiple abcs static pthread_mutex_t abcnmMutex = PTHREAD_MUTEX_INITIALIZER; static int abcnmPID = -1; static int abcnmOut[2]; static int abcnmIn[2]; static FILE *abcnmFIn; static bool checkForJVMError(const std::string& str) { return str.find("Error: Could not create the Java Virtual Machine") != std::string::npos || str.find("Error occurred during initialization of VM") != std::string::npos; } // extract symbols from the abc static ld_plugin_status extract_syms(std::vector *syms, const char *buf, size_t nbyte) { if(pthread_mutex_lock(&abcnmMutex)) { (*message)(LDPL_ERROR, "Failed to lock abcnm mutex"); return LDPS_ERR; } if(abcnmPID < 0) { // abcnm process not created yet if(pipe(abcnmOut) || pipe(abcnmIn)) { (*message)(LDPL_ERROR, "Failed to create pipes for abcnm"); pthread_mutex_unlock(&abcnmMutex); return LDPS_ERR; } if((abcnmPID = fork()) < 0) { close(abcnmOut[0]); close(abcnmOut[1]); close(abcnmIn[0]); close(abcnmIn[1]); (*message)(LDPL_ERROR, "Failed to create abcnm fork"); pthread_mutex_unlock(&abcnmMutex); return LDPS_ERR; } if(!abcnmPID) { // fork for exec-ing abcnm close(abcnmOut[1]); close(abcnmIn[0]); if(dup2(abcnmOut[0], 0) < 0 || dup2(abcnmIn[1], 1) < 0) { fprintf(stderr, "Failed to redirect IO for abcnm\n"); exit(-1); } close(abcnmOut[0]); close(abcnmIn[1]); std::string cp = sysroot + "/usr/lib"; std::string alctool = sysroot + "/usr/lib/alctool.jar"; std::vector args; args.push_back("java"); if(!jvmopts.size()) args.push_back("-Xmx1G"); else for(unsigned int i=0; i 0) { ssize_t written = write(abcnmOut[1], buf, nbyte); if(written < 1) { (*message)(LDPL_ERROR, "Failed to write abc data to abcnm"); pthread_mutex_unlock(&abcnmMutex); return LDPS_ERR; } nbyte -= written; buf += written; } size = 0; if(write(abcnmOut[1], &size, sizeof(size)) != sizeof(size)) { (*message)(LDPL_ERROR, "Failed to write abc eof to abcnm"); pthread_mutex_unlock(&abcnmMutex); return LDPS_ERR; } syms->clear(); char symEntry[16384]; while(fgets(symEntry, sizeof(symEntry), abcnmFIn)) { if(!symEntry[0] || symEntry[0] == '\n' && !symEntry[1]) { // empty line... done! break; } // validate the format... // should be 9 spaces, one symbol type char, another space, and the non-zero length symbol bool fmtOk = true; for(int i = 0; i < 9; i++) if(symEntry[i] != ' ') { fmtOk = false; } if(fmtOk && !isalpha(symEntry[9])) { fmtOk = false; } if(fmtOk && symEntry[10] != ' ') { fmtOk = false; } if(fmtOk && symEntry[11] == 0) { fmtOk = false; } if(!fmtOk) { if(checkForJVMError(std::string(&symEntry[0]))) { (*message)(LDPL_ERROR, "Error: Unable to launch the Java Virtual Machine. \n" "This usually means you have a 32bit JVM installed or have set your Java heap size too large.\n" "Try lowering the Java heap size by passing \"-jvmopt=-Xmx1G\" to gcc/g++."); } else { (*message)(LDPL_ERROR, "Couldn't parse abcnm output: '%s'", symEntry); } while(fgets(symEntry, sizeof(symEntry), abcnmFIn)) {} pthread_mutex_unlock(&abcnmMutex); return LDPS_ERR; } char symType = symEntry[9]; switch(symType) { case 't': case 'd': continue; // ld doesn't care about these } char *end = symEntry + strlen(symEntry) - 1; if(*end == '\n') { *end = 0; } syms->push_back(ld_plugin_symbol()); ld_plugin_symbol *sym = &syms->back(); sym->name = strdup(symEntry + 11); sym->version = NULL; sym->visibility = LDPV_DEFAULT; sym->size = 0; sym->comdat_key = NULL; sym->resolution = LDPR_UNKNOWN; switch(symType) { // symbol type default: (*message)(LDPL_WARNING, "Unrecognized symbol type '%c' (assuming definition)", symType); // fall through case 'T': case 'D': sym->def = LDPK_DEF; break; case 'U': sym->def = LDPK_UNDEF; break; case 'W': sym->def = LDPK_WEAKDEF; break; case 'C': sym->def = LDPK_COMMON; break; } } pthread_mutex_unlock(&abcnmMutex); return LDPS_OK; } /// claim_file_hook - called by gold to see whether this file is one that /// our plugin can handle. We'll try to open it and register all the symbols /// with add_symbol if possible. static ld_plugin_status claim_file_hook(const ld_plugin_input_file *file, int *claimed) { char *buf = (char*)malloc(file->filesize); if (!buf) { (*message)(LDPL_ERROR, "Failed to allocate buffer for file of size: %d\n", file->filesize); return LDPS_ERR; } if (file->offset) { // Gold has found what might be IR part-way inside of a file, such as // an .a archive. if (lseek(file->fd, file->offset, SEEK_SET) == -1) { (*message)(LDPL_ERROR, "Failed to seek to archive member of %s at offset %d: %d\n", file->name, file->offset, strerror(errno)); free(buf); return LDPS_ERR; } if (read(file->fd, buf, file->filesize) != file->filesize) { (*message)(LDPL_ERROR, "Failed to read archive member of %s at offset %d: %s\n", file->name, file->offset, strerror(errno)); free(buf); return LDPS_ERR; } } else { lseek(file->fd, 0, SEEK_SET); read(file->fd, buf, file->filesize); } if( file->filesize > 4 && buf[0] == 0x10 && buf[1] == 0x00 && buf[2] == 0x2E && buf[3] == 0x00) { *claimed = 1; claimed_file *cf = NULL; abcs.resize(abcs.size() + 1); cf = &abcs.back(); //cf->name = file->name; cf->handle = file->handle; cf->contents = buf; cf->contentsLength = file->filesize; if(!all_symbols_read) { ld_plugin_status extractSymStatus = extract_syms(&cf->syms, buf, file->filesize); if(extractSymStatus != LDPS_OK) { free(buf); return extractSymStatus; } if (!cf->syms.empty()) { if ((*add_symbols)(cf->handle, cf->syms.size(), &cf->syms[0]) != LDPS_OK) { (*message)(LDPL_ERROR, "Unable to add symbols!"); return LDPS_ERR; } } } return LDPS_OK; } // wasn't a valid abc file free(buf); return LDPS_OK; } static ld_plugin_status all_symbols_read_hook(void) { all_symbols_read = true; /* fprintf(stderr, "all_symbols_read_hook\n"); for (std::list::iterator I = abcs.begin(), E = abcs.end(); I != E; ++I) { (*get_symbols)(I->handle, I->syms.size(), &I->syms[0]); for (unsigned i = 0, e = I->syms.size(); i != e; i++) { fprintf(stderr, "sym: %s %d\n", I->syms[i].name, I->syms[i].resolution); } } */ return LDPS_OK; } char* readContents(const char *path, uint32_t &len) { len = 0; std::ifstream f(path, std::ios::in | std::ios::binary); if (!f) { return NULL; } f.seekg(0,std::ios::end); len = (uint32_t)f.tellg(); f.seekg(0,std::ios::beg); char *buffer = new char[len]; f.read(&buffer[0], len); return buffer; } class BitWriter { char _accum; char _accumCount; std::string _buf; public: BitWriter() : _accum(0), _accumCount(0) {} std::string bytes() const { std::string result = _buf; if(_accumCount) { result += (_accum << (8-_accumCount)); } return result; } void write(int bit) { _accum <<= 1; _accum |= (bit ? 1 : 0); if(++_accumCount == 8) { _buf += _accum; _accumCount = 0; _accum = 0; } } void write(uint64_t val, int size) { while(size--) { write((val >> size) & 1); } } }; static std::string rect(unsigned xMin, unsigned xMax, unsigned yMin, unsigned yMax) { BitWriter bw; bw.write(31, 5); bw.write(xMin, 31); bw.write(xMax, 31); bw.write(yMin, 31); bw.write(yMax, 31); return bw.bytes(); } static ld_plugin_status cleanup_hook(void) { // clean up after abcnm if(!pthread_mutex_destroy(&abcnmMutex) && abcnmPID >= 0) { close(abcnmOut[1]); fclose(abcnmFIn); int dummy; wait(&dummy); } std::ofstream outputfile; if(make_projector) { outputfile.open(output_name.c_str(), std::ios::out | std::ios::binary); std::string avmshellpath; if(enable_debugger) { avmshellpath = sysroot + "/usr/bin/avmshell-release-debugger"; } else { avmshellpath = sysroot + "/usr/bin/avmshell"; } std::ifstream avmshell(avmshellpath.c_str(), std::ios::in | std::ios::binary); std::copy( std::istreambuf_iterator(avmshell), std::istreambuf_iterator( ), std::ostreambuf_iterator(outputfile)); } char resolveTmpPath[PATH_MAX]; strcpy(resolveTmpPath, (tmpdir + "/swfresolve.in.XXXXXX").substr(0, PATH_MAX-1).c_str()); mktemp(resolveTmpPath); std::ofstream swfStream; swfStream.open(resolveTmpPath, std::ios::out | std::ios::binary); int swfstart = outputfile.tellp(); char swfHeader[8]; swfHeader[0] = 'F'; swfHeader[1] = 'W'; swfHeader[2] = 'S'; swfHeader[3] = swf_version; swfHeader[4] = 0x0; // ignored; swfresolve doesn't care about size bytes swfHeader[5] = 0x0; swfHeader[6] = 0x0; swfHeader[7] = 0x0; swfStream.write(swfHeader, sizeof(swfHeader)); std::string rectBytes = rect(0, 20*swf_width, 0, 20*swf_height); swfStream.write(rectBytes.data(), rectBytes.size()); swfStream.put(30); // framerate swfStream.put(0); swfStream.put(1); // framecount swfStream.put(0); for(int i=0; icontents = readContents(appended_abcs[i].c_str(), cf->contentsLength); if(cf->contentsLength == 0) { fprintf(stderr, "Can't append empty ABC file: '%s'\n", appended_abcs[i].c_str()); abort(); } } char fileattributesRecord[10]; memset(fileattributesRecord, 0, sizeof(fileattributesRecord)); *(uint16_t*)&fileattributesRecord[0] = (69 << 6) | 63; *(uint32_t*)&fileattributesRecord[2] = 4; *(uint32_t*)&fileattributesRecord[6] = 8; // AS3 swfStream.write(fileattributesRecord, sizeof(fileattributesRecord)); if(!disable_telemetry) { char EnableTelemetry[4] = {}; *(uint16_t*)&EnableTelemetry[0] = (93 << 6) | 2; *(uint16_t*)&EnableTelemetry[2] = 0; swfStream.write(EnableTelemetry, sizeof(EnableTelemetry)); } //printf("filling swf with %d abcs\n", (int)abcs.size()); int abccount = 0; for (std::list::iterator I = abcs.begin(), E = abcs.end(); I != E; ++I) { claimed_file &cf = *I; std::ostringstream abcnamebuilder; abcnamebuilder << "ABC" << abccount++; std::string abcname = abcnamebuilder.str(); char doABCHeader[10]; *(uint16_t*)&doABCHeader[0] = (82 << 6) | 63; // DoABC, extended length *(uint32_t*)&doABCHeader[2] = (cf.contentsLength + 4 + abcname.length() + 1); *(uint32_t*)&doABCHeader[6] = 0; // flags swfStream.write(doABCHeader, sizeof(doABCHeader)); swfStream.write(abcname.c_str(), abcname.length()); char nullbyte = 0; swfStream.write(&nullbyte, 1); swfStream.write(cf.contents, cf.contentsLength); } if(symbolclass_entries.size() > 0) { //printf("%d SymbolClass entries\n", (int)symbolclass_entries.size()); char symbolclassHeader[8]; uint32_t dataLen = 0; std::map::const_iterator i, e; i = symbolclass_entries.begin(); e = symbolclass_entries.end(); for(; i != e; i++) { //printf("SymbolClass entry: %d '%s'\n", i->first, i->second.c_str()); dataLen += 3 + i->second.length(); // tag, string, null term } *(uint16_t*)&symbolclassHeader[0] = (76 << 6) | 63; // SymbolClass, extended length *(uint32_t*)&symbolclassHeader[2] = 2 + dataLen; *(uint16_t*)&symbolclassHeader[6] = (uint16_t)symbolclass_entries.size(); swfStream.write(symbolclassHeader, sizeof(symbolclassHeader)); i = symbolclass_entries.begin(); e = symbolclass_entries.end(); for(; i != e; i++) { uint16_t tagID = i->first; const std::string &clazz = i->second; swfStream.write((char *)&tagID, sizeof(tagID)); swfStream.write(clazz.c_str(), clazz.length()+1); } } if(enable_debugger) { char enabledebugger2Record[9]; *(uint16_t*)&enabledebugger2Record[0] = (64 << 6) | 63; *(uint32_t*)&enabledebugger2Record[2] = 3; *(uint16_t*)&enabledebugger2Record[6] = 0; *(uint8_t*)&enabledebugger2Record[8] = 0; swfStream.write(enabledebugger2Record, sizeof(enabledebugger2Record)); } uint16_t suffixTags[2] = { 1 << 6, // ShowFrame 0 // End }; swfStream.write((char*)&suffixTags[0], sizeof(suffixTags)); swfStream.close(); // Run SwfResolve/SwcGen { int pid = fork(); if(pid < 0) { (*message)(LDPL_ERROR, "Failed to fork for swfresolve\n"); return LDPS_ERR; } if(!pid) { std::string cp = sysroot + "/usr/lib"; std::string alctool = sysroot + "/usr/lib/alctool.jar"; std::vector args; args.push_back("java"); for(unsigned int i=0; i 0) { args.push_back("-swfpreloader"); args.push_back(swfpreloader); } } args.push_back(resolveTmpPath); int r = execjava(args); fprintf(stderr, "Failed to exec swfresolve"); exit(-1); } else { wait(&pid); } } if(make_projector) { std::ifstream resolvedSwf(resolveTmpPath, std::ios::in | std::ios::binary); std::copy( std::istreambuf_iterator(resolvedSwf), std::istreambuf_iterator( ), std::ostreambuf_iterator(outputfile)); int swfend = (int)outputfile.tellp(); int swfsize = swfend - swfstart; // vm arguments, null separated if(enablevmargs) { for(int i=0; i