diff --git a/android-plugin/app/build.gradle.kts b/android-plugin/app/build.gradle.kts index aeb6a43d..091dac45 100644 --- a/android-plugin/app/build.gradle.kts +++ b/android-plugin/app/build.gradle.kts @@ -189,6 +189,15 @@ android { // Debuggable dev APK: adb (tcp_device_server.py, the CI TCP lane) starts the // server components directly by explicit component name. manifestPlaceholders["serverComponentsExported"] = "true" + // P11 B1 (gate tooling): -Pmobilegl.packageRetraceExe=ON packages the adb-shell retrace CLI + // (tools/trace_replay) as nativeLibraryDir/libMobileGLRetrace.so, so an app - the external + // client of the B1 device gate - can exec it (targetSdk 34 forbids exec anywhere else). + val packageRetraceExe = (findProperty("mobilegl.packageRetraceExe") ?: "OFF").toString() + externalNativeBuild { + cmake { + arguments += "-DMOBILEGL_PACKAGE_RETRACE_EXE=$packageRetraceExe" + } + } } } diff --git a/android-plugin/app/src/trace/cpp/CMakeLists.txt b/android-plugin/app/src/trace/cpp/CMakeLists.txt index cdf595e3..8e5fc468 100644 --- a/android-plugin/app/src/trace/cpp/CMakeLists.txt +++ b/android-plugin/app/src/trace/cpp/CMakeLists.txt @@ -255,3 +255,50 @@ target_link_libraries(trace_replay_runner android log dl) + +# --------------------------------------------------------------------------- +# P11 B1 (gate tooling, opt-in): the adb-shell retrace CLI (tools/trace_replay's +# mobilegl_trace_replay) packaged into the trace APK as a PIE executable named like +# a library, so an app - the external client of the B1 device gate, launched through +# the app_process helper - can exec it from nativeLibraryDir (targetSdk 34 forbids +# exec anywhere else). The shape of MobileGLServer's delivery (root CMakeLists): +# lib-prefixed .so name, $ORIGIN rpath, runtime output in the AGP jniLibs dir. It +# dlopens libMobileGL.so at run time (--mobilegl-library) and links none of it. +# Off unless -DMOBILEGL_PACKAGE_RETRACE_EXE=ON (-Pmobilegl.packageRetraceExe=ON). +# --------------------------------------------------------------------------- +if(MOBILEGL_PACKAGE_RETRACE_EXE) + set(MOBILEGL_TRACE_TOOLS "${CMAKE_CURRENT_LIST_DIR}/../../../../../tools/trace_replay") + add_executable(MobileGLRetrace + trace_replay_core.cpp + "${MOBILEGL_TRACE_TOOLS}/trace_replay_cli.cpp" + "${MOBILEGL_TRACE_TOOLS}/apitrace_fbo_dump.cpp") + target_compile_features(MobileGLRetrace PRIVATE cxx_std_17) + target_compile_definitions(MobileGLRetrace PRIVATE + MOBILEGL_APITRACE_RETRACE_MAIN=mobilegl_apitrace_main) + target_include_directories(MobileGLRetrace PRIVATE + "${CMAKE_CURRENT_LIST_DIR}" + "${APITRACE_ROOT}/lib/image" + "${MOBILEGL_TRACE_TOOLS}") + target_link_libraries(MobileGLRetrace + "-Wl,--start-group" + glretrace_common + retrace_common + image + common + glhelpers + glproc + "-Wl,--end-group" + android + log + dl) + set_target_properties(MobileGLRetrace PROPERTIES + PREFIX "lib" SUFFIX ".so" OUTPUT_NAME "MobileGLRetrace" + POSITION_INDEPENDENT_CODE ON + BUILD_RPATH "$ORIGIN" INSTALL_RPATH "$ORIGIN" BUILD_WITH_INSTALL_RPATH TRUE) + target_compile_options(MobileGLRetrace PRIVATE -fPIE) + target_link_options(MobileGLRetrace PRIVATE -pie) + if(CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set_target_properties(MobileGLRetrace PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + endif() +endif() diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java index 72c14176..301dd983 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java @@ -66,6 +66,16 @@ public final class TraceReplayActivity extends Activity { return; } + // P11 B1 (gate tooling): run an argv as a CHILD OF THIS APP - this uid, this untrusted_app + // domain - and stay in front while it runs. The B1 device gate's route (b): a GL client in + // another app than the server, launched through the app_process helper, with its app in the + // foreground and the server app behind it on its foreground service. + String[] execArgv = intent.getStringArrayExtra(EXTRA_EXEC_ARGV); + if (execArgv != null) { + runExternalClient(execArgv, intent.getStringExtra(EXTRA_EXEC_ENV), intent.getStringExtra(EXTRA_EXEC_LOG)); + return; + } + @SuppressWarnings("unchecked") TraceReplaySession retained = (TraceReplaySession) getLastNonConfigurationInstance(); @@ -256,6 +266,58 @@ public final class TraceReplayActivity extends Activity { private static native String nativeRunSpawnSpike(String serverPath, String markerPath); + // --------------------------------------------------------------------------- + // P11 B1 (gate tooling): an external GL client started from THIS app's process. + // + // --esa mobilegl_exec_argv the child's argv (typically /system/bin/app_process / + // top.mobilegl.plugin.ExternalClientHelper -- ...) + // --es mobilegl_exec_env "K=V;K" added to (K=V) / removed from (bare K) its environment - + // CLASSPATH and MOBILEGL_IPC_TOKEN travel here, not in argv + // --es mobilegl_exec_log its stdout+stderr, then one "exit=" line + // + // Runtime.exec forks the child in this app's uid and SELinux domain (no transition), which is + // what the route stands for. Unlike the P11 hspike launch this Activity STAYS IN FRONT until the + // child exits: a backgrounded app is confined to the little cores and frozen by HyperOS, which is + // not the topology a user runs a GL program in. + // --------------------------------------------------------------------------- + private static final String EXTRA_EXEC_ARGV = "mobilegl_exec_argv"; + private static final String EXTRA_EXEC_ENV = "mobilegl_exec_env"; + private static final String EXTRA_EXEC_LOG = "mobilegl_exec_log"; + + private void runExternalClient(String[] argv, String envOverrides, String logPath) { + statusView.setText("External client running\n" + String.join(" ", argv)); + new Thread(() -> { + String outcome; + try { + ProcessBuilder builder = new ProcessBuilder(argv); + builder.redirectErrorStream(true); + if (logPath != null) { + File log = new File(logPath); + File parent = log.getParentFile(); + if (parent != null) parent.mkdirs(); + builder.redirectOutput(ProcessBuilder.Redirect.appendTo(log)); + } + top.mobilegl.plugin.ServerEnvironment.apply(builder.environment(), envOverrides); + Log.i(TAG, "external client: exec " + java.util.Arrays.toString(argv)); + Process child = builder.start(); + int code = child.waitFor(); + outcome = "exit=" + code; + } catch (Exception error) { + outcome = "exit=launch-failed " + error; + Log.e(TAG, "external client launch failed", error); + } + Log.i(TAG, "external client: " + outcome); + if (logPath != null) { + try (java.io.FileOutputStream out = new java.io.FileOutputStream(logPath, true)) { + out.write((outcome + "\n").getBytes(java.nio.charset.StandardCharsets.UTF_8)); + } catch (Exception ignored) { + } + } + final String shown = outcome; + runOnUiThread(() -> statusView.setText("External client finished: " + shown)); + }, "MobileGLExternalClient").start(); + } + private static final class TraceReplayRequest { final String tracePath; final String goldenPath;