//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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.Format("[AndroidAdb] Devices output: {0}", 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-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")) { disconnectedDevices.Add(deviceName); } else if (deviceType.Equals("unauthorized")) { // 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 (StateListener 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 (StateListener 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.Format("[AndroidAdb] Device disconnected: {0}", deviceName)); m_connectedDevices.Remove(deviceName); foreach (StateListener deviceListener in m_registeredDeviceStateListeners) { deviceListener.DeviceDisconnected(disconnectedDevice); } } } } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void ProcessStdout(object sendingProcess, DataReceivedEventArgs args) { if (!string.IsNullOrEmpty(args.Data)) { LoggingUtils.Print(string.Format("[JdbClient] ProcessStdout: {0}", args.Data)); try { m_timeSinceLastOperation.Restart(); if (args.Data.Equals("Initializing jdb ...")) { m_sessionStarted.Set(); } // // Distribute result records to registered delegate callbacks. // OnAsyncStdout(new string [] { args.Data }); // // Collate output for any ongoing async commands. // lock (m_asyncCommandData) { foreach (KeyValuePair <uint, AsyncCommandData> asyncCommand in m_asyncCommandData) { if (!asyncCommand.Value.Command.StartsWith("-")) { asyncCommand.Value.OutputLines.Add(args.Data); } } } // // Call the corresponding registered delegate for the token response. // uint token = m_sessionCommandToken; AsyncCommandData callbackCommandData = null; lock (m_asyncCommandData) { if (m_asyncCommandData.TryGetValue(token, out callbackCommandData)) { m_asyncCommandData.Remove(token); } } // // Spawn any registered callback handlers on a dedicated thread, as not to block JDB output. // if ((callbackCommandData != null) && (callbackCommandData.OutputDelegate != null)) { ThreadPool.QueueUserWorkItem(delegate(object state) { try { callbackCommandData.OutputDelegate(callbackCommandData.OutputLines.ToArray()); } catch (Exception e) { LoggingUtils.HandleException(e); } }); } } catch (Exception e) { LoggingUtils.HandleException(e); } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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) { try { string ls = m_gdbSetup.Process.HostDevice.Shell("ls", path); if (ls.ToLowerInvariant().Contains("no such file")) { throw new DirectoryNotFoundException(path); } gdbServerPath = path; break; } catch (Exception) { // Ignore. } } // // If we can't find a bundled 'gdbserver' binary, attempt to find one in the NDK. // if (string.IsNullOrWhiteSpace(gdbServerPath)) { foreach (string path in m_gdbSetup.Process.NativeLibraryAbiPaths) { try { string shortAbi = path.Substring(path.LastIndexOf('/') + 1); string local = Path.Combine(AndroidSettings.NdkRoot, string.Format(@"prebuilt\android-{0}\gdbserver\gdbserver", shortAbi)); string remote = string.Format("/data/local/tmp/gdbserver-{0}", shortAbi); m_gdbSetup.Process.HostDevice.Push(local, remote); } catch (Exception e) { LoggingUtils.HandleException(e); } } } if (string.IsNullOrWhiteSpace(gdbServerPath)) { 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."); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void ProcessExited(object sendingProcess, EventArgs args) { LoggingUtils.Print(string.Format("[GdbServer] ProcessExited")); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 [] {}); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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()); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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()); }