[Tool] (P11 B1): trace flavour gate tooling - package the retrace CLI as libMobileGLRetrace.so (packageRetraceExe) and run an external client as a child of the app kept in front (mobilegl_exec_argv)

This commit is contained in:
2026-09-29 14:47:31 -04:00
parent bd05e061fc
commit 952a1ca4a7
3 changed files with 118 additions and 0 deletions
+9
View File
@@ -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"
}
}
}
}
@@ -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()
@@ -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<TraceReplayResult> retained =
(TraceReplaySession<TraceReplayResult>) 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 <argv...> the child's argv (typically /system/bin/app_process /
// top.mobilegl.plugin.ExternalClientHelper -- <program> ...)
// --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 <file> its stdout+stderr, then one "exit=<code>" 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;