C++程序  |  754行  |  23.4 KB

#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