//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public int LaunchSuspended (string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { // // Normally, VS launches a program using the IDebugPortEx2::LaunchSuspended method, and the attaches the debugger to the suspended program. // However, there are circumstances in which the DebugEngine may need to launch a program or other dependencies (e.g. tools or interpreters) in which case this method is used. // IDebugEngineLaunch2::ResumeProcess method is called to start the process after the program has been launched in a suspended state. // LoggingUtils.PrintFunction (); try { if (port == null) { throw new ArgumentNullException ("port"); } if (string.IsNullOrEmpty (exe)) { throw new ArgumentNullException ("exe"); } if (!File.Exists (exe)) { throw new FileNotFoundException ("Failed to find target application: " + exe); } m_sdmCallback = new DebugEngineCallback (this, ad7Callback); DebuggeePort debuggeePort = port as DebuggeePort; DebuggeeProcess debugProcess = null; // // Evaluate options; including current debugger target application. // if (m_launchConfiguration == null) { throw new InvalidOperationException ("No launch configuration found."); } m_launchConfiguration.FromString (options); string packageName = m_launchConfiguration ["PackageName"]; string launchActivity = m_launchConfiguration ["LaunchActivity"]; bool debugMode = m_launchConfiguration ["DebugMode"].Equals ("true"); bool openGlTrace = m_launchConfiguration ["OpenGlTrace"].Equals ("true"); bool appIsRunning = false; // // Cache any LaunchSuspended specific parameters. // m_launchConfiguration ["LaunchSuspendedExe"] = exe; m_launchConfiguration ["LaunchSuspendedDir"] = dir; m_launchConfiguration ["LaunchSuspendedEnv"] = env; // // Prevent blocking the main VS thread when launching a suspended application. // Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.ShowDialog, string.Empty), null, null); ManualResetEvent launchSuspendedMutex = new ManualResetEvent (false); Thread asyncLaunchSuspendedThread = new Thread (delegate () { try { // // Launch application on device in a 'suspended' state. // Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("Starting '{0}'...", packageName)), null, null); if (!appIsRunning) { StringBuilder launchArgumentsBuilder = new StringBuilder (); launchArgumentsBuilder.Append ("start "); if (debugMode) { launchArgumentsBuilder.Append ("-D "); // debug } else { launchArgumentsBuilder.Append ("-W "); // wait } launchArgumentsBuilder.Append ("-S "); // force stop the target app before starting the activity if (openGlTrace) { launchArgumentsBuilder.Append ("--opengl-trace "); } launchArgumentsBuilder.Append (packageName + "/" + launchActivity); Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("[adb:shell:am] {0}", launchArgumentsBuilder)), null, null); string launchResponse = debuggeePort.PortDevice.Shell ("am", launchArgumentsBuilder.ToString ()); if (string.IsNullOrEmpty (launchResponse) || launchResponse.Contains ("Error:")) { throw new InvalidOperationException ("Launch intent failed:\n" + launchResponse); } } // // Query whether the target application is already running. (Double-check) // int launchAttempt = 1; int maxLaunchAttempts = 20; while (!appIsRunning) { Broadcast(new DebugEngineEvent.DebuggerConnectionEvent(DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format("Waiting for '{0}' to launch (Attempt {1} of {2})...", packageName, launchAttempt, maxLaunchAttempts)), null, null); LoggingUtils.RequireOk (debuggeePort.RefreshProcesses ()); // // Validate that the process is running and was spawned by one of the zygote processes. // uint [] zygotePids = debuggeePort.PortDevice.GetPidsFromName ("zygote"); uint [] zygote64Pids = debuggeePort.PortDevice.GetPidsFromName ("zygote64"); uint [] packagePids = debuggeePort.PortDevice.GetPidsFromName (packageName); for (int i = packagePids.Length - 1; i >= 0; --i) { uint pid = packagePids [i]; AndroidProcess packageProcess = debuggeePort.PortDevice.GetProcessFromPid (pid); bool spawnedByZygote = false; if ((zygotePids.Length > 0) && (packageProcess.ParentPid == zygotePids [0])) { spawnedByZygote = true; } else if ((zygote64Pids.Length > 0) && (packageProcess.ParentPid == zygote64Pids [0])) { spawnedByZygote = true; } if (spawnedByZygote) { debugProcess = debuggeePort.GetProcessForPid (pid); appIsRunning = (debugProcess != null); break; } } if (!appIsRunning) { if (++launchAttempt > maxLaunchAttempts) { throw new TimeoutException (string.Format ("'{0}' failed to launch. Please ensure device is unlocked.", packageName)); } Application.DoEvents (); Thread.Sleep (100); } } launchSuspendedMutex.Set (); } catch (Exception e) { LoggingUtils.HandleException (e); string error = string.Format ("[Exception] {0}\n{1}", e.Message, e.StackTrace); Broadcast (ad7Callback, new DebugEngineEvent.Error (error, true), null, null); launchSuspendedMutex.Set (); } }); asyncLaunchSuspendedThread.Start (); while (!launchSuspendedMutex.WaitOne (0)) { Application.DoEvents (); Thread.Sleep (100); } // // Attach to launched process. // if (debugProcess == null) { throw new InvalidOperationException (string.Format ("'{0}' failed to launch. Could not continue.", packageName)); } process = debugProcess; return Constants.S_OK; } catch (Exception e) { LoggingUtils.HandleException (e); process = null; try { string error = string.Format ("[Exception] {0}\n{1}", e.Message, e.StackTrace); Broadcast (ad7Callback, new DebugEngineEvent.Error (error, true), null, null); } catch { LoggingUtils.HandleException (e); } return Constants.E_FAIL; } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #region IDebugEngine2 Members //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public int Attach (IDebugProgram2 [] rgpPrograms, IDebugProgramNode2 [] rgpProgramNodes, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) { // // Attach the debug engine to a program. // LoggingUtils.PrintFunction (); m_sdmCallback = new DebugEngineCallback (this, ad7Callback); m_cLangCallback = new CLangDebuggerCallback (this); m_javaLangCallback = new JavaLangDebuggerCallback (this); try { if ((rgpPrograms == null) || (rgpPrograms.Length == 0)) { throw new ApplicationException ("Attach failed. No target process specified."); } if (celtPrograms > 1) { throw new ApplicationException ("Attach failed. Can not debug multiple target processes concurrently."); } if (Program != null) { throw new ApplicationException ("Attach failed. Already attached to " + Program.DebugProcess.NativeProcess.Name); } AndroidAdb.Refresh (); Program = rgpPrograms [0] as DebuggeeProgram; Program.AttachedEngine = this; Program.DebugProcess.NativeProcess.RefreshPackageInfo (); Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("Starting GDB client...")), null, null); NativeDebugger = new CLangDebugger (this, m_launchConfiguration, Program); Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("Starting JDB client...")), null, null); JavaDebugger = new JavaLangDebugger (this, Program); ThreadPool.QueueUserWorkItem (delegate (object obj) { // // When this method is called, the DE needs to send these events in sequence: // 1. IDebugEngineCreate2 // 2. IDebugProgramCreateEvent2 // 3. IDebugLoadCompleteEvent2 // 4. (if enum_ATTACH_REASON.ATTACH_REASON_LAUNCH), IDebugEntryPointEvent2 // try { Broadcast (new DebugEngineEvent.EngineCreate (this), Program, null); // // Run a couple of tests which prevent the run-as tool from functioning properly: // // 1) Test if this device/emulator is susceptible to a (usually 4.3 specific) run-as permissions bug. // https://code.google.com/p/android/issues/detail?id=58373 // 2) Test if the installed package is not declared 'debuggable'. // AndroidDevice debuggingDevice = Program.DebugProcess.NativeProcess.HostDevice; string runasPackageFileList = debuggingDevice.Shell (string.Format ("run-as {0}", Program.DebugProcess.NativeProcess.Name), "ls -l"); if (runasPackageFileList.Contains (string.Format ("run-as: Package '{0}' is unknown", Program.DebugProcess.NativeProcess.Name))) { throw new InvalidOperationException ("Can not debug native code on this device/emulator.\nMore info: https://code.google.com/p/android/issues/detail?id=58373"); } else if (runasPackageFileList.Contains (string.Format ("run-as: Package '{0}' is not debuggable", Program.DebugProcess.NativeProcess.Name))) { throw new InvalidOperationException (string.Format ("Package '{0}' is not debuggable.\nPlease ensure you're trying to connect to a 'Debug' application.\nAlternatively, completely uninstall the current app and try again.", Program.DebugProcess.NativeProcess.Name)); } Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("Attaching to '{0}'...", Program.DebugProcess.NativeProcess.Name)), null, null); LoggingUtils.RequireOk (Program.Attach (m_sdmCallback), "Failed to attach to target application."); CLangDebuggeeThread currentThread = null; NativeDebugger.RunInterruptOperation (delegate (CLangDebugger debugger) { debugger.NativeProgram.RefreshAllThreads (); currentThread = debugger.NativeProgram.GetThread (debugger.NativeProgram.CurrentThreadId); if (currentThread == null) { // Lack of current thread is usually a good indication that connection/attaching failed. throw new InvalidOperationException (string.Format ("Failed to retrieve program's main thread (tid: {0}).", debugger.NativeProgram.CurrentThreadId)); } }); Broadcast (new DebugEngineEvent.ProgramCreate (), Program, null); Broadcast (new DebugEngineEvent.LoadComplete (), Program, currentThread); if (dwReason == enum_ATTACH_REASON.ATTACH_REASON_LAUNCH) { Broadcast (new DebugEngineEvent.EntryPoint (), Program, currentThread); } Broadcast (new DebugEngineEvent.AttachComplete (), Program, null); Broadcast (new DebugEngineEvent.DebuggerLogcatEvent (debuggingDevice), Program, null); Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.LogStatus, string.Format ("Attached successfully to '{0}'.", Program.DebugProcess.NativeProcess.Name)), null, null); Broadcast (new DebugEngineEvent.DebuggerConnectionEvent (DebugEngineEvent.DebuggerConnectionEvent.EventType.CloseDialog, string.Empty), null, null); } catch (Exception e) { LoggingUtils.HandleException (e); Broadcast (ad7Callback, new DebugEngineEvent.Error (e.Message, true), Program, null); Detach (Program); } }); return Constants.S_OK; } catch (Exception e) { LoggingUtils.HandleException (e); Broadcast (ad7Callback, new DebugEngineEvent.Error (e.Message, true), Program, null); Detach (Program); return Constants.E_FAIL; } }