diff --git a/cmd/gapir/cc/main.cpp b/cmd/gapir/cc/main.cpp index f4eed1aa14..f8fb2a0307 100644 --- a/cmd/gapir/cc/main.cpp +++ b/cmd/gapir/cc/main.cpp @@ -44,6 +44,8 @@ #if TARGET_OS == GAPID_OS_ANDROID #include #include "android_native_app_glue.h" +#include "gapir/cc/android/asset_replay_service.h" +#include "gapir/cc/android/asset_resource_cache.h" #elif TARGET_OS == GAPID_OS_LINUX || TARGET_OS == GAPID_OS_OSX #include #include @@ -55,6 +57,13 @@ using namespace gapir; namespace { +enum ReplayMode { + kUnknown = 0, // Can't determine replay type from arguments yet. + kConflict, // Impossible combination of command line arguments. + kReplayServer, // Run gapir as a server. + kReplayArchive, // Replay an exported archive. +}; + std::vector memorySizes { // If we are on desktop, we can try more memory #if TARGET_OS != GAPID_OS_ANDROID @@ -264,12 +273,44 @@ std::unique_ptr Setup(const char* uri, const char* authToken, }); } +static int replayArchive(core::CrashHandler* crashHandler, + std::unique_ptr resourceCache, + gapir::ReplayService* replayArchiveService) { + // The directory consists an archive(resources.{index,data}) and payload.bin. + MemoryManager memoryManager(memorySizes); + std::unique_ptr resLoader = + CachedResourceLoader::create(resourceCache.get(), nullptr); + + std::unique_ptr context = Context::create( + replayArchiveService, *crashHandler, resLoader.get(), &memoryManager); + + if (context->initialize("payload")) { + GAPID_DEBUG("Replay context initialized successfully"); + } else { + GAPID_ERROR("Replay context initialization failed"); + return EXIT_FAILURE; + } + + GAPID_INFO("Replay started"); + bool ok = context->interpret(); + replayArchiveService->sendReplayFinished(); + if (!context->cleanup()) { + GAPID_ERROR("Replay cleanup failed"); + return EXIT_FAILURE; + } + GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); + + return ok ? EXIT_SUCCESS : EXIT_FAILURE; +} + } // anonymous namespace #if TARGET_OS == GAPID_OS_ANDROID namespace { +const char* kReplayAssetToDetect = "replay_export/resources.index"; + template jobject jni_call_o(JNIEnv* env, jobject obj, const char* name, const char* sig, Args&&... args) { @@ -287,6 +328,7 @@ int jni_call_i(JNIEnv* env, jobject obj, const char* name, const char* sig, struct Options { int idleTimeoutSec = 0; std::string authToken = ""; + ReplayMode mode = kUnknown; static Options Parse(struct android_app* app) { Options opts; @@ -308,6 +350,20 @@ struct Options { env->ReleaseStringUTFChars((jstring)token, tmp); } + opts.mode = kReplayServer; + + // Select replay archive mode if replay assets are detected + jobject j_asset_manager = + jni_call_o(env, app->activity->clazz, "getAssets", + "()Landroid/content/res/AssetManager;"); + AAssetManager* asset_manager = AAssetManager_fromJava(env, j_asset_manager); + AAsset* asset = AAssetManager_open(asset_manager, kReplayAssetToDetect, + AASSET_MODE_UNKNOWN); + if (asset != nullptr) { + opts.mode = kReplayArchive; + AAsset_close(asset); + } + app->activity->vm->DetachCurrentThread(); return opts; } @@ -341,35 +397,76 @@ void android_process(struct android_app* app, int32_t cmd) { // Main function for android void android_main(struct android_app* app) { - MemoryManager memoryManager(memorySizes); CrashHandler crashHandler; + // Attach main thread to the JVM, needed for sane JNI calls + // JNIEnv* env; + // app->activity->vm->AttachCurrentThread(&env, 0); + + std::thread waiting_thread; + std::atomic thread_is_done(false); + // Get the path of the file system socket. const char* pipe = pipeName(); std::string internal_data_path = std::string(app->activity->internalDataPath); std::string socket_file_path = internal_data_path + "/" + std::string(pipe); std::string uri = std::string("unix://") + socket_file_path; - - GAPID_INFO( - "Started Graphics API Replay daemon.\n" - "Listening on unix socket '%s'\n" - "Supported ABIs: %s\n", - uri.c_str(), core::supportedABIs()); - - auto opts = Options::Parse(app); + std::unique_ptr server = nullptr; + MemoryManager memoryManager(memorySizes); auto cache = InMemoryResourceCache::create(memoryManager.getTopAddress()); std::mutex lock; PrewarmData data; - std::unique_ptr server = - Setup(uri.c_str(), opts.authToken.c_str(), cache.get(), - opts.idleTimeoutSec, &crashHandler, &memoryManager, &data, &lock); - std::atomic serverIsDone(false); - std::thread waiting_thread([&]() { - server.get()->wait(); - serverIsDone = true; - }); - if (chmod(socket_file_path.c_str(), S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)) { - GAPID_ERROR("Chmod failed!"); + + auto opts = Options::Parse(app); + + if (opts.mode == kReplayArchive) { + GAPID_INFO("Started Graphics API Replay from archive."); + + waiting_thread = std::thread([&]() { + // It's important to use a different JNIEnv as it is a separate thread + JNIEnv* env; + app->activity->vm->AttachCurrentThread(&env, 0); + + // Hugues: maybe we need to open assets in the main thread? + jobject j_asset_manager = + jni_call_o(env, app->activity->clazz, "getAssets", + "()Landroid/content/res/AssetManager;"); + AAssetManager* asset_manager = + AAssetManager_fromJava(env, j_asset_manager); + + std::unique_ptr assetResourceCache = + AssetResourceCache::create(asset_manager); + gapir::AssetReplayService assetReplayService(asset_manager); + + const char* postbackDirectory = ""; + replayArchive(&crashHandler, std::move(assetResourceCache), + &assetReplayService); + + app->activity->vm->DetachCurrentThread(); + + thread_is_done = true; + }); + + } else if (opts.mode == kReplayServer) { + GAPID_INFO( + "Started Graphics API Replay daemon.\n" + "Listening on unix socket '%s'\n" + "Supported ABIs: %s\n", + uri.c_str(), core::supportedABIs()); + + server = + Setup(uri.c_str(), opts.authToken.c_str(), cache.get(), + opts.idleTimeoutSec, &crashHandler, &memoryManager, &data, &lock); + waiting_thread = std::thread([&]() { + server.get()->wait(); + thread_is_done = true; + }); + if (chmod(socket_file_path.c_str(), + S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)) { + GAPID_ERROR("Chmod failed!"); + } + } else { + GAPID_ERROR("Invalid replay mode"); } app->onAppCmd = android_process; @@ -389,13 +486,15 @@ void android_main(struct android_app* app) { } if (app->destroyRequested) { // Clean up and exit the main loop - server->shutdown(); + if (opts.mode == kReplayServer) { + server->shutdown(); + } alive = false; break; } } - if (serverIsDone && !finishing) { + if (thread_is_done && !finishing) { // Start termination of the app ANativeActivity_finish(app->activity); @@ -411,7 +510,9 @@ void android_main(struct android_app* app) { // Final clean up waiting_thread.join(); - unlink(socket_file_path.c_str()); + if (opts.mode == kReplayServer) { + unlink(socket_file_path.c_str()); + } GAPID_INFO("End of Graphics API Replay"); return; } @@ -429,13 +530,6 @@ struct Options { int logLevel = LOG_LEVEL; const char* logPath = "logs/gapir.log"; - - enum ReplayMode { - kUnknown = 0, // Can't determine replay type from arguments yet. - kConflict, // Impossible combination of command line arguments. - kReplayServer, // Run gapir as a server. - kReplayArchive, // Replay an exported archive. - }; ReplayMode mode = kUnknown; bool waitForDebugger = false; const char* cachePath = nullptr; @@ -669,47 +763,7 @@ std::unique_ptr createCache( } } // namespace -static int replayArchive(Options opts) { - // The directory consists an archive(resources.{index,data}) and payload.bin. - core::CrashHandler crashHandler; - GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath); - MemoryManager memoryManager(memorySizes); - std::string payloadPath = std::string(opts.replayArchive) + "/payload.bin"; - gapir::ArchiveReplayService replayArchive(payloadPath, - opts.postbackDirectory); - // All the resource data must be in the archive file, no fallback resource - // loader to fetch uncached resources data. - auto onDiskCache = OnDiskResourceCache::create(opts.replayArchive, false); - std::unique_ptr resLoader = - CachedResourceLoader::create(onDiskCache.get(), nullptr); - - std::unique_ptr context = Context::create( - &replayArchive, crashHandler, resLoader.get(), &memoryManager); - - if (context->initialize("payload")) { - GAPID_DEBUG("Replay context initialized successfully"); - } else { - GAPID_ERROR("Replay context initialization failed"); - return EXIT_FAILURE; - } - - GAPID_INFO("Replay started"); - bool ok = context->interpret(); - replayArchive.sendReplayFinished(); - if (!context->cleanup()) { - GAPID_ERROR("Replay cleanup failed"); - return EXIT_FAILURE; - } - GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); - - return ok ? EXIT_SUCCESS : EXIT_FAILURE; -} - -static int startServer(Options opts) { - core::CrashHandler crashHandler; - - GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath); - +static int startServer(core::CrashHandler* crashHandler, Options opts) { // Read the auth-token. // Note: This must come before the socket is created as the auth token // file is deleted by GAPIS as soon as the port is written to stdout. @@ -754,7 +808,7 @@ static int startServer(Options opts) { PrewarmData data; std::unique_ptr server = Setup(uri.c_str(), (authToken.size() > 0) ? authToken.data() : nullptr, - cache.get(), opts.idleTimeoutSec, &crashHandler, &memoryManager, + cache.get(), opts.idleTimeoutSec, crashHandler, &memoryManager, &data, &lock); // The following message is parsed by launchers to detect the selected port. // DO NOT CHANGE! @@ -786,13 +840,25 @@ int main(int argc, const char* argv[]) { } else if (opts.version) { printf("GAPIR version " GAPID_VERSION_AND_BUILD "\n"); return EXIT_SUCCESS; - } else if (opts.mode == Options::kConflict) { + } else if (opts.mode == kConflict) { GAPID_ERROR("Argument conflicts."); return EXIT_FAILURE; - } else if (opts.mode == Options::kReplayArchive) { - return replayArchive(opts); + } + + core::CrashHandler crashHandler; + GAPID_LOGGER_INIT(opts.logLevel, "gapir", opts.logPath); + + if (opts.mode == kReplayArchive) { + std::string payloadPath = std::string(opts.replayArchive) + "/payload.bin"; + gapir::ArchiveReplayService replayArchiveService(payloadPath, + opts.postbackDirectory); + // All the resource data must be in the archive file, no fallback resource + // loader to fetch uncached resources data. + auto onDiskCache = OnDiskResourceCache::create(opts.replayArchive, false); + return replayArchive(&crashHandler, std::move(onDiskCache), + &replayArchiveService); } else { - return startServer(opts); + return startServer(&crashHandler, opts); } } diff --git a/cmd/gapit/BUILD.bazel b/cmd/gapit/BUILD.bazel index 385091cecf..d5ed60e6ca 100644 --- a/cmd/gapit/BUILD.bazel +++ b/cmd/gapit/BUILD.bazel @@ -55,6 +55,7 @@ go_library( "//core/app/auth:go_default_library", "//core/app/crash:go_default_library", "//core/app/flags:go_default_library", + "//core/app/layout:go_default_library", "//core/app/status:go_default_library", "//core/data/endian:go_default_library", "//core/data/id:go_default_library", diff --git a/cmd/gapit/export_replay.go b/cmd/gapit/export_replay.go index 0ad7865222..b9eaca6e11 100644 --- a/cmd/gapit/export_replay.go +++ b/cmd/gapit/export_replay.go @@ -15,21 +15,38 @@ package main import ( + "archive/zip" "context" "flag" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "text/template" "github.com/google/gapid/core/app" + "github.com/google/gapid/core/app/layout" "github.com/google/gapid/core/log" "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/service" - "github.com/google/gapid/gapis/service/path" + gapidPath "github.com/google/gapid/gapis/service/path" ) +// Version must match the ones in gapidapk/android/apk/AndroidManifest.xml.in +const minSdkVersion = 21 +const targetSdkVersion = 26 + type exportReplayVerb struct{ ExportReplayFlags } func init() { verb := &exportReplayVerb{ - ExportReplayFlags{Out: "replay_export"}, + ExportReplayFlags{Out: "replay_export", Apk: "", SdkPath: ""}, } app.AddVerb(&app.Verb{ Name: "export_replay", @@ -44,13 +61,25 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error return nil } + // Early argument check + if verb.Apk != "" { + if !strings.HasSuffix(verb.Apk, ".apk") { + app.Usage(ctx, "APK name must be a valid Android package name followed by '.apk', e.g. com.example.myapp.replay.apk") + return nil + } + if _, err := os.Stat(verb.Apk); err == nil { + app.Usage(ctx, "APK archive file must not exists") + return nil + } + } + client, capturePath, err := getGapisAndLoadCapture(ctx, verb.Gapis, verb.Gapir, flags.Arg(0), verb.CaptureFileFlags) if err != nil { return err } defer client.Close() - var device *path.Device + var device *gapidPath.Device if !verb.OriginalDevice { device, err = getDevice(ctx, client, capturePath, verb.Gapir) if err != nil { @@ -65,7 +94,7 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error return log.Err(ctx, err, "Couldn't get filter") } - requestEvents := path.Events{ + requestEvents := gapidPath.Events{ Capture: capturePath, LastInFrame: true, Filter: filter, @@ -98,5 +127,293 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error if err := client.ExportReplay(ctx, capturePath, device, verb.Out, opts); err != nil { return log.Err(ctx, err, "Failed to export replay") } + + if verb.Apk != "" { + // Create stand-alone APK + replayAPK := filepath.Base(verb.Apk) + replayPackage := strings.TrimSuffix(replayAPK, ".apk") + log.I(ctx, "Create replay apk: %s with package name %s", replayAPK, replayPackage) + + boxedCapture, err := client.Get(ctx, capturePath.Path(), nil) + if err != nil { + return log.Err(ctx, err, "Failed to load the capture") + } + capture := boxedCapture.(*service.Capture) + + // Save current directory + startdir, err := os.Getwd() + if err != nil { + return err + } + + // Operate in a temporary directory + tmpdir, err := ioutil.TempDir("", "gapid") + if err != nil { + return err + } + defer os.RemoveAll(tmpdir) // clean up + + if err := os.Chdir(tmpdir); err != nil { + return err + } + defer os.Chdir(startdir) + + // Retrieve replay files + if err := os.Mkdir("assets", os.ModePerm); err != nil { + return err + } + assetsPath := path.Join("assets", "replay_export") + if err := os.Rename(path.Join(startdir, verb.Out), assetsPath); err != nil { + return err + } + + // Extract reference APK + refAPKPath, err := layout.GapidApk(ctx, capture.ABI) + if err != nil { + return err + } + if err := unzip(ctx, refAPKPath.String(), tmpdir); err != nil { + return err + } + + // Create new manifest + f, err := os.Create("AndroidManifest.xml") + if err != nil { + return err + } + t, err := template.New("manifest").Parse(manifest()) + if err != nil { + return err + } + type ManifestEntries struct { + Package string + MinSdkVersion int + TargetSdkVersion int + } + var entries = ManifestEntries{replayPackage, minSdkVersion, targetSdkVersion} + t.Execute(f, entries) + f.Close() + + // Android tools + + // find latest build tools + sdkPath := verb.SdkPath + if sdkPath == "" { + sdkPath = os.ExpandEnv("${ANDROID_SDK_HOME}") + } + if _, err := os.Stat(sdkPath); err != nil { + return err + } + toolsPathParent := path.Join(sdkPath, "build-tools") + matches, err := filepath.Glob(path.Join(toolsPathParent, "*")) + if err != nil { + return err + } + if len(matches) <= 0 { + return fmt.Errorf("Cannot find any directory under " + toolsPathParent) + } + sort.Strings(matches) + toolsPath := matches[len(matches)-1] + + aapt := path.Join(toolsPath, "aapt") + zipalign := path.Join(toolsPath, "zipalign") + apksigner := path.Join(toolsPath, "apksigner") + + tmpAPK := "tmp.apk" + + // Re-assemble new APK with the new manifest + baseJar := path.Join(sdkPath, "platforms", "android-"+strconv.Itoa(targetSdkVersion), "android.jar") + if _, err := os.Stat(verb.Apk); err == nil { + return fmt.Errorf("Cannot find android platform %d, please install it.", targetSdkVersion) + } + + if err := execCommand(ctx, aapt, "package", "-f", "-M", "AndroidManifest.xml", "-I", baseJar, "-F", tmpAPK); err != nil { + return err + } + + // Add replay assets, uncompressed + assets, err := ioutil.ReadDir(assetsPath) + if err != nil { + return err + } + for _, a := range assets { + // Arguments ("-0", "") ensure the asset will not be compressed + if err := execCommand(ctx, aapt, "add", "-0", "", tmpAPK, path.Join(assetsPath, a.Name())); err != nil { + return err + } + } + + // Add the replay libraries + // TODO(#2677): get rid of this workaround once ABI naming is sanitized + abi := capture.ABI.Name + if abi == "armeabi" { + abi = "armeabi-v7a" + } + + files := []string{ + "classes.dex", + path.Join("lib", abi, "libgapir.so"), + path.Join("lib", abi, "libVkLayer_VirtualSwapchain.so"), + } + + for _, f := range files { + if err := execCommand(ctx, aapt, "add", "-0", "", tmpAPK, f); err != nil { + return err + } + } + + // Zip-align, output in final APK file + replayAPKPath := path.Join(startdir, replayAPK) + if err := execCommand(ctx, zipalign, "4", tmpAPK, replayAPKPath); err != nil { + return err + } + + // Sign the new APK + keystorePath := path.Join(os.ExpandEnv("${HOME}"), ".android", "debug.keystore") + if _, err := os.Stat(keystorePath); err != nil { + // No keystore found, create one + keystorePath = "debug.keystore" + // https://developer.android.com/studio/publish/app-signing#debug-mode + keytool := "keytool" + if _, err := exec.LookPath("keytool"); err != nil { + // keytool is not found in PATH, look in JAVA_HOME/bin + keytool = path.Join(os.ExpandEnv("JAVA_HOME"), "bin") + if _, err := os.Stat(keytool); err != nil { + return fmt.Errorf("Cannot find the 'keytool' command") + } + } + if err := execCommand(ctx, keytool, "-genkey", "-dname", "CN=Android Debug,O=Android,C=US", "-v", "-keystore", keystorePath, "-storepass", "android", "-alias", "androiddebugkey", "-keypass", "android", "-keyalg", "RSA", "-keysize", "2048", "-validity", "10000"); err != nil { + return err + } + } + if err := execCommand(ctx, apksigner, "sign", "--ks", keystorePath, "--ks-pass", "pass:android", replayAPKPath); err != nil { + return err + } + + } + return nil } + +func execCommand(ctx context.Context, name string, args ...string) error { + cmd := exec.Command(name, args...) + log.I(ctx, "Executing %v", cmd.Args) + out, err := cmd.CombinedOutput() + logger := log.From(ctx).Writer(log.Debug) + if err != nil { + logger = log.From(ctx).Writer(log.Error) + } + logger.Write(out) + return err +} + +func unzip(ctx context.Context, archiveFilename string, outputDir string) error { + log.I(ctx, "Unzip %s to %s", archiveFilename, outputDir) + + z, err := zip.OpenReader(archiveFilename) + if err != nil { + return err + } + defer z.Close() + + for _, f := range z.File { + destPath := filepath.Join(outputDir, f.Name) + + // Protect from ZipSlip, see https://snyk.io/research/zip-slip-vulnerability + prefix := "" + if filepath.Clean(outputDir) != "." { + prefix = filepath.Clean(outputDir) + string(os.PathSeparator) + } + if !strings.HasPrefix(destPath, prefix) { + return fmt.Errorf("ZipSlip: illegal file path: %s", destPath) + } + + if f.FileInfo().IsDir() { + os.MkdirAll(destPath, os.ModePerm) + continue + } else { + parentDir := filepath.Dir(destPath) + os.MkdirAll(parentDir, os.ModePerm) + } + + destFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + fContent, err := f.Open() + if err != nil { + destFile.Close() + return err + } + + _, err = io.Copy(destFile, fContent) + destFile.Close() + fContent.Close() + if err != nil { + return err + } + + } + return nil +} + +func manifest() string { + return ` + + + + + + + + + + + + + + + + + + + + + + + + +` +} diff --git a/cmd/gapit/flags.go b/cmd/gapit/flags.go index 17d9feeb5e..1bd6270b21 100644 --- a/cmd/gapit/flags.go +++ b/cmd/gapit/flags.go @@ -124,6 +124,8 @@ type ( OriginalDevice bool `help:"export replay for the original device"` Out string `help:"output directory for commands and assets"` OutputFrames bool `help:"generate trace that output frames(disable diagnostics)"` + Apk string `help:"(experimental) create a stand-alone APK with that name which performs the replay"` + SdkPath string `help:"Path to Android SDK directory (default: ANDROID_SDK_HOME environment variable)"` CommandFilterFlags CaptureFileFlags } @@ -355,4 +357,11 @@ type ( SmokeTestsFlags struct { } + + Trace2apkFlags struct { + Gapis GapisFlags + Gapir GapirFlags + CommandFilterFlags + CaptureFileFlags + } ) diff --git a/core/cc/archive.cpp b/core/cc/archive.cpp index 092f16f3e0..dda84935bb 100644 --- a/core/cc/archive.cpp +++ b/core/cc/archive.cpp @@ -210,6 +210,10 @@ Archive::Archive(const std::string& archiveName) GAPID_FATAL("Unable to open archive index file %s", indexFilename.c_str()); } + // Linux fopen() with mode "a" leads to reads from the beginning of file, but + // this is not true on Android, hence the explicit rewind() here + rewind(mIndexFile); + // Load the archive index in memory. for (;;) { uint32_t idSize; diff --git a/gapir/cc/BUILD.bazel b/gapir/cc/BUILD.bazel index 177bba497e..dc039f6593 100644 --- a/gapir/cc/BUILD.bazel +++ b/gapir/cc/BUILD.bazel @@ -82,7 +82,10 @@ cc_library( "//tools/build:darwin": glob(["osx/*.cpp"]), "//tools/build:windows": glob(["windows/*.cpp"]), # Android - "//conditions:default": glob(["android/*.cpp"]), + "//conditions:default": glob([ + "android/*.cpp", + "android/*.h", + ]), }) + [ ":gles_cc", ":gles_h", diff --git a/gapir/cc/android/asset_replay_service.cpp b/gapir/cc/android/asset_replay_service.cpp new file mode 100644 index 0000000000..959f438a81 --- /dev/null +++ b/gapir/cc/android/asset_replay_service.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "asset_replay_service.h" +#include "core/cc/log.h" + +#include +#include + +namespace { + +const char* kAssetPathPayloadBin = "replay_export/payload.bin"; + +} // namespace + +namespace gapir { + +std::unique_ptr AssetReplayService::getPayload( + const std::string& id) { + AAsset* asset_payload = AAssetManager_open( + mAssetManager, kAssetPathPayloadBin, AASSET_MODE_STREAMING); + + off64_t offset; + off64_t length; + int payload_fd = AAsset_openFileDescriptor64(asset_payload, &offset, &length); + if (payload_fd < 0) { + GAPID_FATAL( + "AssetReplayService::getPayload() cannot open payload asset as a " + "file descriptor (because the asset was stored compressed?)"); + } + AAsset_close(asset_payload); + + off64_t ret = lseek64(payload_fd, offset, SEEK_SET); + if (ret == (off64_t)-1) { + GAPID_FATAL("AssetReplayService::getPayload() lseek64 failed"); + } + + std::unique_ptr payload(new replay_service::Payload); + payload->ParseFromFileDescriptor(payload_fd); + close(payload_fd); + return std::unique_ptr(new Payload(std::move(payload))); +} + +} // namespace gapir diff --git a/gapir/cc/android/asset_replay_service.h b/gapir/cc/android/asset_replay_service.h new file mode 100644 index 0000000000..e54bba7a1d --- /dev/null +++ b/gapir/cc/android/asset_replay_service.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GAPIR_ASSET_REPLAY_SERVICE_H +#define GAPIR_ASSET_REPLAY_SERVICE_H + +#include "asset_replay_service.h" +#include "gapir/cc/replay_service.h" +#include "gapir/cc/resource.h" + +#include "core/cc/archive.h" +#include "core/cc/log.h" + +#include +#include + +#include + +namespace gapir { + +// AssetReplayService implements ReplayService interface for exported replays on +// Android. It accesses replay payload data via Android assets. +class AssetReplayService : public ReplayService { + public: + AssetReplayService(AAssetManager* assetManager) + : mAssetManager(assetManager) {} + + // Read payload from Android assets. + std::unique_ptr getPayload(const std::string& _payload) override; + + // We are reading from Android assets, so the following methods are not + // implemented. + std::unique_ptr getResources(const Resource* resources, + size_t resCount) override { + return nullptr; + } + + bool sendReplayFinished() override { return true; } + + bool sendCrashDump(const std::string& filepath, const void* crash_data, + uint32_t crash_size) override { + return true; + } + + bool sendPosts(std::unique_ptr posts) override { return true; } + + bool sendNotification(uint64_t id, uint32_t severity, uint32_t api_index, + uint64_t label, const std::string& msg, + const void* data, uint32_t data_size) override { + return true; + } + + std::unique_ptr getReplayRequest() override { + return std::unique_ptr( + new replay_service::ReplayRequest()); + } + + private: + AAssetManager* mAssetManager; +}; + +} // namespace gapir + +#endif // GAPIR_ASSET_REPLAY_SERVICE_H diff --git a/gapir/cc/android/asset_resource_cache.cpp b/gapir/cc/android/asset_resource_cache.cpp new file mode 100644 index 0000000000..6bd6a36100 --- /dev/null +++ b/gapir/cc/android/asset_resource_cache.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "asset_resource_cache.h" +#include "gapir/cc/replay_service.h" + +#include "core/cc/log.h" + +#include +#include +#include +#include + +namespace gapir { + +namespace { + +const char *kAssetPathResourcesIndex = "replay_export/resources.index"; +const char *kAssetPathResourcesData = "replay_export/resources.data"; + +// asset_read bails out if the read fails. Otherwise, it returns true unless EOF +// is reached. +bool asset_read(AAsset *asset, void *buf, size_t count) { + int ret = AAsset_read(asset, buf, count); + if (ret < 0) { + GAPID_FATAL("Error on asset read"); + } + if (ret == 0) { + return false; + } + if (ret != count) { + GAPID_FATAL("Asset read only %d bytes out of %zu bytes required", ret, + count); + } + return true; +} + +} // namespace + +std::unique_ptr AssetResourceCache::create( + AAssetManager *assetManager) { + return std::unique_ptr(new AssetResourceCache(assetManager)); +} + +AssetResourceCache::AssetResourceCache(AAssetManager *assetManager) { + mAssetManager = assetManager; + AAsset *asset_resource_index = AAssetManager_open( + mAssetManager, kAssetPathResourcesIndex, AASSET_MODE_STREAMING); + + // Load the archive index in memory. + for (;;) { + uint32_t idSize; + if (!asset_read(asset_resource_index, &idSize, sizeof(idSize))) break; + std::string id(idSize, 0); + uint64_t offset; + uint32_t size; + if (!asset_read(asset_resource_index, &id.front(), idSize) || + !asset_read(asset_resource_index, &offset, sizeof(offset)) || + !asset_read(asset_resource_index, &size, sizeof(size))) { + break; + } + mRecords.emplace(id, AssetRecord{offset, size}); + } + + AAsset_close(asset_resource_index); + + // Open the resource data file descriptor + AAsset *asset_resource_data = AAssetManager_open( + mAssetManager, kAssetPathResourcesData, AASSET_MODE_STREAMING); + off64_t length; + mResourceDataFd = AAsset_openFileDescriptor64(asset_resource_data, + &mResourceDataStart, &length); + if (mResourceDataFd < 0) { + GAPID_FATAL( + "AssetResourceCache::AssetResourceCache() cannot open resource " + "data asset as a file descriptor (due to compressed asset?)"); + } + AAsset_close(asset_resource_data); +} + +AssetResourceCache::~AssetResourceCache() { + if (mResourceDataFd >= 0) { + close(mResourceDataFd); + } +} + +bool AssetResourceCache::putCache(const Resource &resource, const void *data) { + // AssetResourceCache is read-only, putCache always fails. + return false; +} + +bool AssetResourceCache::hasCache(const Resource &resource) { + return (mRecords.find(resource.id) != mRecords.end()); +} + +bool AssetResourceCache::loadCache(const Resource &resource, void *data) { + std::unordered_map::const_iterator it; + it = mRecords.find(resource.id); + if (it == mRecords.end()) { + GAPID_FATAL("AssetResourceCache::loadCache() cannot find resource: %s", + resource.id.c_str()); + } + + AssetRecord record = it->second; + + off64_t offset = mResourceDataStart + record.offset; + off64_t ret = lseek64(mResourceDataFd, offset, SEEK_SET); + if (ret == (off64_t)-1) { + GAPID_FATAL("AssetResourceCache::loadCache() lseek64() failed"); + } + + size_t left_to_read = record.size; + char *p = (char *)data; + + while (left_to_read > 0) { + ssize_t read_this_time = read(mResourceDataFd, p, left_to_read); + + if (read_this_time == (ssize_t)-1) { + char *errmsg = strerror(errno); + GAPID_FATAL( + "AssetResourceCache::loadCache() read() failed, errno: %d, strerror: " + "%s", + errno, errmsg); + } + if (read_this_time > left_to_read) { + GAPID_FATAL( + "AssetResourceCache::loadCache() read() returned" + "more (%zu) than what is was asked for (%zu)", + read_this_time, left_to_read); + } + left_to_read -= (size_t)read_this_time; + p += read_this_time; + } + + return true; +} + +} // namespace gapir diff --git a/gapir/cc/android/asset_resource_cache.h b/gapir/cc/android/asset_resource_cache.h new file mode 100644 index 0000000000..92c15e419b --- /dev/null +++ b/gapir/cc/android/asset_resource_cache.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GAPIR_ASSET_RESOURCE_CACHE_H +#define GAPIR_ASSET_RESOURCE_CACHE_H + +#include + +#include "gapir/cc/resource_cache.h" +#include "gapir/cc/resource_loader.h" + +#include + +namespace gapir { + +// AssetResourceCache is a read-only cache based on Android Assets +class AssetResourceCache : public ResourceCache { + public: + // Creates new asset cache. + static std::unique_ptr create(AAssetManager* assetManager); + + ~AssetResourceCache(); + + // ResourceCache interface implementation + virtual bool putCache(const Resource& res, const void* resData) override; + virtual bool hasCache(const Resource& res) override; + virtual bool loadCache(const Resource& res, void* target) override; + + // Unlimited size for on-disk cache. + virtual size_t totalCacheSize() const override { + return std::numeric_limits::max(); + } + // Do not support resize. + virtual bool resize(size_t newSize) override { return true; }; + + private: + AssetResourceCache(AAssetManager* assetManager); + + struct AssetRecord { + uint64_t offset; + uint32_t size; + }; + std::unordered_map mRecords; + AAssetManager* mAssetManager; + // File descriptor data to access resources + int mResourceDataFd; + off64_t mResourceDataStart; +}; + +} // namespace gapir + +#endif // GAPIR_ASSET_RESOURCE_CACHE_H diff --git a/gapir/cc/on_disk_resource_cache.cpp b/gapir/cc/on_disk_resource_cache.cpp index 084faa753b..5317d9e822 100644 --- a/gapir/cc/on_disk_resource_cache.cpp +++ b/gapir/cc/on_disk_resource_cache.cpp @@ -60,7 +60,7 @@ int mkdirAll(const std::string& path) { } // anonymous namespace -std::unique_ptr OnDiskResourceCache::create( +std::unique_ptr OnDiskResourceCache::create( const std::string& path, bool cleanUp) { if (0 != mkdirAll(path)) { GAPID_WARNING( @@ -72,7 +72,7 @@ std::unique_ptr OnDiskResourceCache::create( diskPath.push_back(PATH_DELIMITER); } - return std::unique_ptr( + return std::unique_ptr( new OnDiskResourceCache(std::move(diskPath), cleanUp)); } } diff --git a/gapir/cc/on_disk_resource_cache.h b/gapir/cc/on_disk_resource_cache.h index f666a22fcf..05c9b47859 100644 --- a/gapir/cc/on_disk_resource_cache.h +++ b/gapir/cc/on_disk_resource_cache.h @@ -38,8 +38,8 @@ class OnDiskResourceCache : public ResourceCache { public: // Creates new disk cache with the specified base path. If the base path is // not readable or it can't be created then returns the fall back provider. - static std::unique_ptr create(const std::string& path, - bool cleanUp); + static std::unique_ptr create(const std::string& path, + bool cleanUp); virtual ~OnDiskResourceCache() { #if TARGET_OS == GAPID_OS_LINUX || TARGET_OS == GAPID_OS_OSX diff --git a/gapis/api/gles/api/egl.api b/gapis/api/gles/api/egl.api index 26063d1024..9a272a3d15 100644 --- a/gapis/api/gles/api/egl.api +++ b/gapis/api/gles/api/egl.api @@ -357,14 +357,12 @@ cmd EGLBoolean eglSurfaceAttrib(EGLDisplay display, return ? // TODO } -@no_replay @frame_start ///http://www.khronos.org/registry/egl/sdk/docs/man/html/eglSwapBuffers.xhtml cmd EGLBoolean eglSwapBuffers(EGLDisplay display, void* surface) { return ? } -@no_replay @frame_start ///https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt cmd EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy,