public void BeforeAllTests() { try { DeviceSdkVersion = GetSdkVersion(); if (HasDevices = DeviceSdkVersion != -1) { if (DeviceSdkVersion >= 21) { DeviceAbi = RunAdbCommand("shell getprop ro.product.cpu.abilist64").Trim(); } if (string.IsNullOrEmpty(DeviceAbi)) { DeviceAbi = RunAdbCommand("shell getprop ro.product.cpu.abi") ?? RunAdbCommand("shell getprop ro.product.cpu.abi2"); } if (DeviceAbi.Contains(",")) { DeviceAbi = DeviceAbi.Split(',')[0]; } } } catch (Exception ex) { Console.Error.WriteLine("Failed to determine whether there is Android target emulator or not: " + ex); } }
private LaunchOptions SetupForDebuggingWorker(CancellationToken token) { CancellationTokenRegistration onCancelRegistration = token.Register(() => { _gdbServerExecCancellationSource.Cancel(); }); using (onCancelRegistration) { // TODO: Adb exception messages should be improved. Example, if ADB is not started, this is returned: // + [libadb.AdbException] {"Could not connect to the adb.exe server. See InnerException for details."} libadb.AdbException // 'See InnerException for details.' should not be there. It should just add the inner exception message: // [System.Net.Sockets.SocketException] {"No connection could be made because the target machine actively refused it 127.0.0.1:5037"} System.Net.Sockets.SocketException Device device = null; string workingDirectory = null; string gdbServerRemotePath = null; string gdbServerSocketDescription = null; string exePath = null; Task taskGdbServer = null; int gdbPortNumber = 0; int progressCurrentIndex = 0; int progressStepCount = 0; List<NamedAction> actions = new List<NamedAction>(); actions.Add(new NamedAction(LauncherResources.Step_ResolveInstallPaths, () => { _installPaths = InstallPaths.Resolve(token, _launchOptions, Logger); })); actions.Add(new NamedAction(LauncherResources.Step_ConnectToDevice, () => { Adb adb; try { adb = new Adb(_installPaths.SDKRoot); } catch (ArgumentException) { throw new LauncherException(Telemetry.LaunchFailureCode.InvalidAndroidSDK, string.Format(CultureInfo.CurrentCulture, LauncherResources.Error_InvalidAndroidSDK, _installPaths.SDKRoot)); } try { adb.Start(); device = adb.GetDeviceById(_launchOptions.DeviceId); // There is a rare case, which we have seen it a few times now with the Android emulator where the device will be initially // in the offline state. But after a very short amount of time it comes online. Retry waiting for this. if (device.GetState().HasFlag(DeviceState.Offline)) { // Add in an extra progress step and update the dialog progressStepCount++; _waitLoop.SetProgress(progressStepCount, progressCurrentIndex, LauncherResources.Step_WaitingForDeviceToComeOnline); progressCurrentIndex++; const int waitTimePerIteration = 50; const int maxTries = 5000 / waitTimePerIteration; // We will wait for up to 5 seconds // NOTE: libadb has device discovery built in which we could allegedly use instead of a retry loop, // but I couldn't get this to work (though the problem is so rare, I had a lot of trouble testing it), so just using // a retry loop. for (int cTry = 0; true; cTry++) { if (cTry == maxTries) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceOffline, LauncherResources.Error_DeviceOffline); } // Sleep for a little while unless this operation is canceled if (token.WaitHandle.WaitOne(waitTimePerIteration)) { throw new OperationCanceledException(); } if (!device.GetState().HasFlag(DeviceState.Offline)) { break; // we are no longer offline } } } } catch (AdbException) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceNotResponding, LauncherResources.Error_DeviceNotResponding); } })); actions.Add(new NamedAction(LauncherResources.Step_InspectingDevice, () => { try { DeviceAbi[] allowedAbis; switch (_launchOptions.TargetArchitecture) { case TargetArchitecture.ARM: allowedAbis = new DeviceAbi[] { DeviceAbi.armeabi, DeviceAbi.armeabiv7a }; break; case TargetArchitecture.ARM64: allowedAbis = new DeviceAbi[] { DeviceAbi.arm64v8a }; break; case TargetArchitecture.X86: allowedAbis = new DeviceAbi[] { DeviceAbi.x86 }; break; case TargetArchitecture.X64: allowedAbis = new DeviceAbi[] { DeviceAbi.x64 }; break; default: Debug.Fail("New target architucture support added without updating this code???"); throw new InvalidOperationException(); } if (!DoesDeviceSupportAnyAbi(device, allowedAbis)) { throw GetBadDeviceAbiException(device.Abi); } if (_launchOptions.TargetArchitecture == TargetArchitecture.ARM && device.IsEmulator) { _isUsingArmEmulator = true; } _shell = device.Shell; } catch (AdbException) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceNotResponding, LauncherResources.Error_DeviceNotResponding); } VerifySdkVersion(); if (_targetEngine == TargetEngine.Native) { string pwdCommand = string.Concat("run-as ", _launchOptions.Package, " /system/bin/sh -c pwd"); ExecCommand(pwdCommand); workingDirectory = PwdOutputParser.ExtractWorkingDirectory(_shell.Out, _launchOptions.Package); gdbServerRemotePath = GetGdbServerPath(workingDirectory, device); KillOldInstances(gdbServerRemotePath); } })); if (!_launchOptions.IsAttach) { actions.Add(new NamedAction(LauncherResources.Step_StartingApp, () => { string activateCommand = string.Concat("am start -D -n ", _launchOptions.Package, "/", _launchOptions.LaunchActivity); ExecCommand(activateCommand); ValidateActivityManagerOutput(activateCommand, _shell.Out); })); } actions.Add(new NamedAction(LauncherResources.Step_GettingAppProcessId, () => { _appProcessId = GetAppProcessId(); })); if (_targetEngine == TargetEngine.Native) { actions.Add(new NamedAction(LauncherResources.Step_StartGDBServer, () => { // We will default to using a unix socket with gdbserver as this is what the ndk-gdb script uses. Though we have seen // some machines where this doesn't work and we fall back to TCP instead. const bool useUnixSocket = true; taskGdbServer = StartGdbServer(gdbServerRemotePath, workingDirectory, useUnixSocket, out gdbServerSocketDescription); })); } actions.Add(new NamedAction(LauncherResources.Step_PortForwarding, () => { // TODO: Use a dynamic socket gdbPortNumber = 5039; _jdbPortNumber = 65534; if (_targetEngine == TargetEngine.Native) { device.Forward(string.Format(CultureInfo.InvariantCulture, "tcp:{0}", gdbPortNumber), gdbServerSocketDescription); } device.Forward(string.Format(CultureInfo.InvariantCulture, "tcp:{0}", _jdbPortNumber), string.Format(CultureInfo.InvariantCulture, "jdwp:{0}", _appProcessId)); })); if (_targetEngine == TargetEngine.Native) { actions.Add(new NamedAction(LauncherResources.Step_DownloadingFiles, () => { //pull binaries from the emulator/device var fileSystem = device.FileSystem; string app_process_suffix = String.Empty; switch (_launchOptions.TargetArchitecture) { case TargetArchitecture.X86: case TargetArchitecture.ARM: app_process_suffix = "32"; break; case TargetArchitecture.X64: case TargetArchitecture.ARM64: app_process_suffix = "64"; break; default: Debug.Fail("Unsupported Target Architecture!"); break; } string app_process = String.Concat("app_process", app_process_suffix); exePath = Path.Combine(_launchOptions.IntermediateDirectory, app_process); bool retry = false; try { fileSystem.Download(@"/system/bin/" + app_process, exePath, true); } catch (AdbException) when (String.Compare(app_process_suffix, "32", StringComparison.OrdinalIgnoreCase) == 0) { // Older devices don't have an 'app_process32', only an 'app_process', so retry // NOTE: libadb doesn't have an error code property to verify that this is caused // by the file not being found. retry = true; } if (retry) { app_process = "app_process"; exePath = Path.Combine(_launchOptions.IntermediateDirectory, app_process); fileSystem.Download(@"/system/bin/app_process", exePath, true); } //on 64 bit, 'linker64' is the 64bit version and 'linker' is the 32 bit version string suffix64bit = String.Empty; if (_launchOptions.TargetArchitecture == TargetArchitecture.X64 || _launchOptions.TargetArchitecture == TargetArchitecture.ARM64) { suffix64bit = "64"; } string linker = String.Concat("linker", suffix64bit); fileSystem.Download(String.Concat(@"/system/bin/", linker), Path.Combine(_launchOptions.IntermediateDirectory, linker), true); //on 64 bit, libc.so lives in /system/lib64/, on 32 bit it lives in simply /system/lib/ fileSystem.Download(@"/system/lib" + suffix64bit + "/libc.so", Path.Combine(_launchOptions.IntermediateDirectory, "libc.so"), true); })); } progressStepCount = actions.Count; foreach (NamedAction namedAction in actions) { token.ThrowIfCancellationRequested(); _waitLoop.SetProgress(progressStepCount, progressCurrentIndex, namedAction.Name); progressCurrentIndex++; namedAction.Action(); } _waitLoop.SetProgress(progressStepCount, progressStepCount, string.Empty); if (_targetEngine == TargetEngine.Native && taskGdbServer.IsCompleted) { token.ThrowIfCancellationRequested(); throw new LauncherException(Telemetry.LaunchFailureCode.GDBServerFailed, LauncherResources.Error_GDBServerFailed); } if (_launchOptions.LogcatServiceId != Guid.Empty) { _eventCallback.OnCustomDebugEvent(_launchOptions.LogcatServiceId, new Guid(LogcatServiceMessage_SourceId), LogcatServiceMessage_NewProcess, _appProcessId, null); } LaunchOptions launchOptions = null; if (_targetEngine == TargetEngine.Native) { launchOptions = new LocalLaunchOptions(_installPaths.GDBPath, string.Format(CultureInfo.InvariantCulture, ":{0}", gdbPortNumber), 0, null); launchOptions.ExePath = exePath; } else { launchOptions = new JavaLaunchOptions(_launchOptions.JVMHost, _launchOptions.JVMPort, _launchOptions.SourceRoots, _launchOptions.Package); } launchOptions.AdditionalSOLibSearchPath = _launchOptions.AdditionalSOLibSearchPath; launchOptions.AbsolutePrefixSOLibSearchPath = _launchOptions.AbsolutePrefixSOLibSearchPath; // The default ABI is 'Cygwin' in the Android NDK >= r11 for Windows. launchOptions.SetupCommands = new ReadOnlyCollection<LaunchCommand>( new LaunchCommand[] { new LaunchCommand("-gdb-set osabi GNU/Linux") }); launchOptions.TargetArchitecture = _launchOptions.TargetArchitecture; launchOptions.WorkingDirectory = _launchOptions.IntermediateDirectory; launchOptions.DebuggerMIMode = MIMode.Gdb; launchOptions.VisualizerFile = "Microsoft.Android.natvis"; launchOptions.WaitDynamicLibLoad = _launchOptions.WaitDynamicLibLoad; return launchOptions; } }
private LauncherException GetBadDeviceAbiException(DeviceAbi deviceAbi) { string message = string.Format(CultureInfo.CurrentCulture, LauncherResources.Error_BadDeviceAbi, _launchOptions.TargetArchitecture, _launchOptions.DeviceId, deviceAbi); throw new LauncherException(Telemetry.LaunchFailureCode.BadDeviceAbi, message); }
private static bool DoesDeviceSupportAnyAbi(Device device, DeviceAbi[] allowedAbis) { if (allowedAbis.Contains(device.Abi)) return true; if (allowedAbis.Contains(device.Abi2)) return true; string abiListValue; if (device.Properties.TryGetPropertyByName("ro.product.cpu.abilist", out abiListValue)) { string[] deviceAbis = abiListValue.Split(','); foreach (DeviceAbi allowedAbi in allowedAbis) { string allowedAbiString = allowedAbi.ToString(); foreach (string deviceAbi in deviceAbis) { if (deviceAbi.Equals(allowedAbiString, StringComparison.OrdinalIgnoreCase)) return true; } } } return false; }
private LaunchOptions SetupForDebuggingWorker(CancellationToken token) { CancellationTokenRegistration onCancelRegistration = token.Register(() => { _gdbServerExecCancellationSource.Cancel(); }); using (onCancelRegistration) { // TODO: Adb exception messages should be improved. Example, if ADB is not started, this is returned: // + [libadb.AdbException] {"Could not connect to the adb.exe server. See InnerException for details."} libadb.AdbException // 'See InnerException for details.' should not be there. It should just add the inner exception message: // [System.Net.Sockets.SocketException] {"No connection could be made because the target machine actively refused it 127.0.0.1:5037"} System.Net.Sockets.SocketException Device device = null; string workingDirectory = null; string gdbServerRemotePath = null; string gdbServerSocketDescription = null; string exePath = null; Task taskGdbServer = null; int gdbPortNumber = 0; int progressCurrentIndex = 0; int progressStepCount = 0; List <NamedAction> actions = new List <NamedAction>(); actions.Add(new NamedAction(LauncherResources.Step_ResolveInstallPaths, () => { _installPaths = InstallPaths.Resolve(token, _launchOptions); })); actions.Add(new NamedAction(LauncherResources.Step_ConnectToDevice, () => { Adb adb; try { adb = new Adb(_installPaths.SDKRoot); } catch (ArgumentException) { throw new LauncherException(Telemetry.LaunchFailureCode.InvalidAndroidSDK, string.Format(CultureInfo.CurrentCulture, LauncherResources.Error_InvalidAndroidSDK, _installPaths.SDKRoot)); } try { adb.Start(); device = adb.GetDeviceById(_launchOptions.DeviceId); // There is a rare case, which we have seen it a few times now with the Android emulator where the device will be initially // in the offline state. But after a very short amount of time it comes online. Retry waiting for this. if (device.GetState().HasFlag(DeviceState.Offline)) { // Add in an extra progress step and update the dialog progressStepCount++; _waitLoop.SetProgress(progressStepCount, progressCurrentIndex, LauncherResources.Step_WaitingForDeviceToComeOnline); progressCurrentIndex++; const int waitTimePerIteration = 50; const int maxTries = 5000 / waitTimePerIteration; // We will wait for up to 5 seconds // NOTE: libadb has device discovery built in which we could allegedly use instead of a retry loop, // but I couldn't get this to work (though the problem is so rare, I had a lot of trouble testing it), so just using // a retry loop. for (int cTry = 0; true; cTry++) { if (cTry == maxTries) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceOffline, LauncherResources.Error_DeviceOffline); } // Sleep for a little while unless this operation is canceled if (token.WaitHandle.WaitOne(waitTimePerIteration)) { throw new OperationCanceledException(); } if (!device.GetState().HasFlag(DeviceState.Offline)) { break; // we are no longer offline } } } } catch (AdbException) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceNotResponding, LauncherResources.Error_DeviceNotResponding); } })); actions.Add(new NamedAction(LauncherResources.Step_InspectingDevice, () => { try { DeviceAbi[] allowedAbis; switch (_launchOptions.TargetArchitecture) { case TargetArchitecture.ARM: allowedAbis = new DeviceAbi[] { DeviceAbi.armeabi, DeviceAbi.armeabiv7a }; break; case TargetArchitecture.ARM64: allowedAbis = new DeviceAbi[] { DeviceAbi.arm64v8a }; break; case TargetArchitecture.X86: allowedAbis = new DeviceAbi[] { DeviceAbi.x86 }; break; case TargetArchitecture.X64: allowedAbis = new DeviceAbi[] { DeviceAbi.x64 }; break; default: Debug.Fail("New target architucture support added without updating this code???"); throw new InvalidOperationException(); } if (!DoesDeviceSupportAnyAbi(device, allowedAbis)) { throw GetBadDeviceAbiException(device.Abi); } if (_launchOptions.TargetArchitecture == TargetArchitecture.ARM && device.IsEmulator) { _isUsingArmEmulator = true; } _shell = device.Shell; } catch (AdbException) { throw new LauncherException(Telemetry.LaunchFailureCode.DeviceNotResponding, LauncherResources.Error_DeviceNotResponding); } VerifySdkVersion(); if (_targetEngine == TargetEngine.Native) { string pwdCommand = string.Concat("run-as ", _launchOptions.Package, " /system/bin/sh -c pwd"); ExecCommand(pwdCommand); workingDirectory = PwdOutputParser.ExtractWorkingDirectory(_shell.Out, _launchOptions.Package); gdbServerRemotePath = GetGdbServerPath(workingDirectory); KillOldInstances(gdbServerRemotePath); } })); if (!_launchOptions.IsAttach) { actions.Add(new NamedAction(LauncherResources.Step_StartingApp, () => { string activateCommand = string.Concat("am start -D -n ", _launchOptions.Package, "/", _launchOptions.LaunchActivity); ExecCommand(activateCommand); ValidateActivityManagerOutput(activateCommand, _shell.Out); })); } actions.Add(new NamedAction(LauncherResources.Step_GettingAppProcessId, () => { _appProcessId = GetAppProcessId(); })); if (_targetEngine == TargetEngine.Native) { actions.Add(new NamedAction(LauncherResources.Step_StartGDBServer, () => { // We will default to using a unix socket with gdbserver as this is what the ndk-gdb script uses. Though we have seen // some machines where this doesn't work and we fall back to TCP instead. const bool useUnixSocket = true; taskGdbServer = StartGdbServer(gdbServerRemotePath, workingDirectory, useUnixSocket, out gdbServerSocketDescription); })); } actions.Add(new NamedAction(LauncherResources.Step_PortForwarding, () => { // TODO: Use a dynamic socket gdbPortNumber = 5039; _jdbPortNumber = 65534; if (_targetEngine == TargetEngine.Native) { device.Forward(string.Format(CultureInfo.InvariantCulture, "tcp:{0}", gdbPortNumber), gdbServerSocketDescription); } device.Forward(string.Format(CultureInfo.InvariantCulture, "tcp:{0}", _jdbPortNumber), string.Format(CultureInfo.InvariantCulture, "jdwp:{0}", _appProcessId)); })); if (_targetEngine == TargetEngine.Native) { actions.Add(new NamedAction(LauncherResources.Step_DownloadingFiles, () => { //pull binaries from the emulator/device var fileSystem = device.FileSystem; string app_process_suffix = String.Empty; switch (_launchOptions.TargetArchitecture) { case TargetArchitecture.X86: case TargetArchitecture.ARM: app_process_suffix = "32"; break; case TargetArchitecture.X64: case TargetArchitecture.ARM64: app_process_suffix = "64"; break; default: Debug.Fail("Unsupported Target Architecture!"); break; } string app_process = String.Concat("app_process", app_process_suffix); exePath = Path.Combine(_launchOptions.IntermediateDirectory, app_process); bool retry = false; try { fileSystem.Download(@"/system/bin/" + app_process, exePath, true); } catch (AdbException) when(String.Compare(app_process_suffix, "32", StringComparison.OrdinalIgnoreCase) == 0) { // Older devices don't have an 'app_process32', only an 'app_process', so retry // NOTE: libadb doesn't have an error code property to verify that this is caused // by the file not being found. retry = true; } if (retry) { app_process = "app_process"; exePath = Path.Combine(_launchOptions.IntermediateDirectory, app_process); fileSystem.Download(@"/system/bin/app_process", exePath, true); } //on 64 bit, 'linker64' is the 64bit version and 'linker' is the 32 bit version string suffix64bit = String.Empty; if (_launchOptions.TargetArchitecture == TargetArchitecture.X64 || _launchOptions.TargetArchitecture == TargetArchitecture.ARM64) { suffix64bit = "64"; } string linker = String.Concat("linker", suffix64bit); fileSystem.Download(String.Concat(@"/system/bin/", linker), Path.Combine(_launchOptions.IntermediateDirectory, linker), true); //on 64 bit, libc.so lives in /system/lib64/, on 32 bit it lives in simply /system/lib/ fileSystem.Download(@"/system/lib" + suffix64bit + "/libc.so", Path.Combine(_launchOptions.IntermediateDirectory, "libc.so"), true); })); } progressStepCount = actions.Count; foreach (NamedAction namedAction in actions) { token.ThrowIfCancellationRequested(); _waitLoop.SetProgress(progressStepCount, progressCurrentIndex, namedAction.Name); progressCurrentIndex++; namedAction.Action(); } _waitLoop.SetProgress(progressStepCount, progressStepCount, string.Empty); if (_targetEngine == TargetEngine.Native && taskGdbServer.IsCompleted) { token.ThrowIfCancellationRequested(); throw new LauncherException(Telemetry.LaunchFailureCode.GDBServerFailed, LauncherResources.Error_GDBServerFailed); } if (_launchOptions.LogcatServiceId != Guid.Empty) { _eventCallback.OnCustomDebugEvent(_launchOptions.LogcatServiceId, new Guid(LogcatServiceMessage_SourceId), LogcatServiceMessage_NewProcess, _appProcessId, null); } LaunchOptions launchOptions = null; if (_targetEngine == TargetEngine.Native) { launchOptions = new LocalLaunchOptions(_installPaths.GDBPath, string.Format(CultureInfo.InvariantCulture, ":{0}", gdbPortNumber)); launchOptions.ExePath = exePath; } else { launchOptions = new JavaLaunchOptions(_launchOptions.JVMHost, _launchOptions.JVMPort, _launchOptions.SourceRoots, _launchOptions.Package); } launchOptions.AdditionalSOLibSearchPath = _launchOptions.AdditionalSOLibSearchPath; launchOptions.TargetArchitecture = _launchOptions.TargetArchitecture; launchOptions.WorkingDirectory = _launchOptions.IntermediateDirectory; launchOptions.DebuggerMIMode = MIMode.Gdb; launchOptions.VisualizerFile = "Microsoft.Android.natvis"; return(launchOptions); } }