aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Target/Process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lldb/source/Target/Process.cpp')
-rw-r--r--lldb/source/Target/Process.cpp386
1 files changed, 291 insertions, 95 deletions
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 05ddbbd146a2..aa3b04c43cc5 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -21,7 +21,6 @@
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
-#include "lldb/Core/StreamFile.h"
#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/DynamicCheckerFunctions.h"
#include "lldb/Expression/UserExpression.h"
@@ -90,7 +89,7 @@ using namespace std::chrono;
class ProcessOptionValueProperties
: public Cloneable<ProcessOptionValueProperties, OptionValueProperties> {
public:
- ProcessOptionValueProperties(ConstString name) : Cloneable(name) {}
+ ProcessOptionValueProperties(llvm::StringRef name) : Cloneable(name) {}
const Property *
GetPropertyAtIndex(size_t idx,
@@ -147,8 +146,7 @@ class ProcessExperimentalOptionValueProperties
OptionValueProperties> {
public:
ProcessExperimentalOptionValueProperties()
- : Cloneable(
- ConstString(Properties::GetExperimentalSettingsName())) {}
+ : Cloneable(Properties::GetExperimentalSettingsName()) {}
};
ProcessExperimentalProperties::ProcessExperimentalProperties()
@@ -163,8 +161,7 @@ ProcessProperties::ProcessProperties(lldb_private::Process *process)
{
if (process == nullptr) {
// Global process properties, set them up one time
- m_collection_sp =
- std::make_shared<ProcessOptionValueProperties>(ConstString("process"));
+ m_collection_sp = std::make_shared<ProcessOptionValueProperties>("process");
m_collection_sp->Initialize(g_process_properties);
m_collection_sp->AppendProperty(
"thread", "Settings specific to threads.", true,
@@ -439,7 +436,7 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
m_exit_status_mutex(), m_thread_mutex(), m_thread_list_real(this),
m_thread_list(this), m_thread_plans(*this), m_extended_thread_list(this),
m_extended_thread_stop_id(0), m_queue_list(this), m_queue_list_stop_id(0),
- m_notifications(), m_image_tokens(), m_listener_sp(listener_sp),
+ m_watchpoint_resource_list(), m_notifications(), m_image_tokens(),
m_breakpoint_site_list(), m_dynamic_checkers_up(),
m_unix_signals_sp(unix_signals_sp), m_abi_sp(), m_process_input_reader(),
m_stdio_communication("process.stdio"), m_stdio_communication_mutex(),
@@ -448,7 +445,7 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
m_memory_cache(*this), m_allocated_memory_cache(*this),
m_should_detach(false), m_next_event_action_up(), m_public_run_lock(),
m_private_run_lock(), m_currently_handling_do_on_removals(false),
- m_resume_requested(false), m_finalizing(false),
+ m_resume_requested(false), m_finalizing(false), m_destructing(false),
m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false),
m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false),
m_can_interpret_function_calls(false), m_run_thread_plan_lock(),
@@ -475,10 +472,9 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
m_private_state_control_broadcaster.SetEventName(
eBroadcastInternalStateControlResume, "control-resume");
- m_listener_sp->StartListeningForEvents(
- this, eBroadcastBitStateChanged | eBroadcastBitInterrupt |
- eBroadcastBitSTDOUT | eBroadcastBitSTDERR |
- eBroadcastBitProfileData | eBroadcastBitStructuredData);
+ // The listener passed into process creation is the primary listener:
+ // It always listens for all the event bits for Process:
+ SetPrimaryListener(listener_sp);
m_private_state_listener_sp->StartListeningForEvents(
&m_private_state_broadcaster,
@@ -522,9 +518,11 @@ ProcessProperties &Process::GetGlobalProperties() {
return *g_settings_ptr;
}
-void Process::Finalize() {
+void Process::Finalize(bool destructing) {
if (m_finalizing.exchange(true))
return;
+ if (destructing)
+ m_destructing.exchange(true);
// Destroy the process. This will call the virtual function DoDestroy under
// the hood, giving our derived class a chance to do the ncessary tear down.
@@ -551,6 +549,7 @@ void Process::Finalize() {
m_extended_thread_list.Destroy();
m_queue_list.Clear();
m_queue_list_stop_id = 0;
+ m_watchpoint_resource_list.Clear();
std::vector<Notifications> empty_notifications;
m_notifications.swap(empty_notifications);
m_image_tokens.clear();
@@ -565,15 +564,6 @@ void Process::Finalize() {
// Clear the last natural stop ID since it has a strong reference to this
// process
m_mod_id.SetStopEventForLastNaturalStopID(EventSP());
- //#ifdef LLDB_CONFIGURATION_DEBUG
- // StreamFile s(stdout, false);
- // EventSP event_sp;
- // while (m_private_state_listener_sp->GetNextEvent(event_sp))
- // {
- // event_sp->Dump (&s);
- // s.EOL();
- // }
- //#endif
// We have to be very careful here as the m_private_state_listener might
// contain events that have ProcessSP values in them which can keep this
// process around forever. These events need to be cleared out.
@@ -628,7 +618,7 @@ void Process::SynchronouslyNotifyStateChanged(StateType state) {
StateType Process::GetNextEvent(EventSP &event_sp) {
StateType state = eStateInvalid;
- if (m_listener_sp->GetEventForBroadcaster(this, event_sp,
+ if (GetPrimaryListener()->GetEventForBroadcaster(this, event_sp,
std::chrono::seconds(0)) &&
event_sp)
state = Process::ProcessEventData::GetStateFromEvent(event_sp.get());
@@ -640,7 +630,7 @@ void Process::SyncIOHandler(uint32_t iohandler_id,
const Timeout<std::micro> &timeout) {
// don't sync (potentially context switch) in case where there is no process
// IO
- if (!m_process_input_reader)
+ if (!ProcessIOHandlerExists())
return;
auto Result = m_iohandler_sync.WaitForValueNotEqualTo(iohandler_id, timeout);
@@ -976,7 +966,7 @@ StateType Process::GetStateChangedEvents(EventSP &event_sp,
ListenerSP listener_sp = hijack_listener_sp;
if (!listener_sp)
- listener_sp = m_listener_sp;
+ listener_sp = GetPrimaryListener();
StateType state = eStateInvalid;
if (listener_sp->GetEventForBroadcasterWithType(
@@ -998,7 +988,7 @@ Event *Process::PeekAtStateChangedEvents() {
LLDB_LOGF(log, "Process::%s...", __FUNCTION__);
Event *event_ptr;
- event_ptr = m_listener_sp->PeekAtNextEventForBroadcasterWithType(
+ event_ptr = GetPrimaryListener()->PeekAtNextEventForBroadcasterWithType(
this, eBroadcastBitStateChanged);
if (log) {
if (event_ptr) {
@@ -1063,27 +1053,27 @@ const char *Process::GetExitDescription() {
return nullptr;
}
-bool Process::SetExitStatus(int status, const char *cstr) {
+bool Process::SetExitStatus(int status, llvm::StringRef exit_string) {
// Use a mutex to protect setting the exit status.
std::lock_guard<std::mutex> guard(m_exit_status_mutex);
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
- LLDB_LOGF(log, "(plugin = %s status=%i (0x%8.8x), description=%s%s%s)",
- GetPluginName().data(), status, status, cstr ? "\"" : "",
- cstr ? cstr : "NULL", cstr ? "\"" : "");
+ LLDB_LOG(log, "(plugin = {0} status = {1} ({1:x8}), description=\"{2}\")",
+ GetPluginName(), status, exit_string);
// We were already in the exited state
if (m_private_state.GetValue() == eStateExited) {
- LLDB_LOGF(log,
- "(plugin = %s) ignoring exit status because state was already set "
- "to eStateExited",
- GetPluginName().data());
+ LLDB_LOG(
+ log,
+ "(plugin = {0}) ignoring exit status because state was already set "
+ "to eStateExited",
+ GetPluginName());
return false;
}
m_exit_status = status;
- if (cstr)
- m_exit_string = cstr;
+ if (!exit_string.empty())
+ m_exit_string = exit_string.str();
else
m_exit_string.clear();
@@ -1135,11 +1125,9 @@ bool Process::SetProcessExitStatus(
if (target_sp) {
ProcessSP process_sp(target_sp->GetProcessSP());
if (process_sp) {
- const char *signal_cstr = nullptr;
- if (signo)
- signal_cstr = process_sp->GetUnixSignals()->GetSignalAsCString(signo);
-
- process_sp->SetExitStatus(exit_status, signal_cstr);
+ llvm::StringRef signal_str =
+ process_sp->GetUnixSignals()->GetSignalAsStringRef(signo);
+ process_sp->SetExitStatus(exit_status, signal_str);
}
}
return true;
@@ -1429,7 +1417,13 @@ bool Process::StateChangedIsHijackedForSynchronousResume() {
StateType Process::GetPrivateState() { return m_private_state.GetValue(); }
void Process::SetPrivateState(StateType new_state) {
- if (m_finalizing)
+ // Use m_destructing not m_finalizing here. If we are finalizing a process
+ // that we haven't started tearing down, we'd like to be able to nicely
+ // detach if asked, but that requires the event system be live. That will
+ // not be true for an in-the-middle-of-being-destructed Process, since the
+ // event system relies on Process::shared_from_this, which may have already
+ // been destroyed.
+ if (m_destructing)
return;
Log *log(GetLog(LLDBLog::State | LLDBLog::Process | LLDBLog::Unwind));
@@ -1578,11 +1572,12 @@ void Process::SetDynamicCheckers(DynamicCheckerFunctions *dynamic_checkers) {
m_dynamic_checkers_up.reset(dynamic_checkers);
}
-BreakpointSiteList &Process::GetBreakpointSiteList() {
+StopPointSiteList<BreakpointSite> &Process::GetBreakpointSiteList() {
return m_breakpoint_site_list;
}
-const BreakpointSiteList &Process::GetBreakpointSiteList() const {
+const StopPointSiteList<BreakpointSite> &
+Process::GetBreakpointSiteList() const {
return m_breakpoint_site_list;
}
@@ -1630,7 +1625,7 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
}
lldb::break_id_t
-Process::CreateBreakpointSite(const BreakpointLocationSP &owner,
+Process::CreateBreakpointSite(const BreakpointLocationSP &constituent,
bool use_hardware) {
addr_t load_addr = LLDB_INVALID_ADDRESS;
@@ -1657,10 +1652,10 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &owner,
// Reset the IsIndirect flag here, in case the location changes from pointing
// to a indirect symbol to a regular symbol.
- owner->SetIsIndirect(false);
+ constituent->SetIsIndirect(false);
- if (owner->ShouldResolveIndirectFunctions()) {
- Symbol *symbol = owner->GetAddress().CalculateSymbolContextSymbol();
+ if (constituent->ShouldResolveIndirectFunctions()) {
+ Symbol *symbol = constituent->GetAddress().CalculateSymbolContextSymbol();
if (symbol && symbol->IsIndirect()) {
Status error;
Address symbol_address = symbol->GetAddress();
@@ -1670,37 +1665,37 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &owner,
"warning: failed to resolve indirect function at 0x%" PRIx64
" for breakpoint %i.%i: %s\n",
symbol->GetLoadAddress(&GetTarget()),
- owner->GetBreakpoint().GetID(), owner->GetID(),
+ constituent->GetBreakpoint().GetID(), constituent->GetID(),
error.AsCString() ? error.AsCString() : "unknown error");
return LLDB_INVALID_BREAK_ID;
}
Address resolved_address(load_addr);
load_addr = resolved_address.GetOpcodeLoadAddress(&GetTarget());
- owner->SetIsIndirect(true);
+ constituent->SetIsIndirect(true);
} else
- load_addr = owner->GetAddress().GetOpcodeLoadAddress(&GetTarget());
+ load_addr = constituent->GetAddress().GetOpcodeLoadAddress(&GetTarget());
} else
- load_addr = owner->GetAddress().GetOpcodeLoadAddress(&GetTarget());
+ load_addr = constituent->GetAddress().GetOpcodeLoadAddress(&GetTarget());
if (load_addr != LLDB_INVALID_ADDRESS) {
BreakpointSiteSP bp_site_sp;
- // Look up this breakpoint site. If it exists, then add this new owner,
- // otherwise create a new breakpoint site and add it.
+ // Look up this breakpoint site. If it exists, then add this new
+ // constituent, otherwise create a new breakpoint site and add it.
bp_site_sp = m_breakpoint_site_list.FindByAddress(load_addr);
if (bp_site_sp) {
- bp_site_sp->AddOwner(owner);
- owner->SetBreakpointSite(bp_site_sp);
+ bp_site_sp->AddConstituent(constituent);
+ constituent->SetBreakpointSite(bp_site_sp);
return bp_site_sp->GetID();
} else {
- bp_site_sp.reset(new BreakpointSite(&m_breakpoint_site_list, owner,
- load_addr, use_hardware));
+ bp_site_sp.reset(
+ new BreakpointSite(constituent, load_addr, use_hardware));
if (bp_site_sp) {
Status error = EnableBreakpointSite(bp_site_sp.get());
if (error.Success()) {
- owner->SetBreakpointSite(bp_site_sp);
+ constituent->SetBreakpointSite(bp_site_sp);
return m_breakpoint_site_list.Add(bp_site_sp);
} else {
if (show_error || use_hardware) {
@@ -1708,7 +1703,8 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &owner,
GetTarget().GetDebugger().GetErrorStream().Printf(
"warning: failed to set breakpoint site at 0x%" PRIx64
" for breakpoint %i.%i: %s\n",
- load_addr, owner->GetBreakpoint().GetID(), owner->GetID(),
+ load_addr, constituent->GetBreakpoint().GetID(),
+ constituent->GetID(),
error.AsCString() ? error.AsCString() : "unknown error");
}
}
@@ -1719,11 +1715,12 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &owner,
return LLDB_INVALID_BREAK_ID;
}
-void Process::RemoveOwnerFromBreakpointSite(lldb::user_id_t owner_id,
- lldb::user_id_t owner_loc_id,
- BreakpointSiteSP &bp_site_sp) {
- uint32_t num_owners = bp_site_sp->RemoveOwner(owner_id, owner_loc_id);
- if (num_owners == 0) {
+void Process::RemoveConstituentFromBreakpointSite(
+ lldb::user_id_t constituent_id, lldb::user_id_t constituent_loc_id,
+ BreakpointSiteSP &bp_site_sp) {
+ uint32_t num_constituents =
+ bp_site_sp->RemoveConstituent(constituent_id, constituent_loc_id);
+ if (num_constituents == 0) {
// Don't try to disable the site if we don't have a live process anymore.
if (IsAlive())
DisableBreakpointSite(bp_site_sp.get());
@@ -1734,7 +1731,7 @@ void Process::RemoveOwnerFromBreakpointSite(lldb::user_id_t owner_id,
size_t Process::RemoveBreakpointOpcodesFromBuffer(addr_t bp_addr, size_t size,
uint8_t *buf) const {
size_t bytes_removed = 0;
- BreakpointSiteList bp_sites_in_range;
+ StopPointSiteList<BreakpointSite> bp_sites_in_range;
if (m_breakpoint_site_list.FindInRange(bp_addr, bp_addr + size,
bp_sites_in_range)) {
@@ -2155,7 +2152,7 @@ size_t Process::WriteMemory(addr_t addr, const void *buf, size_t size,
// (enabled software breakpoints) any software traps (breakpoints) that we
// may have placed in our tasks memory.
- BreakpointSiteList bp_sites_in_range;
+ StopPointSiteList<BreakpointSite> bp_sites_in_range;
if (!m_breakpoint_site_list.FindInRange(addr, addr + size, bp_sites_in_range))
return WriteMemoryPrivate(addr, buf, size, error);
@@ -2419,26 +2416,17 @@ bool Process::GetLoadAddressPermissions(lldb::addr_t load_addr,
range_info.GetExecutable() == MemoryRegionInfo::eDontKnow) {
return false;
}
-
- if (range_info.GetReadable() == MemoryRegionInfo::eYes)
- permissions |= lldb::ePermissionsReadable;
-
- if (range_info.GetWritable() == MemoryRegionInfo::eYes)
- permissions |= lldb::ePermissionsWritable;
-
- if (range_info.GetExecutable() == MemoryRegionInfo::eYes)
- permissions |= lldb::ePermissionsExecutable;
-
+ permissions = range_info.GetLLDBPermissions();
return true;
}
-Status Process::EnableWatchpoint(Watchpoint *watchpoint, bool notify) {
+Status Process::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
Status error;
error.SetErrorString("watchpoints are not supported");
return error;
}
-Status Process::DisableWatchpoint(Watchpoint *watchpoint, bool notify) {
+Status Process::DisableWatchpoint(WatchpointSP wp_sp, bool notify) {
Status error;
error.SetErrorString("watchpoints are not supported");
return error;
@@ -2515,7 +2503,11 @@ Status Process::LaunchPrivate(ProcessLaunchInfo &launch_info, StateType &state,
m_jit_loaders_up.reset();
m_system_runtime_up.reset();
m_os_up.reset();
- m_process_input_reader.reset();
+
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ m_process_input_reader.reset();
+ }
Module *exe_module = GetTarget().GetExecutableModulePointer();
@@ -2813,7 +2805,10 @@ Status Process::WillAttachToProcessWithName(const char *process_name,
Status Process::Attach(ProcessAttachInfo &attach_info) {
m_abi_sp.reset();
- m_process_input_reader.reset();
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ m_process_input_reader.reset();
+ }
m_dyld_up.reset();
m_jit_loaders_up.reset();
m_system_runtime_up.reset();
@@ -3064,7 +3059,10 @@ void Process::CompleteAttach() {
Status Process::ConnectRemote(llvm::StringRef remote_url) {
m_abi_sp.reset();
- m_process_input_reader.reset();
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ m_process_input_reader.reset();
+ }
// Find the process and its architecture. Make sure it matches the
// architecture of the current Target, and if not adjust it.
@@ -3170,8 +3168,8 @@ Status Process::Halt(bool clear_thread_plans, bool use_run_lock) {
// Don't hijack and eat the eStateExited as the code that was doing the
// attach will be waiting for this event...
RestoreProcessEvents();
- SetExitStatus(SIGKILL, "Cancelled async attach.");
Destroy(false);
+ SetExitStatus(SIGKILL, "Cancelled async attach.");
return Status();
}
@@ -3352,10 +3350,13 @@ Status Process::DestroyImpl(bool force_kill) {
m_stdio_communication.Disconnect();
m_stdin_forward = false;
- if (m_process_input_reader) {
- m_process_input_reader->SetIsDone(true);
- m_process_input_reader->Cancel();
- m_process_input_reader.reset();
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ if (m_process_input_reader) {
+ m_process_input_reader->SetIsDone(true);
+ m_process_input_reader->Cancel();
+ m_process_input_reader.reset();
+ }
}
// If we exited when we were waiting for a process to stop, then forward
@@ -3854,6 +3855,13 @@ thread_result_t Process::RunPrivateStateThread(bool is_secondary_thread) {
") woke up with an interrupt while attaching - "
"forwarding interrupt.",
__FUNCTION__, static_cast<void *>(this), GetID());
+ // The server may be spinning waiting for a process to appear, in which
+ // case we should tell it to stop doing that. Normally, we don't NEED
+ // to do that because we will next close the communication to the stub
+ // and that will get it to shut down. But there are remote debugging
+ // cases where relying on that side-effect causes the shutdown to be
+ // flakey, so we should send a positive signal to interrupt the wait.
+ Status error = HaltPrivate();
BroadcastEvent(eBroadcastBitInterrupt, nullptr);
} else if (StateIsRunningState(m_last_broadcast_state)) {
LLDB_LOGF(log,
@@ -4114,8 +4122,8 @@ void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) {
if (!still_should_stop && does_anybody_have_an_opinion) {
// We've been asked to continue, so do that here.
SetRestarted(true);
- // Use the public resume method here, since this is just extending a
- // public resume.
+ // Use the private resume method here, since we aren't changing the run
+ // lock state.
process_sp->PrivateResume();
} else {
bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) &&
@@ -4533,20 +4541,25 @@ void Process::SetSTDIOFileDescriptor(int fd) {
m_stdio_communication.StartReadThread();
// Now read thread is set up, set up input reader.
-
- if (!m_process_input_reader)
- m_process_input_reader =
- std::make_shared<IOHandlerProcessSTDIO>(this, fd);
+ {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
+ if (!m_process_input_reader)
+ m_process_input_reader =
+ std::make_shared<IOHandlerProcessSTDIO>(this, fd);
+ }
}
}
bool Process::ProcessIOHandlerIsActive() {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
IOHandlerSP io_handler_sp(m_process_input_reader);
if (io_handler_sp)
return GetTarget().GetDebugger().IsTopIOHandler(io_handler_sp);
return false;
}
+
bool Process::PushProcessIOHandler() {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
IOHandlerSP io_handler_sp(m_process_input_reader);
if (io_handler_sp) {
Log *log = GetLog(LLDBLog::Process);
@@ -4566,6 +4579,7 @@ bool Process::PushProcessIOHandler() {
}
bool Process::PopProcessIOHandler() {
+ std::lock_guard<std::mutex> guard(m_process_input_reader_mutex);
IOHandlerSP io_handler_sp(m_process_input_reader);
if (io_handler_sp)
return GetTarget().GetDebugger().RemoveIOHandler(io_handler_sp);
@@ -5907,12 +5921,12 @@ size_t Process::AddImageToken(lldb::addr_t image_ptr) {
lldb::addr_t Process::GetImagePtrFromToken(size_t token) const {
if (token < m_image_tokens.size())
return m_image_tokens[token];
- return LLDB_INVALID_ADDRESS;
+ return LLDB_INVALID_IMAGE_TOKEN;
}
void Process::ResetImageToken(size_t token) {
if (token < m_image_tokens.size())
- m_image_tokens[token] = LLDB_INVALID_ADDRESS;
+ m_image_tokens[token] = LLDB_INVALID_IMAGE_TOKEN;
}
Address
@@ -6248,3 +6262,185 @@ Status Process::WriteMemoryTags(lldb::addr_t addr, size_t len,
return DoWriteMemoryTags(addr, len, tag_manager->GetAllocationTagType(),
*packed_tags);
}
+
+// Create a CoreFileMemoryRange from a MemoryRegionInfo
+static Process::CoreFileMemoryRange
+CreateCoreFileMemoryRange(const MemoryRegionInfo &region) {
+ const addr_t addr = region.GetRange().GetRangeBase();
+ llvm::AddressRange range(addr, addr + region.GetRange().GetByteSize());
+ return {range, region.GetLLDBPermissions()};
+}
+
+// Add dirty pages to the core file ranges and return true if dirty pages
+// were added. Return false if the dirty page information is not valid or in
+// the region.
+static bool AddDirtyPages(const MemoryRegionInfo &region,
+ Process::CoreFileMemoryRanges &ranges) {
+ const auto &dirty_page_list = region.GetDirtyPageList();
+ if (!dirty_page_list)
+ return false;
+ const uint32_t lldb_permissions = region.GetLLDBPermissions();
+ const addr_t page_size = region.GetPageSize();
+ if (page_size == 0)
+ return false;
+ llvm::AddressRange range(0, 0);
+ for (addr_t page_addr : *dirty_page_list) {
+ if (range.empty()) {
+ // No range yet, initialize the range with the current dirty page.
+ range = llvm::AddressRange(page_addr, page_addr + page_size);
+ } else {
+ if (range.end() == page_addr) {
+ // Combine consective ranges.
+ range = llvm::AddressRange(range.start(), page_addr + page_size);
+ } else {
+ // Add previous contiguous range and init the new range with the
+ // current dirty page.
+ ranges.push_back({range, lldb_permissions});
+ range = llvm::AddressRange(page_addr, page_addr + page_size);
+ }
+ }
+ }
+ // The last range
+ if (!range.empty())
+ ranges.push_back({range, lldb_permissions});
+ return true;
+}
+
+// Given a region, add the region to \a ranges.
+//
+// Only add the region if it isn't empty and if it has some permissions.
+// If \a try_dirty_pages is true, then try to add only the dirty pages for a
+// given region. If the region has dirty page information, only dirty pages
+// will be added to \a ranges, else the entire range will be added to \a
+// ranges.
+static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
+ Process::CoreFileMemoryRanges &ranges) {
+ // Don't add empty ranges or ranges with no permissions.
+ if (region.GetRange().GetByteSize() == 0 || region.GetLLDBPermissions() == 0)
+ return;
+ if (try_dirty_pages && AddDirtyPages(region, ranges))
+ return;
+ ranges.push_back(CreateCoreFileMemoryRange(region));
+}
+
+// Save all memory regions that are not empty or have at least some permissions
+// for a full core file style.
+static void GetCoreFileSaveRangesFull(Process &process,
+ const MemoryRegionInfos &regions,
+ Process::CoreFileMemoryRanges &ranges) {
+
+ // Don't add only dirty pages, add full regions.
+const bool try_dirty_pages = false;
+ for (const auto &region : regions)
+ AddRegion(region, try_dirty_pages, ranges);
+}
+
+// Save only the dirty pages to the core file. Make sure the process has at
+// least some dirty pages, as some OS versions don't support reporting what
+// pages are dirty within an memory region. If no memory regions have dirty
+// page information fall back to saving out all ranges with write permissions.
+static void
+GetCoreFileSaveRangesDirtyOnly(Process &process,
+ const MemoryRegionInfos &regions,
+ Process::CoreFileMemoryRanges &ranges) {
+ // Iterate over the regions and find all dirty pages.
+ bool have_dirty_page_info = false;
+ for (const auto &region : regions) {
+ if (AddDirtyPages(region, ranges))
+ have_dirty_page_info = true;
+ }
+
+ if (!have_dirty_page_info) {
+ // We didn't find support for reporting dirty pages from the process
+ // plug-in so fall back to any region with write access permissions.
+ const bool try_dirty_pages = false;
+ for (const auto &region : regions)
+ if (region.GetWritable() == MemoryRegionInfo::eYes)
+ AddRegion(region, try_dirty_pages, ranges);
+ }
+}
+
+// Save all thread stacks to the core file. Some OS versions support reporting
+// when a memory region is stack related. We check on this information, but we
+// also use the stack pointers of each thread and add those in case the OS
+// doesn't support reporting stack memory. This function also attempts to only
+// emit dirty pages from the stack if the memory regions support reporting
+// dirty regions as this will make the core file smaller. If the process
+// doesn't support dirty regions, then it will fall back to adding the full
+// stack region.
+static void
+GetCoreFileSaveRangesStackOnly(Process &process,
+ const MemoryRegionInfos &regions,
+ Process::CoreFileMemoryRanges &ranges) {
+ // Some platforms support annotating the region information that tell us that
+ // it comes from a thread stack. So look for those regions first.
+
+ // Keep track of which stack regions we have added
+ std::set<addr_t> stack_bases;
+
+ const bool try_dirty_pages = true;
+ for (const auto &region : regions) {
+ if (region.IsStackMemory() == MemoryRegionInfo::eYes) {
+ stack_bases.insert(region.GetRange().GetRangeBase());
+ AddRegion(region, try_dirty_pages, ranges);
+ }
+ }
+
+ // Also check with our threads and get the regions for their stack pointers
+ // and add those regions if not already added above.
+ for (lldb::ThreadSP thread_sp : process.GetThreadList().Threads()) {
+ if (!thread_sp)
+ continue;
+ StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(0);
+ if (!frame_sp)
+ continue;
+ RegisterContextSP reg_ctx_sp = frame_sp->GetRegisterContext();
+ if (!reg_ctx_sp)
+ continue;
+ const addr_t sp = reg_ctx_sp->GetSP();
+ lldb_private::MemoryRegionInfo sp_region;
+ if (process.GetMemoryRegionInfo(sp, sp_region).Success()) {
+ // Only add this region if not already added above. If our stack pointer
+ // is pointing off in the weeds, we will want this range.
+ if (stack_bases.count(sp_region.GetRange().GetRangeBase()) == 0)
+ AddRegion(sp_region, try_dirty_pages, ranges);
+ }
+ }
+}
+
+Status Process::CalculateCoreFileSaveRanges(lldb::SaveCoreStyle core_style,
+ CoreFileMemoryRanges &ranges) {
+ lldb_private::MemoryRegionInfos regions;
+ Status err = GetMemoryRegions(regions);
+ if (err.Fail())
+ return err;
+ if (regions.empty())
+ return Status("failed to get any valid memory regions from the process");
+
+ switch (core_style) {
+ case eSaveCoreUnspecified:
+ err = Status("callers must set the core_style to something other than "
+ "eSaveCoreUnspecified");
+ break;
+
+ case eSaveCoreFull:
+ GetCoreFileSaveRangesFull(*this, regions, ranges);
+ break;
+
+ case eSaveCoreDirtyOnly:
+ GetCoreFileSaveRangesDirtyOnly(*this, regions, ranges);
+ break;
+
+ case eSaveCoreStackOnly:
+ GetCoreFileSaveRangesStackOnly(*this, regions, ranges);
+ break;
+ }
+
+ if (err.Fail())
+ return err;
+
+ if (ranges.empty())
+ return Status("no valid address ranges found for core style");
+
+ return Status(); // Success!
+}