//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Kill() { LoggingUtils.PrintFunction(); try { if (m_gdbServerInstance != null) { m_gdbServerInstance.Kill(); } } catch (Exception e) { LoggingUtils.HandleException(e); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } } } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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)); } } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Dispose () { LoggingUtils.PrintFunction (); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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(); } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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()); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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>()); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 void Stop() { LoggingUtils.PrintFunction(); Kill(); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public JdbClient(JdbSetup jdbSetup) { LoggingUtils.PrintFunction(); m_jdbSetup = jdbSetup; }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 (); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Refresh() { LoggingUtils.PrintFunction(); RefreshProcesses(); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Terminate() { LoggingUtils.PrintFunction(); SendAsyncCommand("exit"); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Continue() { LoggingUtils.PrintFunction(); SendCommand("cont"); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Interrupt() { LoggingUtils.PrintFunction(); SendCommand("interrupt 0"); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public void Detach() { LoggingUtils.PrintFunction(); SendCommand("exit"); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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; } } } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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."); } }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 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); } } } } } }