#include "rsCpuExecutable.h" #include "rsCppUtils.h" #include <fstream> #include <set> #include <memory> #ifdef RS_COMPATIBILITY_LIB #include <stdio.h> #include <sys/stat.h> #include <unistd.h> #else #include "bcc/Config/Config.h" #endif #include <dlfcn.h> namespace android { namespace renderscript { namespace { // Check if a path exists and attempt to create it if it doesn't. static bool ensureCacheDirExists(const char *path) { if (access(path, R_OK | W_OK | X_OK) == 0) { // Done if we can rwx the directory return true; } if (mkdir(path, 0700) == 0) { return true; } return false; } // Copy the file named \p srcFile to \p dstFile. // Return 0 on success and -1 if anything wasn't copied. static int copyFile(const char *dstFile, const char *srcFile) { std::ifstream srcStream(srcFile); if (!srcStream) { ALOGE("Could not verify or read source file: %s", srcFile); return -1; } std::ofstream dstStream(dstFile); if (!dstStream) { ALOGE("Could not verify or write destination file: %s", dstFile); return -1; } dstStream << srcStream.rdbuf(); if (!dstStream) { ALOGE("Could not write destination file: %s", dstFile); return -1; } srcStream.close(); dstStream.close(); return 0; } static std::string findSharedObjectName(const char *cacheDir, const char *resName) { #ifndef RS_SERVER std::string scriptSOName(cacheDir); #if defined(RS_COMPATIBILITY_LIB) && !defined(__LP64__) size_t cutPos = scriptSOName.rfind("cache"); if (cutPos != std::string::npos) { scriptSOName.erase(cutPos); } else { ALOGE("Found peculiar cacheDir (missing \"cache\"): %s", cacheDir); } scriptSOName.append("/lib/librs."); #else scriptSOName.append("/librs."); #endif // RS_COMPATIBILITY_LIB #else std::string scriptSOName("lib"); #endif // RS_SERVER scriptSOName.append(resName); scriptSOName.append(".so"); return scriptSOName; } } // anonymous namespace const char* SharedLibraryUtils::LD_EXE_PATH = "/system/bin/ld.mc"; const char* SharedLibraryUtils::RS_CACHE_DIR = "com.android.renderscript.cache"; #ifndef RS_COMPATIBILITY_LIB bool SharedLibraryUtils::createSharedLibrary(const char *driverName, const char *cacheDir, const char *resName) { std::string sharedLibName = findSharedObjectName(cacheDir, resName); std::string objFileName = cacheDir; objFileName.append("/"); objFileName.append(resName); objFileName.append(".o"); // Should be something like "libRSDriver.so". std::string linkDriverName = driverName; // Remove ".so" and replace "lib" with "-l". // This will leave us with "-lRSDriver" instead. linkDriverName.erase(linkDriverName.length() - 3); linkDriverName.replace(0, 3, "-l"); const char *compiler_rt = SYSLIBPATH"/libcompiler_rt.so"; const char *mTriple = "-mtriple=" DEFAULT_TARGET_TRIPLE_STRING; const char *libPath = "--library-path=" SYSLIBPATH; const char *vendorLibPath = "--library-path=" SYSLIBPATH_VENDOR; std::vector<const char *> args = { LD_EXE_PATH, "-shared", "-nostdlib", compiler_rt, mTriple, vendorLibPath, libPath, linkDriverName.c_str(), "-lm", "-lc", objFileName.c_str(), "-o", sharedLibName.c_str(), nullptr }; return rsuExecuteCommand(LD_EXE_PATH, args.size()-1, args.data()); } #endif // RS_COMPATIBILITY_LIB const char* RsdCpuScriptImpl::BCC_EXE_PATH = "/system/bin/bcc"; void* SharedLibraryUtils::loadSharedLibrary(const char *cacheDir, const char *resName, const char *nativeLibDir, bool* alreadyLoaded) { void *loaded = nullptr; #if defined(RS_COMPATIBILITY_LIB) && defined(__LP64__) std::string scriptSOName = findSharedObjectName(nativeLibDir, resName); #else std::string scriptSOName = findSharedObjectName(cacheDir, resName); #endif // We should check if we can load the library from the standard app // location for shared libraries first. loaded = loadSOHelper(scriptSOName.c_str(), cacheDir, resName, alreadyLoaded); if (loaded == nullptr) { ALOGE("Unable to open shared library (%s): %s", scriptSOName.c_str(), dlerror()); #ifdef RS_COMPATIBILITY_LIB // One final attempt to find the library in "/system/lib". // We do this to allow bundled applications to use the compatibility // library fallback path. Those applications don't have a private // library path, so they need to install to the system directly. // Note that this is really just a testing path. std::string scriptSONameSystem("/system/lib/librs."); scriptSONameSystem.append(resName); scriptSONameSystem.append(".so"); loaded = loadSOHelper(scriptSONameSystem.c_str(), cacheDir, resName); if (loaded == nullptr) { ALOGE("Unable to open system shared library (%s): %s", scriptSONameSystem.c_str(), dlerror()); } #endif } return loaded; } String8 SharedLibraryUtils::getRandomString(size_t len) { char buf[len + 1]; for (size_t i = 0; i < len; i++) { uint32_t r = arc4random() & 0xffff; r %= 62; if (r < 26) { // lowercase buf[i] = 'a' + r; } else if (r < 52) { // uppercase buf[i] = 'A' + (r - 26); } else { // Use a number buf[i] = '0' + (r - 52); } } buf[len] = '\0'; return String8(buf); } void* SharedLibraryUtils::loadSOHelper(const char *origName, const char *cacheDir, const char *resName, bool *alreadyLoaded) { // Keep track of which .so libraries have been loaded. Once a library is // in the set (per-process granularity), we must instead make a copy of // the original shared object (randomly named .so file) and load that one // instead. If we don't do this, we end up aliasing global data between // the various Script instances (which are supposed to be completely // independent). static std::set<std::string> LoadedLibraries; void *loaded = nullptr; // Skip everything if we don't even have the original library available. if (access(origName, F_OK) != 0) { return nullptr; } // Common path is that we have not loaded this Script/library before. if (LoadedLibraries.find(origName) == LoadedLibraries.end()) { if (alreadyLoaded != nullptr) { *alreadyLoaded = false; } loaded = dlopen(origName, RTLD_NOW | RTLD_LOCAL); if (loaded) { LoadedLibraries.insert(origName); } return loaded; } if (alreadyLoaded != nullptr) { *alreadyLoaded = true; } std::string newName(cacheDir); // Append RS_CACHE_DIR only if it is not found in cacheDir // In driver mode, RS_CACHE_DIR is already appended to cacheDir. if (newName.find(RS_CACHE_DIR) == std::string::npos) { newName.append("/"); newName.append(RS_CACHE_DIR); newName.append("/"); } if (!ensureCacheDirExists(newName.c_str())) { ALOGE("Could not verify or create cache dir: %s", cacheDir); return nullptr; } // Construct an appropriately randomized filename for the copy. newName.append("librs."); newName.append(resName); newName.append("#"); newName.append(getRandomString(6).string()); // 62^6 potential filename variants. newName.append(".so"); int r = copyFile(newName.c_str(), origName); if (r != 0) { ALOGE("Could not create copy %s -> %s", origName, newName.c_str()); return nullptr; } loaded = dlopen(newName.c_str(), RTLD_NOW | RTLD_LOCAL); r = unlink(newName.c_str()); if (r != 0) { ALOGE("Could not unlink copy %s", newName.c_str()); } if (loaded) { LoadedLibraries.insert(newName.c_str()); } return loaded; } // MAXLINESTR must be compatible with operator '#' in C macro. #define MAXLINESTR 499 // MAXLINE must be (MAXLINESTR + 1), representing the size of a C string // containing MAXLINESTR non-null chars plus a null. #define MAXLINE (MAXLINESTR + 1) #define MAKE_STR_HELPER(S) #S #define MAKE_STR(S) MAKE_STR_HELPER(S) #define EXPORT_VAR_STR "exportVarCount: " #define EXPORT_FUNC_STR "exportFuncCount: " #define EXPORT_FOREACH_STR "exportForEachCount: " #define EXPORT_REDUCE_STR "exportReduceCount: " #define OBJECT_SLOT_STR "objectSlotCount: " #define PRAGMA_STR "pragmaCount: " #define THREADABLE_STR "isThreadable: " #define CHECKSUM_STR "buildChecksum: " // Copy up to a newline or size chars from str -> s, updating str // Returns s when successful and nullptr when '\0' is finally reached. static char* strgets(char *s, int size, const char **ppstr) { if (!ppstr || !*ppstr || **ppstr == '\0' || size < 1) { return nullptr; } int i; for (i = 0; i < (size - 1); i++) { s[i] = **ppstr; (*ppstr)++; if (s[i] == '\0') { return s; } else if (s[i] == '\n') { s[i+1] = '\0'; return s; } } // size has been exceeded. s[i] = '\0'; return s; } ScriptExecutable* ScriptExecutable::createFromSharedObject( void* sharedObj, uint32_t expectedChecksum) { char line[MAXLINE]; size_t varCount = 0; size_t funcCount = 0; size_t forEachCount = 0; size_t reduceCount = 0; size_t objectSlotCount = 0; size_t pragmaCount = 0; bool isThreadable = true; void** fieldAddress = nullptr; bool* fieldIsObject = nullptr; char** fieldName = nullptr; InvokeFunc_t* invokeFunctions = nullptr; ForEachFunc_t* forEachFunctions = nullptr; uint32_t* forEachSignatures = nullptr; ReduceDescription* reduceDescriptions = nullptr; const char ** pragmaKeys = nullptr; const char ** pragmaValues = nullptr; uint32_t checksum = 0; const char *rsInfo = (const char *) dlsym(sharedObj, kRsInfo); int numEntries = 0; const int *rsGlobalEntries = (const int *) dlsym(sharedObj, kRsGlobalEntries); const char **rsGlobalNames = (const char **) dlsym(sharedObj, kRsGlobalNames); const void **rsGlobalAddresses = (const void **) dlsym(sharedObj, kRsGlobalAddresses); const size_t *rsGlobalSizes = (const size_t *) dlsym(sharedObj, kRsGlobalSizes); const uint32_t *rsGlobalProperties = (const uint32_t *) dlsym(sharedObj, kRsGlobalProperties); if (strgets(line, MAXLINE, &rsInfo) == nullptr) { return nullptr; } if (sscanf(line, EXPORT_VAR_STR "%zu", &varCount) != 1) { ALOGE("Invalid export var count!: %s", line); return nullptr; } fieldAddress = new void*[varCount]; if (fieldAddress == nullptr) { return nullptr; } fieldIsObject = new bool[varCount]; if (fieldIsObject == nullptr) { goto error; } fieldName = new char*[varCount]; if (fieldName == nullptr) { goto error; } for (size_t i = 0; i < varCount; ++i) { if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } char *c = strrchr(line, '\n'); if (c) { *c = '\0'; } void* addr = dlsym(sharedObj, line); if (addr == nullptr) { ALOGE("Failed to find variable address for %s: %s", line, dlerror()); // Not a critical error if we don't find a global variable. } fieldAddress[i] = addr; fieldIsObject[i] = false; fieldName[i] = new char[strlen(line)+1]; strcpy(fieldName[i], line); } if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, EXPORT_FUNC_STR "%zu", &funcCount) != 1) { ALOGE("Invalid export func count!: %s", line); goto error; } invokeFunctions = new InvokeFunc_t[funcCount]; if (invokeFunctions == nullptr) { goto error; } for (size_t i = 0; i < funcCount; ++i) { if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } char *c = strrchr(line, '\n'); if (c) { *c = '\0'; } invokeFunctions[i] = (InvokeFunc_t) dlsym(sharedObj, line); if (invokeFunctions[i] == nullptr) { ALOGE("Failed to get function address for %s(): %s", line, dlerror()); goto error; } } if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, EXPORT_FOREACH_STR "%zu", &forEachCount) != 1) { ALOGE("Invalid export forEach count!: %s", line); goto error; } forEachFunctions = new ForEachFunc_t[forEachCount]; if (forEachFunctions == nullptr) { goto error; } forEachSignatures = new uint32_t[forEachCount]; if (forEachSignatures == nullptr) { goto error; } for (size_t i = 0; i < forEachCount; ++i) { unsigned int tmpSig = 0; char tmpName[MAXLINE]; if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, "%u - %" MAKE_STR(MAXLINESTR) "s", &tmpSig, tmpName) != 2) { ALOGE("Invalid export forEach!: %s", line); goto error; } // Lookup the expanded ForEach kernel. strncat(tmpName, ".expand", MAXLINESTR-strlen(tmpName)); forEachSignatures[i] = tmpSig; forEachFunctions[i] = (ForEachFunc_t) dlsym(sharedObj, tmpName); if (i != 0 && forEachFunctions[i] == nullptr && strcmp(tmpName, "root.expand")) { // Ignore missing root.expand functions. // root() is always specified at location 0. ALOGE("Failed to find forEach function address for %s(): %s", tmpName, dlerror()); goto error; } } // Read general reduce kernels if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, EXPORT_REDUCE_STR "%zu", &reduceCount) != 1) { ALOGE("Invalid export reduce new count!: %s", line); goto error; } reduceDescriptions = new ReduceDescription[reduceCount]; if (reduceDescriptions == nullptr) { goto error; } for (size_t i = 0; i < reduceCount; ++i) { static const char kNoName[] = "."; unsigned int tmpSig = 0; size_t tmpSize = 0; char tmpNameReduce[MAXLINE]; char tmpNameInitializer[MAXLINE]; char tmpNameAccumulator[MAXLINE]; char tmpNameCombiner[MAXLINE]; char tmpNameOutConverter[MAXLINE]; char tmpNameHalter[MAXLINE]; if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } #define DELIMNAME " - %" MAKE_STR(MAXLINESTR) "s" if (sscanf(line, "%u - %zu" DELIMNAME DELIMNAME DELIMNAME DELIMNAME DELIMNAME DELIMNAME, &tmpSig, &tmpSize, tmpNameReduce, tmpNameInitializer, tmpNameAccumulator, tmpNameCombiner, tmpNameOutConverter, tmpNameHalter) != 8) { ALOGE("Invalid export reduce new!: %s", line); goto error; } #undef DELIMNAME // For now, we expect // - Reduce and Accumulator names // - optional Initializer, Combiner, and OutConverter name // - no Halter name if (!strcmp(tmpNameReduce, kNoName) || !strcmp(tmpNameAccumulator, kNoName)) { ALOGE("Expected reduce and accumulator names!: %s", line); goto error; } if (strcmp(tmpNameHalter, kNoName)) { ALOGE("Did not expect halter name!: %s", line); goto error; } // The current implementation does not use the signature // or reduce name. reduceDescriptions[i].accumSize = tmpSize; // Process the (optional) initializer. if (strcmp(tmpNameInitializer, kNoName)) { // Lookup the original user-written initializer. if (!(reduceDescriptions[i].initFunc = (ReduceInitializerFunc_t) dlsym(sharedObj, tmpNameInitializer))) { ALOGE("Failed to find initializer function address for %s(): %s", tmpNameInitializer, dlerror()); goto error; } } else { reduceDescriptions[i].initFunc = nullptr; } // Lookup the expanded accumulator. strncat(tmpNameAccumulator, ".expand", MAXLINESTR-strlen(tmpNameAccumulator)); if (!(reduceDescriptions[i].accumFunc = (ReduceAccumulatorFunc_t) dlsym(sharedObj, tmpNameAccumulator))) { ALOGE("Failed to find accumulator function address for %s(): %s", tmpNameAccumulator, dlerror()); goto error; } // Process the (optional) combiner. if (strcmp(tmpNameCombiner, kNoName)) { // Lookup the original user-written combiner. if (!(reduceDescriptions[i].combFunc = (ReduceCombinerFunc_t) dlsym(sharedObj, tmpNameCombiner))) { ALOGE("Failed to find combiner function address for %s(): %s", tmpNameCombiner, dlerror()); goto error; } } else { reduceDescriptions[i].combFunc = nullptr; } // Process the (optional) outconverter. if (strcmp(tmpNameOutConverter, kNoName)) { // Lookup the original user-written outconverter. if (!(reduceDescriptions[i].outFunc = (ReduceOutConverterFunc_t) dlsym(sharedObj, tmpNameOutConverter))) { ALOGE("Failed to find outconverter function address for %s(): %s", tmpNameOutConverter, dlerror()); goto error; } } else { reduceDescriptions[i].outFunc = nullptr; } } if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, OBJECT_SLOT_STR "%zu", &objectSlotCount) != 1) { ALOGE("Invalid object slot count!: %s", line); goto error; } for (size_t i = 0; i < objectSlotCount; ++i) { uint32_t varNum = 0; if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, "%u", &varNum) != 1) { ALOGE("Invalid object slot!: %s", line); goto error; } if (varNum < varCount) { fieldIsObject[varNum] = true; } } #ifndef RS_COMPATIBILITY_LIB // Do not attempt to read pragmas or isThreadable flag in compat lib path. // Neither is applicable for compat lib if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } if (sscanf(line, PRAGMA_STR "%zu", &pragmaCount) != 1) { ALOGE("Invalid pragma count!: %s", line); goto error; } pragmaKeys = new const char*[pragmaCount]; if (pragmaKeys == nullptr) { goto error; } pragmaValues = new const char*[pragmaCount]; if (pragmaValues == nullptr) { goto error; } bzero(pragmaKeys, sizeof(char*) * pragmaCount); bzero(pragmaValues, sizeof(char*) * pragmaCount); for (size_t i = 0; i < pragmaCount; ++i) { if (strgets(line, MAXLINE, &rsInfo) == nullptr) { ALOGE("Unable to read pragma at index %zu!", i); goto error; } char key[MAXLINE]; char value[MAXLINE] = ""; // initialize in case value is empty // pragmas can just have a key and no value. Only check to make sure // that the key is not empty if (sscanf(line, "%" MAKE_STR(MAXLINESTR) "s - %" MAKE_STR(MAXLINESTR) "s", key, value) == 0 || strlen(key) == 0) { ALOGE("Invalid pragma value!: %s", line); goto error; } char *pKey = new char[strlen(key)+1]; strcpy(pKey, key); pragmaKeys[i] = pKey; char *pValue = new char[strlen(value)+1]; strcpy(pValue, value); pragmaValues[i] = pValue; //ALOGE("Pragma %zu: Key: '%s' Value: '%s'", i, pKey, pValue); } if (strgets(line, MAXLINE, &rsInfo) == nullptr) { goto error; } char tmpFlag[4]; if (sscanf(line, THREADABLE_STR "%3s", tmpFlag) != 1) { ALOGE("Invalid threadable flag!: %s", line); goto error; } if (strcmp(tmpFlag, "yes") == 0) { isThreadable = true; } else if (strcmp(tmpFlag, "no") == 0) { isThreadable = false; } else { ALOGE("Invalid threadable flag!: %s", tmpFlag); goto error; } if (strgets(line, MAXLINE, &rsInfo) != nullptr) { if (sscanf(line, CHECKSUM_STR "%08x", &checksum) != 1) { ALOGE("Invalid checksum flag!: %s", line); goto error; } } else { ALOGE("Missing checksum in shared obj file"); goto error; } if (expectedChecksum != 0 && checksum != expectedChecksum) { ALOGE("Found invalid checksum. Expected %08x, got %08x\n", expectedChecksum, checksum); goto error; } #endif // RS_COMPATIBILITY_LIB // Read in information about mutable global variables provided by bcc's // RSGlobalInfoPass if (rsGlobalEntries) { numEntries = *rsGlobalEntries; if (numEntries > 0) { rsAssert(rsGlobalNames); rsAssert(rsGlobalAddresses); rsAssert(rsGlobalSizes); rsAssert(rsGlobalProperties); } } return new ScriptExecutable( fieldAddress, fieldIsObject, fieldName, varCount, invokeFunctions, funcCount, forEachFunctions, forEachSignatures, forEachCount, reduceDescriptions, reduceCount, pragmaKeys, pragmaValues, pragmaCount, rsGlobalNames, rsGlobalAddresses, rsGlobalSizes, rsGlobalProperties, numEntries, isThreadable, checksum); error: #ifndef RS_COMPATIBILITY_LIB for (size_t idx = 0; idx < pragmaCount; ++idx) { delete [] pragmaKeys[idx]; delete [] pragmaValues[idx]; } delete[] pragmaValues; delete[] pragmaKeys; #endif // RS_COMPATIBILITY_LIB delete[] forEachSignatures; delete[] forEachFunctions; delete[] invokeFunctions; for (size_t i = 0; i < varCount; i++) { delete[] fieldName[i]; } delete[] fieldName; delete[] fieldIsObject; delete[] fieldAddress; return nullptr; } void* ScriptExecutable::getFieldAddress(const char* name) const { // TODO: improve this by using a hash map. for (size_t i = 0; i < mExportedVarCount; i++) { if (strcmp(name, mFieldName[i]) == 0) { return mFieldAddress[i]; } } return nullptr; } bool ScriptExecutable::dumpGlobalInfo() const { ALOGE("Globals: %p %p %p", mGlobalAddresses, mGlobalSizes, mGlobalNames); ALOGE("P - Pointer"); ALOGE(" C - Constant"); ALOGE(" S - Static"); for (int i = 0; i < mGlobalEntries; i++) { ALOGE("Global[%d]: %p %zu %s", i, mGlobalAddresses[i], mGlobalSizes[i], mGlobalNames[i]); uint32_t properties = mGlobalProperties[i]; ALOGE("%c%c%c Type: %u", isGlobalPointer(properties) ? 'P' : ' ', isGlobalConstant(properties) ? 'C' : ' ', isGlobalStatic(properties) ? 'S' : ' ', getGlobalRsType(properties)); } return true; } } // namespace renderscript } // namespace android