示例#1
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Kill()
        {
            LoggingUtils.PrintFunction();

            try
            {
                if (m_gdbServerInstance != null)
                {
                    m_gdbServerInstance.Kill();
                }
            }
            catch (Exception e)
            {
                LoggingUtils.HandleException(e);
            }
        }
示例#2
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void StepOver(uint threadId, StepType stepType, bool reverse)
        {
            LoggingUtils.PrintFunction();

            switch (stepType)
            {
            case StepType.Statement:
            case StepType.Line:
            case StepType.Instruction:
            {
                SendCommand("next");

                break;
            }
            }
        }
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Kill()
        {
            LoggingUtils.PrintFunction();

            try
            {
                if ((m_process != null) && (!m_process.HasExited))
                {
                    m_process.Kill();
                }
            }
            catch (Exception e)
            {
                LoggingUtils.HandleException(e);
            }
        }
示例#4
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void PopulateProperties()
        {
            LoggingUtils.PrintFunction();

            string getPropOutput = Shell("getprop", "");

            if (!string.IsNullOrEmpty(getPropOutput))
            {
                string pattern = @"^\[(?<key>[^\]:]+)\]:[ ]+\[(?<value>[^\]$]+)";

                Regex regExMatcher = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                string [] properties = getPropOutput.Replace("\r", "").Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

                for (int i = 0; i < properties.Length; ++i)
                {
                    if (!properties [i].StartsWith("["))
                    {
                        continue; // early rejection.
                    }

                    string unescapedStream = Regex.Unescape(properties [i]);

                    Match regExLineMatch = regExMatcher.Match(unescapedStream);

                    if (regExLineMatch.Success)
                    {
                        string key = regExLineMatch.Result("${key}");

                        string value = regExLineMatch.Result("${value}");

                        if (string.IsNullOrEmpty(key) || key.Equals("${key}"))
                        {
                            continue;
                        }
                        else if (value.Equals("${value}"))
                        {
                            continue;
                        }
                        else
                        {
                            m_deviceProperties [key] = value;
                        }
                    }
                }
            }
        }
示例#5
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void ClearPortForwarding ()
    {
      // 
      // Clear network redirection.
      // 

      LoggingUtils.PrintFunction ();

      StringBuilder forwardArgsBuilder = new StringBuilder ();

      forwardArgsBuilder.AppendFormat ("--remove tcp:{0}", Port);

      using (SyncRedirectProcess adbPortForward = AndroidAdb.AdbCommand (Process.HostDevice, "forward", forwardArgsBuilder.ToString ()))
      {
        adbPortForward.StartAndWaitForExit (1000);
      }
    }
示例#6
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public uint [] GetChildPidsFromPpid(uint parentProcessId)
        {
            LoggingUtils.PrintFunction();

            HashSet <uint> processPpidSiblingSet;

            uint [] processesArray = new uint [] { };

            if (m_devicePidsByPpid.TryGetValue(parentProcessId, out processPpidSiblingSet))
            {
                processesArray = new uint [processPpidSiblingSet.Count];

                processPpidSiblingSet.CopyTo(processesArray, 0);
            }

            return(processesArray);
        }
示例#7
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public uint [] GetPidsFromName(string processName)
        {
            LoggingUtils.PrintFunction();

            HashSet <uint> processPidSet;

            uint [] processesArray = new uint [] { };

            if (m_devicePidsByName.TryGetValue(processName, out processPidSet))
            {
                processesArray = new uint [processPidSet.Count];

                processPidSet.CopyTo(processesArray, 0);
            }

            return(processesArray);
        }
示例#8
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void StepOut(uint threadId, StepType stepType, bool reverse)
        {
            LoggingUtils.PrintFunction();

            switch (stepType)
            {
            case StepType.Statement:
            case StepType.Line:
            case StepType.Instruction:
            {
                string command = "step up";

                SendCommand(command);

                break;
            }
            }
        }
示例#9
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void KillActiveGdbServerSessions()
        {
            LoggingUtils.PrintFunction();

            //
            // Killing GDB server instances requires use of run-as [package-name],
            // but it's very difficult to get the parent package of lib/gdbserver as the PPID
            // will always refer to the zygote. This hack uses the sand-boxed 'user' to try all combinations.
            //

            m_gdbSetup.Process.HostDevice.Refresh();

            uint [] activeDevicePids = m_gdbSetup.Process.HostDevice.GetActivePids();

            List <AndroidProcess> activeGdbProcesses = new List <AndroidProcess> ();

            foreach (uint pid in activeDevicePids)
            {
                AndroidProcess process = m_gdbSetup.Process.HostDevice.GetProcessFromPid(pid);

                if (process.Name.Contains("lib/gdbserver"))
                {
                    activeGdbProcesses.Add(process);
                }
            }

            foreach (AndroidProcess gdbProcess in activeGdbProcesses)
            {
                foreach (uint pid in activeDevicePids)
                {
                    AndroidProcess process = m_gdbSetup.Process.HostDevice.GetProcessFromPid(pid);

                    if ((gdbProcess != process) && (gdbProcess.User.Equals(process.User)))
                    {
                        LoggingUtils.Print(string.Format("[GdbServer] Attempting to terminate existing GDB debugging session: {0} ({1}).", gdbProcess.Name, gdbProcess.Pid));

                        m_gdbSetup.Process.HostDevice.Shell("run-as", string.Format("{0} kill -9 {1}", process.Name, gdbProcess.Pid));
                    }
                }
            }
        }
示例#10
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Attach()
        {
            LoggingUtils.PrintFunction();

            try
            {
                m_jdbSetup.ClearPortForwarding();

                m_jdbSetup.SetupPortForwarding();

                m_jdbClientInstance.Start(this);

                m_timeSinceLastOperation.Start();

                /*uint timeout = 15000;
                 *
                 * bool responseSignaled = false;
                 *
                 * while ((!responseSignaled) && (m_timeSinceLastOperation.ElapsedMilliseconds < timeout))
                 * {
                 * responseSignaled = m_sessionStarted.WaitOne (0);
                 *
                 * if (!responseSignaled)
                 * {
                 *  Thread.Sleep (100);
                 * }
                 * }
                 *
                 * if (!responseSignaled)
                 * {
                 * throw new TimeoutException ("Timed out waiting for JDB client to execute/attach");
                 * }*/
            }
            catch (Exception e)
            {
                LoggingUtils.HandleException(e);

                throw;
            }
        }
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        protected static ProcessStartInfo CreateDefaultStartInfo()
        {
            LoggingUtils.PrintFunction();

            ProcessStartInfo startInfo = new ProcessStartInfo();

            startInfo.CreateNoWindow = true;

            startInfo.UseShellExecute = false;

            startInfo.LoadUserProfile = false;

            startInfo.ErrorDialog = false;

            startInfo.RedirectStandardOutput = true;

            startInfo.RedirectStandardError = true;

            startInfo.RedirectStandardInput = true;

            return(startInfo);
        }
示例#12
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public void Dispose ()
    {
      LoggingUtils.PrintFunction ();
    }
示例#13
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void RefreshProcesses(uint processIdFilter = 0)
        {
            //
            // Skip the first line, and read in tab-separated process data.
            //

            LoggingUtils.PrintFunction();

            string deviceProcessList = Shell("ps", string.Format("-t {0}", ((processIdFilter == 0) ? "" : processIdFilter.ToString())));

            if (!String.IsNullOrEmpty(deviceProcessList))
            {
                string [] processesOutputLines = deviceProcessList.Replace("\r", "").Split(new char [] { '\n' });

                string processesRegExPattern = @"(?<user>[^ ]+)[ ]*(?<pid>[0-9]+)[ ]*(?<ppid>[0-9]+)[ ]*(?<vsize>[0-9]+)[ ]*(?<rss>[0-9]+)[ ]*(?<wchan>[^ ]+)[ ]*(?<pc>[A-Za-z0-9]+)[ ]*(?<s>[^ ]+)[ ]*(?<name>[^\r\n]+)";

                Regex regExMatcher = new Regex(processesRegExPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);

                m_deviceProcessesByPid.Clear();

                m_devicePidsByName.Clear();

                m_devicePidsByPpid.Clear();

                for (uint i = 1; i < processesOutputLines.Length; ++i)
                {
                    if (!String.IsNullOrEmpty(processesOutputLines [i]))
                    {
                        Match regExLineMatches = regExMatcher.Match(processesOutputLines [i]);

                        string processUser = regExLineMatches.Result("${user}");

                        uint processPid = uint.Parse(regExLineMatches.Result("${pid}"));

                        uint processPpid = uint.Parse(regExLineMatches.Result("${ppid}"));

                        uint processVsize = uint.Parse(regExLineMatches.Result("${vsize}"));

                        uint processRss = uint.Parse(regExLineMatches.Result("${rss}"));

                        string processWchan = regExLineMatches.Result("${wchan}");

                        string processPc = regExLineMatches.Result("${pc}");

                        string processPcS = regExLineMatches.Result("${s}");

                        string processName = regExLineMatches.Result("${name}");

                        AndroidProcess process = new AndroidProcess(this, processName, processPid, processPpid, processUser);

                        m_deviceProcessesByPid [processPid] = process;

                        //
                        // Add new process to a fast-lookup collection organised by process name.
                        //

                        HashSet <uint> processPidsList;

                        if (!m_devicePidsByName.TryGetValue(processName, out processPidsList))
                        {
                            processPidsList = new HashSet <uint> ();
                        }

                        if (!processPidsList.Contains(processPid))
                        {
                            processPidsList.Add(processPid);
                        }

                        m_devicePidsByName [processName] = processPidsList;

                        //
                        // Check whether this process is sibling of another; keep ppids-pid relationships tracked.
                        //

                        HashSet <uint> processPpidSiblingList;

                        if (!m_devicePidsByPpid.TryGetValue(processPpid, out processPpidSiblingList))
                        {
                            processPpidSiblingList = new HashSet <uint> ();
                        }

                        if (!processPpidSiblingList.Contains(process.Pid))
                        {
                            processPpidSiblingList.Add(process.Pid);
                        }

                        m_devicePidsByPpid [processPpid] = processPpidSiblingList;
                    }
                }
            }
        }
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Populate(List <MiResultValue> breakpointValues)
        {
            LoggingUtils.PrintFunction();

            if (breakpointValues == null)
            {
                throw new ArgumentNullException(nameof(breakpointValues));
            }

            foreach (MiResultValue resultValue in breakpointValues)
            {
                if (resultValue.HasField("number"))
                {
                    ID = resultValue ["number"] [0].GetUnsignedInt();
                }

                if (resultValue.HasField("type"))
                {
                    Type = resultValue ["type"] [0].GetString();
                }

                if (resultValue.HasField("disp"))
                {
                    Disposition = resultValue ["disp"] [0].GetString();
                }

                if (resultValue.HasField("enabled"))
                {
                    Enabled = (resultValue ["enabled"] [0].GetString().Equals("y"));
                }

                if (resultValue.HasField("addr"))
                {
                    string addr = resultValue ["addr"] [0].GetString();

                    bool pending = addr.Equals("<PENDING>");

                    bool multiple = addr.Equals("<MULTIPLE>");

                    if (pending)
                    {
                        Address = Pending;
                    }
                    else if (multiple)
                    {
                        Address = Multiple;
                    }
                    else
                    {
                        if (addr.ToLower().StartsWith("0x"))
                        {
                            Address = ulong.Parse(addr.Substring(2), NumberStyles.HexNumber);
                        }
                        else
                        {
                            Address = ulong.Parse(addr, NumberStyles.HexNumber);
                        }
                    }
                }

                if (resultValue.HasField("func"))
                {
                    Function = resultValue ["func"] [0].GetString();
                }

                if (resultValue.HasField("filename"))
                {
                    Filename = resultValue ["filename"] [0].GetString();
                }

                if (resultValue.HasField("fullname"))
                {
                    Fullname = resultValue ["fullname"] [0].GetString();
                }

                if (resultValue.HasField("line"))
                {
                    Line = resultValue ["line"] [0].GetUnsignedInt();
                }
            }
        }
示例#15
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void RefreshPackageInfo()
        {
            LoggingUtils.PrintFunction();

            StringBuilder builder = new StringBuilder(256);

            //
            // Retrieves the install specific (coded) remote APK path.
            //   i.e: /data/app/com.example.hellogdbserver-2.apk
            //

            builder.Length = 0;

            builder.Append(HostDevice.Shell("pm", string.Format("path {0}", Name)));

            builder.Replace("\r", "");

            builder.Replace("\n", "");

            string remoteAppPath = builder.ToString();

            if (remoteAppPath.StartsWith("package:"))
            {
                m_apkPath = remoteAppPath.Substring("package:".Length);
            }

            //
            // Retrieves the data directory associated with an installed application.
            //   i.e: /data/data/com.example.hellogdbserver/
            //

            builder.Length = 0;

            builder.Append(HostDevice.Shell("run-as", string.Format("{0} /system/bin/sh -c pwd", Name)));

            builder.Replace("\r", "");

            builder.Replace("\n", "");

            string remoteDataDirectory = builder.ToString();

            if (remoteDataDirectory.StartsWith("/data/"))
            {
                m_dataDir = remoteDataDirectory;
            }

            //
            // Perform an 'adb shell pm dump <package>' request, and parse output for relevant data.
            // - This isn't available on older devices; it's a fairly recent addition. JB+ possibly?
            // - TODO: This is extremely sub-optimal, but will have to do for now.
            //

            if (HostDevice.SdkVersion >= AndroidSettings.VersionCode.JELLY_BEAN_MR1)
            {
                builder.Length = 0;

                builder.Append(HostDevice.Shell("pm", string.Format("dump {0}", Name)));

                builder.Replace("\r", "");

                string [] packageDumpReport = builder.ToString().Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

                for (int i = 0; i < packageDumpReport.Length; ++i)
                {
                    string line = packageDumpReport [i];

                    if (line.StartsWith(CODE_PATH_EXPRESSION))
                    {
                        string codePath = line.Substring(CODE_PATH_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(codePath))
                        {
                            m_codePath = codePath;
                        }
                    }
                    else if (line.StartsWith(DATA_DIR_EXPRESSION))
                    {
                        string dataDir = line.Substring(DATA_DIR_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(dataDir))
                        {
                            m_dataDir = dataDir;
                        }
                    }
                    else if (line.StartsWith(RESOURCE_PATH_EXPRESSION))
                    {
                        string resourcePath = line.Substring(RESOURCE_PATH_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(resourcePath))
                        {
                            m_resourcePath = resourcePath;
                        }
                    }
                    else if (line.StartsWith(NATIVE_LIBRARY_PATH_EXPRESSION))
                    {
                        string nativeLibraryPath = line.Substring(NATIVE_LIBRARY_PATH_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(nativeLibraryPath))
                        {
                            m_nativeLibraryPath = nativeLibraryPath;
                        }
                    }
                    else if (line.StartsWith(LEGACY_NATIVE_LIBRARY_DIR_EXPRESSION))
                    {
                        string legacyNativeLibraryDir = line.Substring(LEGACY_NATIVE_LIBRARY_DIR_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(legacyNativeLibraryDir))
                        {
                            m_legacyNativeLibraryDir = legacyNativeLibraryDir;
                        }
                    }
                    else if (line.StartsWith(PRIMARY_CPU_ABI_EXPRESSION))
                    {
                        string primaryCpiAbi = line.Substring(PRIMARY_CPU_ABI_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(primaryCpiAbi) && !primaryCpiAbi.Equals("null"))
                        {
                            m_processSupportedCpuAbis.Add(primaryCpiAbi);
                        }
                    }
                    else if (line.StartsWith(SECONDARY_CPU_ABI_EXPRESSION))
                    {
                        string secondaryAbi = line.Substring(SECONDARY_CPU_ABI_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(secondaryAbi) && !secondaryAbi.Equals("null"))
                        {
                            m_processSupportedCpuAbis.Add(secondaryAbi);
                        }
                    }
                    else if (line.StartsWith(FIRST_INSTALL_TIME_EXPRESSION))
                    {
                        string firstInstallTime = line.Substring(FIRST_INSTALL_TIME_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(firstInstallTime))
                        {
                            m_firstInstallTime = firstInstallTime;
                        }
                    }
                    else if (line.StartsWith(LAST_UPDATE_TIME_EXPRESSION))
                    {
                        string lastUpdateTime = line.Substring(LAST_UPDATE_TIME_EXPRESSION.Length);

                        if (!string.IsNullOrWhiteSpace(lastUpdateTime))
                        {
                            m_lastUpdateTime = lastUpdateTime;
                        }
                    }
                    else if (line.StartsWith(PKG_FLAGS_EXPRESSION))
                    {
                        string pkgFlags = line.Substring(PKG_FLAGS_EXPRESSION.Length);

                        string [] pkgFlagsArray = pkgFlags.Trim(new char [] { '[', ']' }).Split(new char [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                        m_pkgFlags = pkgFlagsArray;
                    }
                }
            }

            //
            // Clean up some variables which may be empty or undefined.
            //

            if (string.IsNullOrWhiteSpace(m_dataDir))
            {
                m_dataDir = string.Concat("/data/data/", Name);
            }

            if (string.IsNullOrWhiteSpace(m_codePath))
            {
                if (!string.IsNullOrWhiteSpace(m_dataDir))
                {
                    m_codePath = m_dataDir;
                }
                else
                {
                    m_codePath = string.Concat("/data/data/", Name);
                }
            }

            foreach (string abi in HostDevice.SupportedCpuAbis)
            {
                //
                // Add each of the additional supported ABIs; but keep those already identified via dump output as primary/secondary.
                //

                if (!m_processSupportedCpuAbis.Contains(abi))
                {
                    m_processSupportedCpuAbis.Add(abi);
                }
            }

            if (string.IsNullOrWhiteSpace(m_legacyNativeLibraryDir))
            {
                if (HostDevice.SdkVersion >= AndroidSettings.VersionCode.JELLY_BEAN_MR1)
                {
                    string bundleId = Path.GetFileNameWithoutExtension(m_apkPath);

                    m_legacyNativeLibraryDir = string.Concat("/data/app-lib/", bundleId);
                }
                else
                {
                    m_legacyNativeLibraryDir = string.Concat(m_codePath, "/lib");
                }
            }

            if (string.IsNullOrWhiteSpace(m_nativeLibraryPath))
            {
                m_nativeLibraryPath = m_legacyNativeLibraryDir;
            }

            if (HostDevice.SdkVersion >= AndroidSettings.VersionCode.JELLY_BEAN_MR1)
            {
                ICollection <string> nativeLibraryAbiPaths = new List <string> (m_processSupportedCpuAbis.Count);

                foreach (string abi in m_processSupportedCpuAbis)
                {
                    switch (abi)
                    {
                    case "armeabi":
                    case "armeabi-v7a":
                    {
                        nativeLibraryAbiPaths.Add(string.Concat(m_nativeLibraryPath, "/", "arm"));

                        break;
                    }

                    case "arm64-v8a":
                    {
                        nativeLibraryAbiPaths.Add(string.Concat(m_nativeLibraryPath, "/", "arm64"));

                        break;
                    }

                    case "x86":
                    case "x86_64":
                    case "mips":
                    case "mips64":
                    default:
                    {
                        nativeLibraryAbiPaths.Add(string.Concat(m_nativeLibraryPath, "/", abi));

                        break;
                    }
                    }
                }

                m_nativeLibraryAbiPaths = nativeLibraryAbiPaths;
            }
        }
示例#16
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public ICollection <string> CacheSystemLibraries()
        {
            //
            // Evaluate the remote libraries required for debugging on the host device.
            //

            LoggingUtils.PrintFunction();

            List <string> systemLibraries = new List <string> ();

            systemLibraries.AddRange(new string []
            {
                "/system/lib/libandroid.so",
                "/system/lib/libandroid_runtime.so",
                "/system/lib/libbinder.so",
                "/system/lib/libc.so",
                "/system/lib/libEGL.so",
                "/system/lib/libGLESv1_CM.so",
                "/system/lib/libGLESv2.so",
                "/system/lib/libutils.so",
            });

            try
            {
                string dir = "/system/lib64";

                string ls = Process.HostDevice.Shell("ls", dir);

                if (ls.ToLowerInvariant().Contains("no such file"))
                {
                    throw new DirectoryNotFoundException(dir);
                }

                systemLibraries.AddRange(new string []
                {
                    "/system/lib64/libandroid.so",
                    "/system/lib64/libandroid_runtime.so",
                    "/system/lib64/libbinder.so",
                    "/system/lib64/libc.so",
                    "/system/lib64/libEGL.so",
                    "/system/lib64/libGLESv1_CM.so",
                    "/system/lib64/libGLESv2.so",
                    "/system/lib64/libutils.so",
                });
            }
            catch (Exception)
            {
                // Ignore. No lib64 directory?
            }

            //
            // Pull the required libraries from the device.
            //

            List <string> hostBinaries = new List <string> ();

            foreach (string binary in systemLibraries)
            {
                string cachedBinary = Path.Combine(CacheSysRoot, binary.Substring(1));

                string cachedBinaryDir = Path.GetDirectoryName(cachedBinary);

                string cachedBinaryFullPath = Path.Combine(Path.GetDirectoryName(cachedBinary), Path.GetFileName(cachedBinary));

                Directory.CreateDirectory(cachedBinaryDir);

                FileInfo cachedBinaryFileInfo = new FileInfo(cachedBinaryFullPath);

                bool usedCached = false;

                if (cachedBinaryFileInfo.Exists && (DateTime.UtcNow - cachedBinaryFileInfo.CreationTimeUtc) < TimeSpan.FromDays(1))
                {
                    LoggingUtils.Print(string.Format("[GdbSetup] Using cached {0}.", binary));

                    hostBinaries.Add(cachedBinaryFullPath);

                    usedCached = true;
                }

                if (!usedCached)
                {
                    try
                    {
                        Process.HostDevice.Pull(binary, cachedBinary);

                        hostBinaries.Add(cachedBinaryFullPath);

                        LoggingUtils.Print(string.Format("[GdbSetup] Pulled {0} from device/emulator.", binary));
                    }
                    catch (Exception e)
                    {
                        LoggingUtils.HandleException(e);
                    }
                }
            }

            return(hostBinaries.ToArray());
        }
示例#17
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public ICollection <string> CacheApplicationLibraries()
        {
            //
            // Application binaries (those under /lib/ of an installed application).
            // TODO: Consider improving this. Pulling libraries ensures consistency, but takes time (ADB is a slow protocol).
            //

            LoggingUtils.PrintFunction();

            try
            {
                HashSet <string> deviceBinaries = new HashSet <string> ();

                foreach (string path in Process.NativeLibraryAbiPaths)
                {
                    string ls = string.Empty;

                    try
                    {
                        ls = Process.HostDevice.Shell("ls", path);

                        if (ls.ToLowerInvariant().Contains("no such file"))
                        {
                            throw new DirectoryNotFoundException(path);
                        }
                    }
                    catch (Exception)
                    {
                    }
                    finally
                    {
                        var libraries = ls.Replace("\r", "").Split(new char [] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

                        foreach (string file in libraries)
                        {
                            string lib = path + '/' + file;

                            deviceBinaries.Add(lib);
                        }
                    }
                }

                //
                // On Android L, Google have broken pull permissions to 'app-lib' (and '/data/app/XXX/lib/') content so we use cp to avoid this.
                //

                List <string> applicationLibraries = new List <string> (deviceBinaries.Count);

                foreach (string binary in deviceBinaries)
                {
                    string cachePath = Path.Combine(CacheSysRoot, binary.Substring(1).Replace('/', '\\'));

                    Directory.CreateDirectory(Path.GetDirectoryName(cachePath));

                    try
                    {
                        if (Process.HostDevice.SdkVersion >= AndroidSettings.VersionCode.LOLLIPOP)
                        {
                            string temporaryStorage = "/data/local/tmp/" + Path.GetFileName(cachePath);

                            Process.HostDevice.Shell("cp", string.Format("-fH {0} {1}", binary, temporaryStorage));

                            Process.HostDevice.Pull(temporaryStorage, cachePath);

                            Process.HostDevice.Shell("rm", temporaryStorage);

                            LoggingUtils.Print(string.Format("[GdbSetup] Pulled {0} from device/emulator.", binary));
                        }
                        else
                        {
                            Process.HostDevice.Pull(binary, cachePath);
                        }

                        LoggingUtils.Print(string.Format("[GdbSetup] Pulled {0} from device/emulator.", binary));

                        applicationLibraries.Add(binary);
                    }
                    catch (Exception e)
                    {
                        LoggingUtils.HandleException(string.Format("[GdbSetup] Failed pulling {0} from device/emulator.", binary), e);
                    }
                }

                return(applicationLibraries);
            }
            catch (Exception e)
            {
                LoggingUtils.HandleException(e);
            }

            return(new List <string>());
        }
示例#18
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public string [] CacheSystemLibraries (bool only64bit)
    {
      // 
      // Evaluate the remote libraries required for debugging on the host device.
      // 

      LoggingUtils.PrintFunction ();

      List<string> deviceLibraries = new List<string> ();

      string libdir = (only64bit) ? "lib64" : "lib";

      deviceLibraries.AddRange (new string []
      {
        string.Format ("/system/{0}/libandroid.so", libdir),
        string.Format ("/system/{0}/libandroid_runtime.so", libdir),
      //string.Format ("/system/{0}/libart.so", libdir),
        string.Format ("/system/{0}/libbinder.so", libdir),
        string.Format ("/system/{0}/libc.so", libdir),
      //string.Format ("/system/{0}/libdvm.so", libdir),
        string.Format ("/system/{0}/libEGL.so", libdir),
        string.Format ("/system/{0}/libGLESv1_CM.so", libdir),
        string.Format ("/system/{0}/libGLESv2.so", libdir),
        string.Format ("/system/{0}/libutils.so", libdir),
      });

      // 
      // Pull the required libraries from the device.
      // 

      List<string> hostBinaries = new List<string> ();

      foreach (string binary in deviceLibraries)
      {
        string cachedBinary = Path.Combine (CacheSysRoot, binary.Substring (1));

        string cachedBinaryDir = Path.GetDirectoryName (cachedBinary);

        string cachedBinaryFullPath = Path.Combine (Path.GetDirectoryName (cachedBinary), Path.GetFileName (cachedBinary));

        Directory.CreateDirectory (cachedBinaryDir);

        FileInfo cachedBinaryFileInfo = new FileInfo (cachedBinaryFullPath);

        bool usedCached = false;

        if (cachedBinaryFileInfo.Exists && (DateTime.UtcNow - cachedBinaryFileInfo.CreationTimeUtc) < TimeSpan.FromDays (1))
        {
          LoggingUtils.Print (string.Format ("[GdbSetup] Using cached {0}.", binary));

          hostBinaries.Add (cachedBinaryFullPath);

          usedCached = true;
        }

        if (!usedCached)
        {
          try
          {
            Process.HostDevice.Pull (binary, cachedBinary);

            hostBinaries.Add (cachedBinaryFullPath);

            LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", binary));
          }
          catch (Exception e)
          {
            LoggingUtils.HandleException (e);
          }
        }
      }

      return hostBinaries.ToArray ();
    }
示例#19
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Stop()
        {
            LoggingUtils.PrintFunction();

            Kill();
        }
示例#20
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public JdbClient(JdbSetup jdbSetup)
        {
            LoggingUtils.PrintFunction();

            m_jdbSetup = jdbSetup;
        }
示例#21
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public string [] CacheSystemBinaries (bool only64bit)
    {
      // 
      // Evaluate remote binaries required for debugging, which must be cached on the host device.
      // 

      LoggingUtils.PrintFunction ();

      List<string> deviceBinaries = new List<string> ();

      if (only64bit && (Process.HostDevice.SdkVersion >= AndroidSettings.VersionCode.LOLLIPOP))
      {
        deviceBinaries.AddRange (new string []
        {
          "/system/bin/app_process64",
          "/system/bin/linker64",
        });
      }
      else
      {
        deviceBinaries.AddRange (new string []
        {
          "/system/bin/app_process",
          "/system/bin/app_process32",
          "/system/bin/linker",
        });
      }

      // 
      // Pull the required binaries from the device.
      // 

      List<string> hostBinaries = new List<string> ();

      foreach (string binary in deviceBinaries)
      {
        string cachedBinary = Path.Combine (CacheSysRoot, binary.Substring (1));

        string cachedBinaryDir = Path.GetDirectoryName (cachedBinary);

        string cachedBinaryFullPath = Path.Combine (cachedBinaryDir, Path.GetFileName (cachedBinary));

        Directory.CreateDirectory (cachedBinaryDir);

        FileInfo cachedBinaryFileInfo = new FileInfo (cachedBinaryFullPath);

        bool usedCached = false;

        if (cachedBinaryFileInfo.Exists && (DateTime.UtcNow - cachedBinaryFileInfo.CreationTimeUtc) < TimeSpan.FromDays (1))
        {
          LoggingUtils.Print (string.Format ("[GdbSetup] Using cached {0}.", binary));

          hostBinaries.Add (cachedBinaryFullPath);

          usedCached = true;
        }

        if (!usedCached)
        {
          try
          {
            Process.HostDevice.Pull (binary, cachedBinary);

            hostBinaries.Add (cachedBinaryFullPath);

            LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", binary));
          }
          catch (Exception e)
          {
            LoggingUtils.HandleException (e);
          }
        }
      }

      return hostBinaries.ToArray ();
    }
示例#22
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Refresh()
        {
            LoggingUtils.PrintFunction();

            RefreshProcesses();
        }
示例#23
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Terminate()
        {
            LoggingUtils.PrintFunction();

            SendAsyncCommand("exit");
        }
示例#24
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Continue()
        {
            LoggingUtils.PrintFunction();

            SendCommand("cont");
        }
示例#25
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Interrupt()
        {
            LoggingUtils.PrintFunction();

            SendCommand("interrupt 0");
        }
示例#26
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Detach()
        {
            LoggingUtils.PrintFunction();

            SendCommand("exit");
        }
示例#27
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public GdbSetup (AndroidProcess process, string gdbToolPath)
    {
      LoggingUtils.PrintFunction ();

      Process = process;

      Host = "localhost";

      Port = 5039;

      if (!Process.HostDevice.IsOverWiFi)
      {
        Socket = "debug-socket";
      }

      string sanitisedDeviceId = Process.HostDevice.ID.Replace (':', '-');

      CacheDirectory = string.Format (@"{0}\Android++\Cache\{1}\{2}", Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), sanitisedDeviceId, Process.Name);

      Directory.CreateDirectory (CacheDirectory);

      CacheSysRoot = Path.Combine (CacheDirectory, "sysroot");

      Directory.CreateDirectory (CacheSysRoot);

      SymbolDirectories = new HashSet<string> ();

      GdbToolPath = gdbToolPath;

      GdbToolArguments = "--interpreter=mi ";

      if (!File.Exists (gdbToolPath))
      {
        throw new FileNotFoundException ("Could not find requested GDB instance. Expected: " + gdbToolPath);
      }

      // 
      // Spawn an initial GDB instance to evaluate the client version.
      // 

      GdbToolVersionMajor = 1;

      GdbToolVersionMinor = 0;

      using (SyncRedirectProcess gdbProcess = new SyncRedirectProcess (GdbToolPath, "--version"))
      {
        gdbProcess.StartAndWaitForExit ();

        string [] versionDetails = gdbProcess.StandardOutput.Replace ("\r", "").Split (new char [] { '\n' });

        string versionPrefix = "GNU gdb (GDB) ";

        for (int i = 0; i < versionDetails.Length; ++i)
        {
          if (versionDetails [i].StartsWith (versionPrefix))
          {
            string gdbVersion = versionDetails [i].Substring (versionPrefix.Length); ;

            string [] gdbVersionComponents = gdbVersion.Split ('.');

            if (gdbVersionComponents.Length > 0)
            {
              GdbToolVersionMajor = int.Parse (gdbVersionComponents [0]);
            }

            if (gdbVersionComponents.Length > 1)
            {
              GdbToolVersionMinor = int.Parse (gdbVersionComponents [1]);
            }

            break;
          }
        }
      }
    }
示例#28
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public void Start()
        {
            LoggingUtils.PrintFunction();

            //
            // Check the target 'gdbserver' binary exits on target device/emulator.
            //

            string gdbServerPath = string.Empty;

            List <string> potentialGdbServerPaths = new List <string> ();

            foreach (string libraryPath in m_gdbSetup.Process.NativeLibraryAbiPaths)
            {
                potentialGdbServerPaths.Add(string.Format("{0}/gdbserver", libraryPath));
            }

            potentialGdbServerPaths.Add(string.Format("{0}/gdbserver", m_gdbSetup.Process.NativeLibraryPath));

            foreach (string path in potentialGdbServerPaths)
            {
                using (SyncRedirectProcess checkGdbServer = AndroidAdb.AdbCommand(m_gdbSetup.Process.HostDevice, "shell", "ls " + path))
                {
                    int exitCode = checkGdbServer.StartAndWaitForExit(1000);

                    if ((exitCode == 0) && !checkGdbServer.StandardOutput.ToLower().Contains("no such file"))
                    {
                        gdbServerPath = path;

                        break;
                    }
                }
            }

            if (string.IsNullOrWhiteSpace(gdbServerPath))
            {
                // TODO: Push the required gdbserver binary, so we can attach to any app.
                throw new InvalidOperationException(string.Format("Failed to locate required 'gdbserver' binary on device ({0}).", m_gdbSetup.Process.HostDevice.ID));
            }

            KillActiveGdbServerSessions();

            //
            // Construct a adaptive command line based on GdbSetup requirements.
            //

            StringBuilder commandLineArgumentsBuilder = new StringBuilder();

            commandLineArgumentsBuilder.AppendFormat("run-as {0} {1} ", m_gdbSetup.Process.Name, gdbServerPath);

            if (!string.IsNullOrWhiteSpace(m_gdbSetup.Socket))
            {
                commandLineArgumentsBuilder.AppendFormat("+{0} ", m_gdbSetup.Socket);
            }

            commandLineArgumentsBuilder.Append("--attach ");

            if (string.IsNullOrWhiteSpace(m_gdbSetup.Socket)) // Don't need a host if we have a bound socket?
            {
                commandLineArgumentsBuilder.AppendFormat("{0}:{1} ", m_gdbSetup.Host, m_gdbSetup.Port);
            }

            commandLineArgumentsBuilder.Append(m_gdbSetup.Process.Pid);

            //
            // Launch 'gdbserver' and wait for output to determine success.
            //

            Stopwatch waitForConnectionTimer = new Stopwatch();

            waitForConnectionTimer.Start();

            m_gdbServerAttached = new ManualResetEvent(false);

            m_gdbServerInstance = AndroidAdb.AdbCommandAsync(m_gdbSetup.Process.HostDevice, "shell", commandLineArgumentsBuilder.ToString());

            m_gdbServerInstance.Start(this);

            LoggingUtils.Print(string.Format("[GdbServer] Waiting to attach..."));

            uint timeout = 5000;

            bool responseSignaled = false;

            while ((!responseSignaled) && (waitForConnectionTimer.ElapsedMilliseconds < timeout))
            {
                responseSignaled = m_gdbServerAttached.WaitOne(0);

                if (!responseSignaled)
                {
                    Thread.Sleep(100);
                }
            }

            if (!responseSignaled)
            {
                throw new TimeoutException("Timed out waiting for GdbServer to execute.");
            }
        }
示例#29
0
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public string [] CacheApplicationLibraries ()
    {
      // 
      // Application binaries (those under /lib/ of an installed application).
      // TODO: Consider improving this. Pulling libraries ensures consistency, but takes time (ADB is a slow protocol).
      // 

      LoggingUtils.PrintFunction ();

      try
      {
        List<string> additionalLibraries = new List<string> ();

        foreach (string nativeLibraryAbiPath in Process.NativeLibraryAbiPaths)
        {
          string nativeOatLibraryPath = nativeLibraryAbiPath.Replace ("/lib", "/oat");

          // 
          // On Android L, Google have broken pull permissions to 'app-lib' (and '/data/app/XXX/lib/') content so we use cp to avoid this.
          // 

          bool pulledLibraries = false;

          string libraryCachePath = Path.Combine (CacheSysRoot, nativeLibraryAbiPath.Substring (1));

          Directory.CreateDirectory (libraryCachePath);

          if (Process.HostDevice.SdkVersion >= AndroidSettings.VersionCode.LOLLIPOP)
          {
            string [] libraries = Process.HostDevice.Shell ("ls", nativeLibraryAbiPath).Replace ("\r", "").Split (new char [] { '\n', ' ' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string file in libraries)
            {
              string remoteLib = nativeLibraryAbiPath + "/" + file;

              string temporaryStorage = "/data/local/tmp/" + file;

              try
              {
                Process.HostDevice.Shell ("cp", string.Format ("-fH {0} {1}", remoteLib, temporaryStorage));

                Process.HostDevice.Pull (temporaryStorage, libraryCachePath);

                Process.HostDevice.Shell ("rm", temporaryStorage);

                LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", remoteLib));

                pulledLibraries = true;
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (string.Format ("[GdbSetup] Failed pulling {0} from device/emulator.", remoteLib), e);
              }
            }
          }

          // 
          // Also on Android L, Google's new oat format is an ELF which is readable by GDB. We want to include this in the sysroot.
          // 

          bool pulledOatLibraries = false;

          string libraryOatCachePath = Path.Combine (CacheSysRoot, nativeOatLibraryPath.Substring (1));

          Directory.CreateDirectory (libraryOatCachePath);

          if (Process.HostDevice.SdkVersion >= AndroidSettings.VersionCode.LOLLIPOP)
          {
            string [] oatLibraries = new string []
            {
              // Due to permissions these have to be directly referenced; ls won't work.
              "base.odex"
            };

            foreach (string file in oatLibraries)
            {
              string remoteLib = nativeOatLibraryPath + "/" + file;

              string temporaryStorage = "/data/local/tmp/" + file;

              try
              {
                Process.HostDevice.Shell ("cp", string.Format ("-fH {0} {1}", remoteLib, temporaryStorage));

                Process.HostDevice.Pull (temporaryStorage, libraryCachePath);

                Process.HostDevice.Shell ("rm", temporaryStorage);

                LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", remoteLib));

                pulledOatLibraries = true;
              }
              catch (Exception e)
              {
                LoggingUtils.HandleException (string.Format ("[GdbSetup] Failed pulling {0} from device/emulator.", remoteLib), e);
              }
            }
          }

          try
          {
            if (!pulledLibraries)
            {
              Process.HostDevice.Pull (nativeLibraryAbiPath, libraryCachePath);

              LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", nativeLibraryAbiPath));

              pulledLibraries = true;
            }

            if (!pulledOatLibraries)
            {
              Process.HostDevice.Pull (nativeOatLibraryPath, libraryOatCachePath);

              LoggingUtils.Print (string.Format ("[GdbSetup] Pulled {0} from device/emulator.", nativeOatLibraryPath));

              pulledOatLibraries = true;
            }
          }
          catch (Exception e)
          {
            LoggingUtils.HandleException (string.Format ("[GdbSetup] Failed pulling {0} from device/emulator.", nativeLibraryAbiPath), e);
          }

          additionalLibraries.AddRange (Directory.GetFiles (libraryCachePath, "lib*.so", SearchOption.AllDirectories));

          additionalLibraries.AddRange (Directory.GetFiles (libraryOatCachePath, "*.odex", SearchOption.AllDirectories));
        }

        return additionalLibraries.ToArray ();
      }
      catch (Exception e)
      {
        LoggingUtils.HandleException (e);
      }

      return new string [] {};
    }
示例#30
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public static void Refresh()
        {
            LoggingUtils.PrintFunction();

            lock (m_updateLockMutex)
            {
                //
                // Start an ADB instance, if required.
                //

                using (SyncRedirectProcess adbStartServer = new SyncRedirectProcess(AndroidSettings.SdkRoot + @"\platform-tools\adb.exe", "start-server"))
                {
                    adbStartServer.StartAndWaitForExit(30000);
                }

                using (SyncRedirectProcess adbDevices = new SyncRedirectProcess(AndroidSettings.SdkRoot + @"\platform-tools\adb.exe", "devices"))
                {
                    adbDevices.StartAndWaitForExit(30000);

                    //
                    // Parse 'devices' output, skipping headers and potential 'start-server' output.
                    //

                    Dictionary <string, string> currentAdbDevices = new Dictionary <string, string> ();

                    LoggingUtils.Print(string.Concat("[AndroidAdb] Devices output: ", adbDevices.StandardOutput));

                    if (!String.IsNullOrEmpty(adbDevices.StandardOutput))
                    {
                        string [] deviceOutputLines = adbDevices.StandardOutput.Replace("\r", "").Split(new char [] { '\n' });

                        foreach (string line in deviceOutputLines)
                        {
                            if (Regex.IsMatch(line, "^[A-Za-z0-9.:\\-]+[\t][A-Za-z]+$"))
                            {
                                string [] segments = line.Split(new char [] { '\t' });

                                string deviceName = segments [0];

                                string deviceType = segments [1];

                                currentAdbDevices.Add(deviceName, deviceType);
                            }
                        }
                    }

                    //
                    // First identify any previously tracked devices which aren't in 'devices' output.
                    //

                    HashSet <string> disconnectedDevices = new HashSet <string> ();

                    foreach (string key in m_connectedDevices.Keys)
                    {
                        string deviceName = (string)key;

                        if (!currentAdbDevices.ContainsKey(deviceName))
                        {
                            disconnectedDevices.Add(deviceName);
                        }
                    }

                    //
                    // Identify whether any devices have changed state; connected/persisted/disconnected.
                    //

                    foreach (KeyValuePair <string, string> devicePair in currentAdbDevices)
                    {
                        string deviceName = devicePair.Key;

                        string deviceType = devicePair.Value;

                        if (deviceType.Equals("offline", StringComparison.InvariantCultureIgnoreCase))
                        {
                            disconnectedDevices.Add(deviceName);
                        }
                        else if (deviceType.Equals("unauthorized", StringComparison.InvariantCultureIgnoreCase))
                        {
                            // User needs to allow USB debugging.
                        }
                        else
                        {
                            AndroidDevice connectedDevice;

                            if (m_connectedDevices.TryGetValue(deviceName, out connectedDevice))
                            {
                                //
                                // Device is pervasive. Refresh internal properties.
                                //

                                LoggingUtils.Print(string.Format("[AndroidAdb] Device pervaded: {0} - {1}", deviceName, deviceType));

                                connectedDevice.Refresh();

                                foreach (IStateListener deviceListener in m_registeredDeviceStateListeners)
                                {
                                    deviceListener.DevicePervasive(connectedDevice);
                                }
                            }
                            else
                            {
                                //
                                // Device connected.
                                //

                                LoggingUtils.Print(string.Format("[AndroidAdb] Device connected: {0} - {1}", deviceName, deviceType));

                                connectedDevice = new AndroidDevice(deviceName);

                                connectedDevice.Refresh();

                                m_connectedDevices.Add(deviceName, connectedDevice);

                                foreach (IStateListener deviceListener in m_registeredDeviceStateListeners)
                                {
                                    deviceListener.DeviceConnected(connectedDevice);
                                }
                            }
                        }
                    }

                    //
                    // Finally, handle device disconnection.
                    //

                    foreach (string deviceName in disconnectedDevices)
                    {
                        AndroidDevice disconnectedDevice;

                        if (m_connectedDevices.TryGetValue(deviceName, out disconnectedDevice))
                        {
                            LoggingUtils.Print(string.Concat("[AndroidAdb] Device disconnected: ", deviceName));

                            m_connectedDevices.Remove(deviceName);

                            foreach (IStateListener deviceListener in m_registeredDeviceStateListeners)
                            {
                                deviceListener.DeviceDisconnected(disconnectedDevice);
                            }
                        }
                    }
                }
            }
        }