You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
575 lines
21 KiB
575 lines
21 KiB
//===-- TargetList.cpp ----------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Target/TargetList.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/ModuleSpec.h"
|
|
#include "lldb/Host/Host.h"
|
|
#include "lldb/Host/HostInfo.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/OptionGroupPlatform.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Target/Platform.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Utility/Broadcaster.h"
|
|
#include "lldb/Utility/Event.h"
|
|
#include "lldb/Utility/State.h"
|
|
#include "lldb/Utility/TildeExpressionResolver.h"
|
|
#include "lldb/Utility/Timer.h"
|
|
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
ConstString &TargetList::GetStaticBroadcasterClass() {
|
|
static ConstString class_name("lldb.targetList");
|
|
return class_name;
|
|
}
|
|
|
|
// TargetList constructor
|
|
TargetList::TargetList(Debugger &debugger)
|
|
: Broadcaster(debugger.GetBroadcasterManager(),
|
|
TargetList::GetStaticBroadcasterClass().AsCString()),
|
|
m_target_list(), m_target_list_mutex(), m_selected_target_idx(0) {
|
|
CheckInWithManager();
|
|
}
|
|
|
|
Status TargetList::CreateTarget(Debugger &debugger,
|
|
llvm::StringRef user_exe_path,
|
|
llvm::StringRef triple_str,
|
|
LoadDependentFiles load_dependent_files,
|
|
const OptionGroupPlatform *platform_options,
|
|
TargetSP &target_sp) {
|
|
return CreateTargetInternal(debugger, user_exe_path, triple_str,
|
|
load_dependent_files, platform_options,
|
|
target_sp);
|
|
}
|
|
|
|
Status TargetList::CreateTarget(Debugger &debugger,
|
|
llvm::StringRef user_exe_path,
|
|
const ArchSpec &specified_arch,
|
|
LoadDependentFiles load_dependent_files,
|
|
PlatformSP &platform_sp, TargetSP &target_sp) {
|
|
return CreateTargetInternal(debugger, user_exe_path, specified_arch,
|
|
load_dependent_files, platform_sp, target_sp);
|
|
}
|
|
|
|
Status TargetList::CreateTargetInternal(
|
|
Debugger &debugger, llvm::StringRef user_exe_path,
|
|
llvm::StringRef triple_str, LoadDependentFiles load_dependent_files,
|
|
const OptionGroupPlatform *platform_options, TargetSP &target_sp) {
|
|
Status error;
|
|
|
|
// Let's start by looking at the selected platform.
|
|
PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
|
|
|
|
// This variable corresponds to the architecture specified by the triple
|
|
// string. If that string was empty the currently selected platform will
|
|
// determine the architecture.
|
|
const ArchSpec arch(triple_str);
|
|
if (!triple_str.empty() && !arch.IsValid()) {
|
|
error.SetErrorStringWithFormat("invalid triple '%s'",
|
|
triple_str.str().c_str());
|
|
return error;
|
|
}
|
|
|
|
ArchSpec platform_arch(arch);
|
|
|
|
// Create a new platform if a platform was specified in the platform options
|
|
// and doesn't match the selected platform.
|
|
if (platform_options && platform_options->PlatformWasSpecified() &&
|
|
!platform_options->PlatformMatches(platform_sp)) {
|
|
const bool select_platform = true;
|
|
platform_sp = platform_options->CreatePlatformWithOptions(
|
|
debugger.GetCommandInterpreter(), arch, select_platform, error,
|
|
platform_arch);
|
|
if (!platform_sp)
|
|
return error;
|
|
}
|
|
|
|
bool prefer_platform_arch = false;
|
|
auto update_platform_arch = [&](const ArchSpec &module_arch) {
|
|
// If the OS or vendor weren't specified, then adopt the module's
|
|
// architecture so that the platform matching can be more accurate.
|
|
if (!platform_arch.TripleOSWasSpecified() ||
|
|
!platform_arch.TripleVendorWasSpecified()) {
|
|
prefer_platform_arch = true;
|
|
platform_arch = module_arch;
|
|
}
|
|
};
|
|
|
|
if (!user_exe_path.empty()) {
|
|
ModuleSpec module_spec(FileSpec(user_exe_path, FileSpec::Style::native));
|
|
FileSystem::Instance().Resolve(module_spec.GetFileSpec());
|
|
// Resolve the executable in case we are given a path to a application
|
|
// bundle like a .app bundle on MacOSX.
|
|
Host::ResolveExecutableInBundle(module_spec.GetFileSpec());
|
|
|
|
lldb::offset_t file_offset = 0;
|
|
lldb::offset_t file_size = 0;
|
|
ModuleSpecList module_specs;
|
|
const size_t num_specs = ObjectFile::GetModuleSpecifications(
|
|
module_spec.GetFileSpec(), file_offset, file_size, module_specs);
|
|
|
|
if (num_specs > 0) {
|
|
ModuleSpec matching_module_spec;
|
|
|
|
if (num_specs == 1) {
|
|
if (module_specs.GetModuleSpecAtIndex(0, matching_module_spec)) {
|
|
if (platform_arch.IsValid()) {
|
|
if (platform_arch.IsCompatibleMatch(
|
|
matching_module_spec.GetArchitecture())) {
|
|
// If the OS or vendor weren't specified, then adopt the module's
|
|
// architecture so that the platform matching can be more
|
|
// accurate.
|
|
update_platform_arch(matching_module_spec.GetArchitecture());
|
|
} else {
|
|
StreamString platform_arch_strm;
|
|
StreamString module_arch_strm;
|
|
|
|
platform_arch.DumpTriple(platform_arch_strm.AsRawOstream());
|
|
matching_module_spec.GetArchitecture().DumpTriple(
|
|
module_arch_strm.AsRawOstream());
|
|
error.SetErrorStringWithFormat(
|
|
"the specified architecture '%s' is not compatible with '%s' "
|
|
"in '%s'",
|
|
platform_arch_strm.GetData(), module_arch_strm.GetData(),
|
|
module_spec.GetFileSpec().GetPath().c_str());
|
|
return error;
|
|
}
|
|
} else {
|
|
// Only one arch and none was specified.
|
|
prefer_platform_arch = true;
|
|
platform_arch = matching_module_spec.GetArchitecture();
|
|
}
|
|
}
|
|
} else if (arch.IsValid()) {
|
|
// Fat binary. A (valid) architecture was specified.
|
|
module_spec.GetArchitecture() = arch;
|
|
if (module_specs.FindMatchingModuleSpec(module_spec,
|
|
matching_module_spec))
|
|
update_platform_arch(matching_module_spec.GetArchitecture());
|
|
} else {
|
|
// Fat binary. No architecture specified, check if there is
|
|
// only one platform for all of the architectures.
|
|
PlatformSP host_platform_sp = Platform::GetHostPlatform();
|
|
std::vector<PlatformSP> platforms;
|
|
for (size_t i = 0; i < num_specs; ++i) {
|
|
ModuleSpec module_spec;
|
|
if (module_specs.GetModuleSpecAtIndex(i, module_spec)) {
|
|
// First consider the platform specified by the user, if any, and
|
|
// the selected platform otherwise.
|
|
if (platform_sp) {
|
|
if (platform_sp->IsCompatibleArchitecture(
|
|
module_spec.GetArchitecture(), false, nullptr)) {
|
|
platforms.push_back(platform_sp);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Now consider the host platform if it is different from the
|
|
// specified/selected platform.
|
|
if (host_platform_sp &&
|
|
(!platform_sp ||
|
|
host_platform_sp->GetName() != platform_sp->GetName())) {
|
|
if (host_platform_sp->IsCompatibleArchitecture(
|
|
module_spec.GetArchitecture(), false, nullptr)) {
|
|
platforms.push_back(host_platform_sp);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Finally find a platform that matches the architecture in the
|
|
// executable file.
|
|
PlatformSP fallback_platform_sp(
|
|
Platform::GetPlatformForArchitecture(
|
|
module_spec.GetArchitecture(), nullptr));
|
|
if (fallback_platform_sp) {
|
|
platforms.push_back(fallback_platform_sp);
|
|
}
|
|
}
|
|
}
|
|
|
|
Platform *platform_ptr = nullptr;
|
|
bool more_than_one_platforms = false;
|
|
for (const auto &the_platform_sp : platforms) {
|
|
if (platform_ptr) {
|
|
if (platform_ptr->GetName() != the_platform_sp->GetName()) {
|
|
more_than_one_platforms = true;
|
|
platform_ptr = nullptr;
|
|
break;
|
|
}
|
|
} else {
|
|
platform_ptr = the_platform_sp.get();
|
|
}
|
|
}
|
|
|
|
if (platform_ptr) {
|
|
// All platforms for all modules in the executable match, so we can
|
|
// select this platform.
|
|
platform_sp = platforms.front();
|
|
} else if (!more_than_one_platforms) {
|
|
// No platforms claim to support this file.
|
|
error.SetErrorString("no matching platforms found for this file");
|
|
return error;
|
|
} else {
|
|
// More than one platform claims to support this file.
|
|
StreamString error_strm;
|
|
std::set<Platform *> platform_set;
|
|
error_strm.Printf(
|
|
"more than one platform supports this executable (");
|
|
for (const auto &the_platform_sp : platforms) {
|
|
if (platform_set.find(the_platform_sp.get()) ==
|
|
platform_set.end()) {
|
|
if (!platform_set.empty())
|
|
error_strm.PutCString(", ");
|
|
error_strm.PutCString(the_platform_sp->GetName().GetCString());
|
|
platform_set.insert(the_platform_sp.get());
|
|
}
|
|
}
|
|
error_strm.Printf("), specify an architecture to disambiguate");
|
|
error.SetErrorString(error_strm.GetString());
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have a valid architecture, make sure the current platform is
|
|
// compatible with that architecture.
|
|
if (!prefer_platform_arch && arch.IsValid()) {
|
|
if (!platform_sp->IsCompatibleArchitecture(arch, false, nullptr)) {
|
|
platform_sp = Platform::GetPlatformForArchitecture(arch, &platform_arch);
|
|
if (platform_sp)
|
|
debugger.GetPlatformList().SetSelectedPlatform(platform_sp);
|
|
}
|
|
} else if (platform_arch.IsValid()) {
|
|
// If "arch" isn't valid, yet "platform_arch" is, it means we have an
|
|
// executable file with a single architecture which should be used.
|
|
ArchSpec fixed_platform_arch;
|
|
if (!platform_sp->IsCompatibleArchitecture(platform_arch, false, nullptr)) {
|
|
platform_sp = Platform::GetPlatformForArchitecture(platform_arch,
|
|
&fixed_platform_arch);
|
|
if (platform_sp)
|
|
debugger.GetPlatformList().SetSelectedPlatform(platform_sp);
|
|
}
|
|
}
|
|
|
|
if (!platform_arch.IsValid())
|
|
platform_arch = arch;
|
|
|
|
return TargetList::CreateTargetInternal(debugger, user_exe_path,
|
|
platform_arch, load_dependent_files,
|
|
platform_sp, target_sp);
|
|
}
|
|
|
|
Status TargetList::CreateTargetInternal(Debugger &debugger,
|
|
llvm::StringRef user_exe_path,
|
|
const ArchSpec &specified_arch,
|
|
LoadDependentFiles load_dependent_files,
|
|
lldb::PlatformSP &platform_sp,
|
|
lldb::TargetSP &target_sp) {
|
|
static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
|
|
Timer scoped_timer(
|
|
func_cat, "TargetList::CreateTarget (file = '%s', arch = '%s')",
|
|
user_exe_path.str().c_str(), specified_arch.GetArchitectureName());
|
|
Status error;
|
|
const bool is_dummy_target = false;
|
|
|
|
ArchSpec arch(specified_arch);
|
|
|
|
if (arch.IsValid()) {
|
|
if (!platform_sp ||
|
|
!platform_sp->IsCompatibleArchitecture(arch, false, nullptr))
|
|
platform_sp = Platform::GetPlatformForArchitecture(specified_arch, &arch);
|
|
}
|
|
|
|
if (!platform_sp)
|
|
platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
|
|
|
|
if (!arch.IsValid())
|
|
arch = specified_arch;
|
|
|
|
FileSpec file(user_exe_path);
|
|
if (!FileSystem::Instance().Exists(file) && user_exe_path.startswith("~")) {
|
|
// we want to expand the tilde but we don't want to resolve any symbolic
|
|
// links so we can't use the FileSpec constructor's resolve flag
|
|
llvm::SmallString<64> unglobbed_path;
|
|
StandardTildeExpressionResolver Resolver;
|
|
Resolver.ResolveFullPath(user_exe_path, unglobbed_path);
|
|
|
|
if (unglobbed_path.empty())
|
|
file = FileSpec(user_exe_path);
|
|
else
|
|
file = FileSpec(unglobbed_path.c_str());
|
|
}
|
|
|
|
bool user_exe_path_is_bundle = false;
|
|
char resolved_bundle_exe_path[PATH_MAX];
|
|
resolved_bundle_exe_path[0] = '\0';
|
|
if (file) {
|
|
if (FileSystem::Instance().IsDirectory(file))
|
|
user_exe_path_is_bundle = true;
|
|
|
|
if (file.IsRelative() && !user_exe_path.empty()) {
|
|
llvm::SmallString<64> cwd;
|
|
if (! llvm::sys::fs::current_path(cwd)) {
|
|
FileSpec cwd_file(cwd.c_str());
|
|
cwd_file.AppendPathComponent(file);
|
|
if (FileSystem::Instance().Exists(cwd_file))
|
|
file = cwd_file;
|
|
}
|
|
}
|
|
|
|
ModuleSP exe_module_sp;
|
|
if (platform_sp) {
|
|
FileSpecList executable_search_paths(
|
|
Target::GetDefaultExecutableSearchPaths());
|
|
ModuleSpec module_spec(file, arch);
|
|
error = platform_sp->ResolveExecutable(module_spec, exe_module_sp,
|
|
executable_search_paths.GetSize()
|
|
? &executable_search_paths
|
|
: nullptr);
|
|
}
|
|
|
|
if (error.Success() && exe_module_sp) {
|
|
if (exe_module_sp->GetObjectFile() == nullptr) {
|
|
if (arch.IsValid()) {
|
|
error.SetErrorStringWithFormat(
|
|
"\"%s\" doesn't contain architecture %s", file.GetPath().c_str(),
|
|
arch.GetArchitectureName());
|
|
} else {
|
|
error.SetErrorStringWithFormat("unsupported file type \"%s\"",
|
|
file.GetPath().c_str());
|
|
}
|
|
return error;
|
|
}
|
|
target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target));
|
|
target_sp->SetExecutableModule(exe_module_sp, load_dependent_files);
|
|
if (user_exe_path_is_bundle)
|
|
exe_module_sp->GetFileSpec().GetPath(resolved_bundle_exe_path,
|
|
sizeof(resolved_bundle_exe_path));
|
|
if (target_sp->GetPreloadSymbols())
|
|
exe_module_sp->PreloadSymbols();
|
|
}
|
|
} else {
|
|
// No file was specified, just create an empty target with any arch if a
|
|
// valid arch was specified
|
|
target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target));
|
|
}
|
|
|
|
if (!target_sp)
|
|
return error;
|
|
|
|
// Set argv0 with what the user typed, unless the user specified a
|
|
// directory. If the user specified a directory, then it is probably a
|
|
// bundle that was resolved and we need to use the resolved bundle path
|
|
if (!user_exe_path.empty()) {
|
|
// Use exactly what the user typed as the first argument when we exec or
|
|
// posix_spawn
|
|
if (user_exe_path_is_bundle && resolved_bundle_exe_path[0]) {
|
|
target_sp->SetArg0(resolved_bundle_exe_path);
|
|
} else {
|
|
// Use resolved path
|
|
target_sp->SetArg0(file.GetPath().c_str());
|
|
}
|
|
}
|
|
if (file.GetDirectory()) {
|
|
FileSpec file_dir;
|
|
file_dir.GetDirectory() = file.GetDirectory();
|
|
target_sp->AppendExecutableSearchPaths(file_dir);
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
m_selected_target_idx = m_target_list.size();
|
|
m_target_list.push_back(target_sp);
|
|
// Now prime this from the dummy target:
|
|
target_sp->PrimeFromDummyTarget(debugger.GetDummyTarget());
|
|
|
|
return error;
|
|
}
|
|
|
|
bool TargetList::DeleteTarget(TargetSP &target_sp) {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
collection::iterator pos, end = m_target_list.end();
|
|
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
if (pos->get() == target_sp.get()) {
|
|
m_target_list.erase(pos);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TargetSP TargetList::FindTargetWithExecutableAndArchitecture(
|
|
const FileSpec &exe_file_spec, const ArchSpec *exe_arch_ptr) const {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
TargetSP target_sp;
|
|
collection::const_iterator pos, end = m_target_list.end();
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
Module *exe_module = (*pos)->GetExecutableModulePointer();
|
|
|
|
if (exe_module) {
|
|
if (FileSpec::Match(exe_file_spec, exe_module->GetFileSpec())) {
|
|
if (exe_arch_ptr) {
|
|
if (!exe_arch_ptr->IsCompatibleMatch(exe_module->GetArchitecture()))
|
|
continue;
|
|
}
|
|
target_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
TargetSP TargetList::FindTargetWithProcessID(lldb::pid_t pid) const {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
TargetSP target_sp;
|
|
collection::const_iterator pos, end = m_target_list.end();
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
Process *process = (*pos)->GetProcessSP().get();
|
|
if (process && process->GetID() == pid) {
|
|
target_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
TargetSP TargetList::FindTargetWithProcess(Process *process) const {
|
|
TargetSP target_sp;
|
|
if (process) {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
collection::const_iterator pos, end = m_target_list.end();
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
if (process == (*pos)->GetProcessSP().get()) {
|
|
target_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
TargetSP TargetList::GetTargetSP(Target *target) const {
|
|
TargetSP target_sp;
|
|
if (target) {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
collection::const_iterator pos, end = m_target_list.end();
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
if (target == (*pos).get()) {
|
|
target_sp = *pos;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return target_sp;
|
|
}
|
|
|
|
uint32_t TargetList::SendAsyncInterrupt(lldb::pid_t pid) {
|
|
uint32_t num_async_interrupts_sent = 0;
|
|
|
|
if (pid != LLDB_INVALID_PROCESS_ID) {
|
|
TargetSP target_sp(FindTargetWithProcessID(pid));
|
|
if (target_sp) {
|
|
Process *process = target_sp->GetProcessSP().get();
|
|
if (process) {
|
|
process->SendAsyncInterrupt();
|
|
++num_async_interrupts_sent;
|
|
}
|
|
}
|
|
} else {
|
|
// We don't have a valid pid to broadcast to, so broadcast to the target
|
|
// list's async broadcaster...
|
|
BroadcastEvent(Process::eBroadcastBitInterrupt, nullptr);
|
|
}
|
|
|
|
return num_async_interrupts_sent;
|
|
}
|
|
|
|
uint32_t TargetList::SignalIfRunning(lldb::pid_t pid, int signo) {
|
|
uint32_t num_signals_sent = 0;
|
|
Process *process = nullptr;
|
|
if (pid == LLDB_INVALID_PROCESS_ID) {
|
|
// Signal all processes with signal
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
collection::iterator pos, end = m_target_list.end();
|
|
for (pos = m_target_list.begin(); pos != end; ++pos) {
|
|
process = (*pos)->GetProcessSP().get();
|
|
if (process) {
|
|
if (process->IsAlive()) {
|
|
++num_signals_sent;
|
|
process->Signal(signo);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Signal a specific process with signal
|
|
TargetSP target_sp(FindTargetWithProcessID(pid));
|
|
if (target_sp) {
|
|
process = target_sp->GetProcessSP().get();
|
|
if (process) {
|
|
if (process->IsAlive()) {
|
|
++num_signals_sent;
|
|
process->Signal(signo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return num_signals_sent;
|
|
}
|
|
|
|
int TargetList::GetNumTargets() const {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
return m_target_list.size();
|
|
}
|
|
|
|
lldb::TargetSP TargetList::GetTargetAtIndex(uint32_t idx) const {
|
|
TargetSP target_sp;
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
if (idx < m_target_list.size())
|
|
target_sp = m_target_list[idx];
|
|
return target_sp;
|
|
}
|
|
|
|
uint32_t TargetList::GetIndexOfTarget(lldb::TargetSP target_sp) const {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
size_t num_targets = m_target_list.size();
|
|
for (size_t idx = 0; idx < num_targets; idx++) {
|
|
if (target_sp == m_target_list[idx])
|
|
return idx;
|
|
}
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
uint32_t TargetList::SetSelectedTarget(Target *target) {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
collection::const_iterator pos, begin = m_target_list.begin(),
|
|
end = m_target_list.end();
|
|
for (pos = begin; pos != end; ++pos) {
|
|
if (pos->get() == target) {
|
|
m_selected_target_idx = std::distance(begin, pos);
|
|
return m_selected_target_idx;
|
|
}
|
|
}
|
|
m_selected_target_idx = 0;
|
|
return m_selected_target_idx;
|
|
}
|
|
|
|
lldb::TargetSP TargetList::GetSelectedTarget() {
|
|
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
|
|
if (m_selected_target_idx >= m_target_list.size())
|
|
m_selected_target_idx = 0;
|
|
return GetTargetAtIndex(m_selected_target_idx);
|
|
}
|