Bug 1522318 - Wait for Java debugger when MOZ_DEBUG_{CHILD_}WAIT_FOR_JAVA_DEBUGGER. r=esawin
authorNick Alexander <nalexander@mozilla.com>
Wed, 03 Apr 2019 16:51:09 +0000
changeset 467801 1afc68022e2b67382069f3f49a39061f23f2806f
parent 467800 1208afc3481a0b062f957f688f43c103a198069f
child 467802 d76eea893d560e8643e59bb451be193a65eaadd4
push id82217
push usernalexander@mozilla.com
push dateWed, 03 Apr 2019 18:51:46 +0000
treeherderautoland@1afc68022e2b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin
bugs1522318
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522318 - Wait for Java debugger when MOZ_DEBUG_{CHILD_}WAIT_FOR_JAVA_DEBUGGER. r=esawin Set `MOZ_DEBUG_WAIT_FOR_JAVA_DEBUGGER=1` in the environment to make the main (Gecko) process wait for a Java debugger to connect. This is a superset of Android Studio's built-in debugging support so it won't be particularly useful, but perhaps some folks want to use a different jdwp debugger. Set `MOZ_DEBUG_CHILD_WAIT_FOR_JAVA_DEBUGGER=suffix` in the environment to make child processes wait for a Java debugger to connect. This is not easy in Android Studio. The value ":tab" will make any child process with a process name with suffix ":tab" wait. N.b., the empty string "" is a suffix of all process names and thus `MOZ_DEBUG_CHILD_WAIT_FOR_JAVA_DEBUGGER=` makes all child processes wait. Differential Revision: https://phabricator.services.mozilla.com/D25299
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -8,25 +8,29 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.process.GeckoProcessManager;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.geckoview.BuildConfig;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
+import android.os.Process;
 import android.os.SystemClock;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -170,17 +174,17 @@ public class GeckoThread extends Thread 
     }
 
     public static boolean init(final InitInfo info) {
         return INSTANCE.initInternal(info);
     }
 
     private synchronized boolean initInternal(final InitInfo info) {
         ThreadUtils.assertOnUiThread();
-        uiThreadId = android.os.Process.myTid();
+        uiThreadId = Process.myTid();
 
         if (mInitialized) {
             return false;
         }
 
         sInitTimer = new TelemetryUtils.UptimeTimer("GV_STARTUP_RUNTIME_MS");
 
         mInitInfo = info;
@@ -426,16 +430,21 @@ public class GeckoThread extends Thread 
         // In Gecko, the native crash reporter is enabled by default in opt builds, and
         // disabled by default in debug builds.
         if ((mInitInfo.flags & FLAG_ENABLE_NATIVE_CRASHREPORTER) == 0 && !BuildConfig.DEBUG_BUILD) {
             env.add(0, "MOZ_CRASHREPORTER_DISABLE=1");
         } else if ((mInitInfo.flags & FLAG_ENABLE_NATIVE_CRASHREPORTER) != 0 && BuildConfig.DEBUG_BUILD) {
             env.add(0, "MOZ_CRASHREPORTER=1");
         }
 
+        // Very early -- before we load mozglue -- wait for Java debuggers.  This allows to connect
+        // a dual/hybrid debugger as well, allowing to debug child processes -- including the
+        // mozglue loading process.
+        maybeWaitForJavaDebugger(context, env);
+
         GeckoLoader.loadMozGlue(context);
         setState(State.MOZGLUE_READY);
 
         GeckoLoader.setupGeckoEnvironment(context, context.getFilesDir().getPath(), env, mInitInfo.prefs);
 
         initGeckoEnvironment();
 
         if ((mInitInfo.flags & FLAG_PRELOAD_CHILD) != 0) {
@@ -478,16 +487,77 @@ public class GeckoThread extends Thread 
         final GeckoBundle data = new GeckoBundle(1);
         data.putBoolean("restart", restarting);
         EventDispatcher.getInstance().dispatch("Gecko:Exited", data);
 
         // Remove pumpMessageLoop() idle handler
         Looper.myQueue().removeIdleHandler(idleHandler);
     }
 
+    private static void maybeWaitForJavaDebugger(final @NonNull Context context, final @NonNull List<String> env) {
+        for (final String e : env) {
+            if (e == null) {
+                continue;
+            }
+
+            if (e.equals("MOZ_DEBUG_WAIT_FOR_JAVA_DEBUGGER=1")) {
+                if (!isChildProcess()) {
+                    final String processName = getProcessName(context);
+                    waitForJavaDebugger(processName);
+                }
+            }
+
+            if (e.startsWith("MOZ_DEBUG_CHILD_WAIT_FOR_JAVA_DEBUGGER=")) {
+                String filter = e.substring("MOZ_DEBUG_CHILD_WAIT_FOR_JAVA_DEBUGGER=".length());
+                if (isChildProcess()) {
+                    final String processName = getProcessName(context);
+                    if (processName == null || processName.endsWith(filter)) {
+                        waitForJavaDebugger(processName);
+                    }
+                }
+            }
+        }
+    }
+
+    private static @Nullable String getProcessName(final @NonNull Context context) {
+        final int pid = Process.myPid();
+        final ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        // This can be quite slow, and it can return null.
+        List<ActivityManager.RunningAppProcessInfo> processInfos = manager.getRunningAppProcesses();
+
+        if (processInfos == null) {
+            return null;
+        }
+
+        for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
+            if (processInfo.pid == pid) {
+                return processInfo.processName;
+            }
+        }
+
+        return null;
+    }
+
+    private static void waitForJavaDebugger(final @Nullable String processName) {
+        final int pid = Process.myPid();
+        final String processIdentification = (isChildProcess() ? "Child process " : "Main process ") +
+                (processName != null ? processName : "<unknown>") +
+                " (" + pid + ")";
+
+        if (Debug.isDebuggerConnected()) {
+            Log.i(LOGTAG, processIdentification + ": Waiting for Java debugger ... " + " already connected");
+            return;
+        }
+
+        Log.w(LOGTAG, processIdentification + ": Waiting for Java debugger ...");
+        Debug.waitForDebugger();
+        Log.w(LOGTAG, processIdentification + ": Waiting for Java debugger ... connected");
+    }
+
     @WrapForJNI(calledFrom = "gecko")
     private static boolean pumpMessageLoop(final Message msg) {
         final Handler geckoHandler = ThreadUtils.sGeckoHandler;
 
         if (msg.obj == geckoHandler && msg.getTarget() == geckoHandler) {
             // Our "queue is empty" message; see runGecko()
             return false;
         }