in src/AndroidDebugLauncher/Launcher.cs [152:467]
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, _targetEngine);
}));
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);
}
int deviceApiLevel = 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);
// Android apps that have their minumium API level set to 24 (Andoird 7.x, aka Nougat) or newer will
// have a working directory which isn't readable from the ADB process. Since we need to read this
// for the 'debug-socket' and we don't know the API level of the app, to be safe we chmod it anytime
// we are using a 24+ device.
if (deviceApiLevel >= 24)
{
string chmodCommand = string.Concat("run-as ", _launchOptions.Package, " /system/bin/chmod a+x ", workingDirectory);
ExecCommand(chmodCommand);
}
}
}));
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), 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;
}
}