Add a corefile style option to process save-core; skinny corefiles

Add a new feature to process save-core on Darwin systems -- for
lldb to create a user process corefile with only the dirty (modified
memory) pages included.  All of the binaries that were used in the
corefile are assumed to still exist on the system for the duration
of the use of the corefile.  A new --style option to process save-core
is added, so a full corefile can be requested if portability across
systems, or across time, is needed for this corefile.

debugserver can now identify the dirty pages in a memory region
when queried with qMemoryRegionInfo, and the size of vm pages is
given in qHostInfo.

Create a new "all image infos" LC_NOTE for Mach-O which allows us
to describe all of the binaries that were loaded in the process --
load address, UUID, file path, segment load addresses, and optionally
whether code from the binary was executing on any thread.  The old
"read dyld_all_image_infos and then the in-memory Mach-O load
commands to get segment load addresses" no longer works when we
only have dirty memory.

rdar://69670807
Differential Revision: https://reviews.llvm.org/D88387
This commit is contained in:
Jason Molenda 2021-06-20 12:19:50 -07:00
parent 1ae266f452
commit 9ea6dd5cfa
36 changed files with 1217 additions and 65 deletions

View File

@ -46,6 +46,42 @@ public:
const char *
GetName ();
%feature("autodoc", "
GetRegionEnd(SBMemoryRegionInfo self) -> lldb::addr_t
Returns whether this memory region has a list of modified (dirty)
pages available or not. When calling GetNumDirtyPages(), you will
have 0 returned for both \"dirty page list is not known\" and
\"empty dirty page list\" (that is, no modified pages in this
memory region). You must use this method to disambiguate.") HasDirtyMemoryPageList;
bool
HasDirtyMemoryPageList();
%feature("autodoc", "
GetNumDirtyPages(SBMemoryRegionInfo self) -> uint32_t
Return the number of dirty (modified) memory pages in this
memory region, if available. You must use the
SBMemoryRegionInfo::HasDirtyMemoryPageList() method to
determine if a dirty memory list is available; it will depend
on the target system can provide this information.") GetNumDirtyPages;
uint32_t
GetNumDirtyPages();
%feature("autodoc", "
GetDirtyPageAddressAtIndex(SBMemoryRegionInfo self, uint32_t idx) -> lldb::addr_t
Return the address of a modified, or dirty, page of memory.
If the provided index is out of range, or this memory region
does not have dirty page information, LLDB_INVALID_ADDRESS
is returned.") GetDirtyPageAddressAtIndex;
addr_t
GetDirtyPageAddressAtIndex(uint32_t idx);
%feature("autodoc", "
GetPageSize(SBMemoryRegionInfo self) -> int
Return the size of pages in this memory region. 0 will be returned
if this information was unavailable.") GetPageSize();
int
GetPageSize();
bool
operator == (const lldb::SBMemoryRegionInfo &rhs) const;

View File

@ -869,10 +869,14 @@ distribution_id: optional. For linux, specifies distribution id (e.g. ubuntu, fe
osmajor: optional, specifies the major version number of the OS (e.g. for macOS 10.12.2, it would be 10)
osminor: optional, specifies the minor version number of the OS (e.g. for macOS 10.12.2, it would be 12)
ospatch: optional, specifies the patch level number of the OS (e.g. for macOS 10.12.2, it would be 2)
vm-page-size: optional, specifies the target system VM page size, base 10.
Needed for the "dirty-pages:" list in the qMemoryRegionInfo
packet, where a list of dirty pages is sent from the remote
stub. This page size tells lldb how large each dirty page is.
addressing_bits: optional, specifies how many bits in addresses are
significant for addressing, base 10. If bits 38..0
in a 64-bit pointer are significant for addressing,
then the value is 39. This is needed on e.g. Aarch64
then the value is 39. This is needed on e.g. AArch64
v8.3 ABIs that use pointer authentication, so lldb
knows which bits to clear/set to get the actual
addresses.
@ -1174,6 +1178,18 @@ tuples to return are:
// a hex encoded string value that
// contains an error string
dirty-pages:[<hexaddr>][,<hexaddr]; // A list of memory pages within this
// region that are "dirty" -- they have been modified.
// Page addresses are in base16. The size of a page can
// be found from the qHostInfo's page-size key-value.
//
// If the stub supports identifying dirty pages within a
// memory region, this key should always be present for all
// qMemoryRegionInfo replies. This key with no pages
// listed ("dirty-pages:;") indicates no dirty pages in
// this memory region. The *absence* of this key means
// that this stub cannot determine dirty pages.
If the address requested is not in a mapped region (e.g. we've jumped through
a NULL pointer and are at 0x0) currently lldb expects to get back the size
of the unmapped region -- that is, the distance to the next valid region.

View File

@ -73,6 +73,40 @@ public:
/// region. If no name can be determined the returns nullptr.
const char *GetName();
/// Returns whether this memory region has a list of memory pages
/// that have been modified -- that are dirty.
///
/// \return
/// True if the dirty page list is available.
bool HasDirtyMemoryPageList();
/// Returns the number of modified pages -- dirty pages -- in this
/// memory region.
///
/// \return
/// The number of dirty page entries will be returned. If
/// there are no dirty pages in this memory region, 0 will
/// be returned. 0 will also be returned if the dirty page
/// list is not available for this memory region -- you must
/// use HasDirtyMemoryPageList() to check for that.
uint32_t GetNumDirtyPages();
/// Returns the address of a memory page that has been modified in
/// this region.
///
/// \return
/// Returns the address for his dirty page in the list.
/// If this memory region does not have a dirty page list,
/// LLDB_INVALID_ADDRESS is returned.
addr_t GetDirtyPageAddressAtIndex(uint32_t idx);
/// Returns the size of a memory page in this region.
///
/// \return
/// Returns the size of the memory pages in this region,
/// or 0 if this information is unavailable.
int GetPageSize();
bool operator==(const lldb::SBMemoryRegionInfo &rhs) const;
bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const;

View File

@ -191,7 +191,8 @@ public:
GetObjectFileCreateMemoryCallbackForPluginName(ConstString name);
static Status SaveCore(const lldb::ProcessSP &process_sp,
const FileSpec &outfile);
const FileSpec &outfile,
lldb::SaveCoreStyle &core_style);
// ObjectContainer
static bool

View File

@ -666,6 +666,22 @@ public:
/// Creates a plugin-specific call frame info
virtual std::unique_ptr<CallFrameInfo> CreateCallFrameInfo();
/// Load binaries listed in a corefile
///
/// A corefile may have metadata listing binaries that can be loaded,
/// and the offsets at which they were loaded. This method will try
/// to add them to the Target. If any binaries were loaded,
///
/// \param[in] process
/// Process where to load binaries.
///
/// \return
/// Returns true if any binaries were loaded.
virtual bool LoadCoreFileImages(lldb_private::Process &process) {
return false;
}
protected:
// Member variables.
FileSpec m_file;

View File

@ -10,8 +10,11 @@
#ifndef LLDB_TARGET_MEMORYREGIONINFO_H
#define LLDB_TARGET_MEMORYREGIONINFO_H
#include <vector>
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/RangeMap.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/FormatProviders.h"
namespace lldb_private {
@ -32,10 +35,7 @@ public:
RangeType &GetRange() { return m_range; }
void Clear() {
m_range.Clear();
m_read = m_write = m_execute = m_memory_tagged = eDontKnow;
}
void Clear() { *this = MemoryRegionInfo(); }
const RangeType &GetRange() const { return m_range; }
@ -97,11 +97,33 @@ public:
m_write == rhs.m_write && m_execute == rhs.m_execute &&
m_mapped == rhs.m_mapped && m_name == rhs.m_name &&
m_flash == rhs.m_flash && m_blocksize == rhs.m_blocksize &&
m_memory_tagged == rhs.m_memory_tagged;
m_memory_tagged == rhs.m_memory_tagged &&
m_pagesize == rhs.m_pagesize;
}
bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); }
/// Get the target system's VM page size in bytes.
/// \return
/// 0 is returned if this information is unavailable.
int GetPageSize() { return m_pagesize; }
/// Get a vector of target VM pages that are dirty -- that have been
/// modified -- within this memory region. This is an Optional return
/// value; it will only be available if the remote stub was able to
/// detail this.
llvm::Optional<std::vector<lldb::addr_t>> &GetDirtyPageList() {
return m_dirty_pages;
}
void SetPageSize(int pagesize) { m_pagesize = pagesize; }
void SetDirtyPageList(std::vector<lldb::addr_t> pagelist) {
if (m_dirty_pages.hasValue())
m_dirty_pages.getValue().clear();
m_dirty_pages = std::move(pagelist);
}
protected:
RangeType m_range;
OptionalBool m_read = eDontKnow;
@ -112,6 +134,8 @@ protected:
OptionalBool m_flash = eDontKnow;
lldb::offset_t m_blocksize = 0;
OptionalBool m_memory_tagged = eDontKnow;
int m_pagesize = 0;
llvm::Optional<std::vector<lldb::addr_t>> m_dirty_pages;
};
inline bool operator<(const MemoryRegionInfo &lhs,

View File

@ -601,6 +601,7 @@ enum CommandArgumentType {
eArgTypeCommand,
eArgTypeColumnNum,
eArgTypeModuleUUID,
eArgTypeSaveCoreStyle,
eArgTypeLastArg // Always keep this entry as the last entry in this
// enumeration!!
};
@ -1111,6 +1112,14 @@ enum CommandInterpreterResult {
/// Stopped because quit was requested.
eCommandInterpreterResultQuitRequested,
};
// Style of core file to create when calling SaveCore.
enum SaveCoreStyle {
eSaveCoreUnspecified = 0,
eSaveCoreFull = 1,
eSaveCoreDirtyOnly = 2,
};
} // namespace lldb
#endif // LLDB_LLDB_ENUMERATIONS_H

View File

@ -54,7 +54,9 @@ typedef ObjectFile *(*ObjectFileCreateMemoryInstance)(
const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp,
const lldb::ProcessSP &process_sp, lldb::addr_t offset);
typedef bool (*ObjectFileSaveCore)(const lldb::ProcessSP &process_sp,
const FileSpec &outfile, Status &error);
const FileSpec &outfile,
lldb::SaveCoreStyle &core_style,
Status &error);
typedef EmulateInstruction *(*EmulateInstructionCreateInstance)(
const ArchSpec &arch, InstructionType inst_type);
typedef OperatingSystem *(*OperatingSystemCreateInstance)(Process *process,

View File

@ -723,7 +723,8 @@ class GdbRemoteTestCaseBase(Base):
"permissions",
"flags",
"name",
"error"])
"error",
"dirty-pages"])
self.assertIsNotNone(val)
mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", ""))

View File

@ -116,6 +116,42 @@ const char *SBMemoryRegionInfo::GetName() {
return m_opaque_up->GetName().AsCString();
}
bool SBMemoryRegionInfo::HasDirtyMemoryPageList() {
LLDB_RECORD_METHOD_NO_ARGS(bool, SBMemoryRegionInfo, HasDirtyMemoryPageList);
return m_opaque_up->GetDirtyPageList().hasValue();
}
uint32_t SBMemoryRegionInfo::GetNumDirtyPages() {
LLDB_RECORD_METHOD_NO_ARGS(uint32_t, SBMemoryRegionInfo, GetNumDirtyPages);
uint32_t num_dirty_pages = 0;
llvm::Optional<std::vector<addr_t>> dirty_page_list =
m_opaque_up->GetDirtyPageList();
if (dirty_page_list.hasValue())
num_dirty_pages = dirty_page_list.getValue().size();
return num_dirty_pages;
}
addr_t SBMemoryRegionInfo::GetDirtyPageAddressAtIndex(uint32_t idx) {
LLDB_RECORD_METHOD(addr_t, SBMemoryRegionInfo, GetDirtyPageAddressAtIndex,
(uint32_t), idx);
addr_t dirty_page_addr = LLDB_INVALID_ADDRESS;
const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
m_opaque_up->GetDirtyPageList();
if (dirty_page_list.hasValue() && idx < dirty_page_list.getValue().size())
dirty_page_addr = dirty_page_list.getValue()[idx];
return dirty_page_addr;
}
int SBMemoryRegionInfo::GetPageSize() {
LLDB_RECORD_METHOD_NO_ARGS(int, SBMemoryRegionInfo, GetPageSize);
return m_opaque_up->GetPageSize();
}
bool SBMemoryRegionInfo::GetDescription(SBStream &description) {
LLDB_RECORD_METHOD(bool, SBMemoryRegionInfo, GetDescription,
(lldb::SBStream &), description);

View File

@ -1227,7 +1227,8 @@ lldb::SBError SBProcess::SaveCore(const char *file_name) {
}
FileSpec core_file(file_name);
error.ref() = PluginManager::SaveCore(process_sp, core_file);
SaveCoreStyle core_style = SaveCoreStyle::eSaveCoreFull;
error.ref() = PluginManager::SaveCore(process_sp, core_file, core_style);
return LLDB_RECORD_RESULT(error);
}

View File

@ -1678,6 +1678,28 @@ protected:
if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes)
result.AppendMessage("memory tagging: enabled");
const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
if (dirty_page_list.hasValue()) {
const size_t page_count = dirty_page_list.getValue().size();
result.AppendMessageWithFormat(
"Modified memory (dirty) page list provided, %zu entries.\n",
page_count);
if (page_count > 0) {
bool print_comma = false;
result.AppendMessageWithFormat("Dirty pages: ");
for (size_t i = 0; i < page_count; i++) {
if (print_comma)
result.AppendMessageWithFormat(", ");
else
print_comma = true;
result.AppendMessageWithFormat("0x%" PRIx64,
dirty_page_list.getValue()[i]);
}
result.AppendMessageWithFormat(".\n");
}
}
m_prev_end_addr = range_info.GetRange().GetRangeEnd();
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;

View File

@ -1161,26 +1161,91 @@ protected:
// CommandObjectProcessSaveCore
#pragma mark CommandObjectProcessSaveCore
static constexpr OptionEnumValueElement g_corefile_save_style[] = {
{eSaveCoreFull, "full", "Create a core file with all memory saved"},
{eSaveCoreDirtyOnly, "modified-memory",
"Create a corefile with only modified memory saved"}};
static constexpr OptionEnumValues SaveCoreStyles() {
return OptionEnumValues(g_corefile_save_style);
}
#define LLDB_OPTIONS_process_save_core
#include "CommandOptions.inc"
class CommandObjectProcessSaveCore : public CommandObjectParsed {
public:
CommandObjectProcessSaveCore(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "process save-core",
"Save the current process as a core file using an "
"appropriate file type.",
"process save-core FILE",
"process save-core [-s corefile-style] FILE",
eCommandRequiresProcess | eCommandTryTargetAPILock |
eCommandProcessMustBeLaunched) {}
~CommandObjectProcessSaveCore() override = default;
Options *GetOptions() override { return &m_options; }
class CommandOptions : public Options {
public:
CommandOptions()
: Options(), m_requested_save_core_style(eSaveCoreUnspecified) {}
~CommandOptions() override = default;
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::makeArrayRef(g_process_save_core_options);
}
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
ExecutionContext *execution_context) override {
const int short_option = m_getopt_table[option_idx].val;
Status error;
switch (short_option) {
case 's':
m_requested_save_core_style =
(lldb::SaveCoreStyle)OptionArgParser::ToOptionEnum(
option_arg, GetDefinitions()[option_idx].enum_values,
eSaveCoreUnspecified, error);
break;
default:
llvm_unreachable("Unimplemented option");
}
return {};
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_requested_save_core_style = eSaveCoreUnspecified;
}
// Instance variables to hold the values for command options.
SaveCoreStyle m_requested_save_core_style;
};
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
ProcessSP process_sp = m_exe_ctx.GetProcessSP();
if (process_sp) {
if (command.GetArgumentCount() == 1) {
FileSpec output_file(command.GetArgumentAtIndex(0));
Status error = PluginManager::SaveCore(process_sp, output_file);
SaveCoreStyle corefile_style = m_options.m_requested_save_core_style;
Status error =
PluginManager::SaveCore(process_sp, output_file, corefile_style);
if (error.Success()) {
if (corefile_style == SaveCoreStyle::eSaveCoreDirtyOnly) {
result.AppendMessageWithFormat(
"\nModified-memory only corefile "
"created. This corefile may not show \n"
"library/framework/app binaries "
"on a different system, or when \n"
"those binaries have "
"been updated/modified. Copies are not included\n"
"in this corefile. Use --style full to include all "
"process memory.\n");
}
result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
result.AppendErrorWithFormat(
@ -1197,6 +1262,8 @@ protected:
return result.Succeeded();
}
CommandOptions m_options;
};
// CommandObjectProcessStatus

View File

@ -730,6 +730,12 @@ let Command = "process status" in {
Desc<"Show verbose process status including extended crash information.">;
}
let Command = "process save_core" in {
def process_save_core_style : Option<"style", "s">, Group<1>,
EnumArg<"SaveCoreStyle", "SaveCoreStyles()">, Desc<"Request a specific style "
"of corefile to be saved.">;
}
let Command = "script import" in {
def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>,
Desc<"Allow the script to be loaded even if it was already loaded before. "

View File

@ -684,11 +684,13 @@ PluginManager::GetObjectFileCreateMemoryCallbackForPluginName(
}
Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp,
const FileSpec &outfile) {
const FileSpec &outfile,
lldb::SaveCoreStyle &core_style) {
Status error;
auto &instances = GetObjectFileInstances().GetInstances();
for (auto &instance : instances) {
if (instance.save_core && instance.save_core(process_sp, outfile, error))
if (instance.save_core &&
instance.save_core(process_sp, outfile, core_style, error))
return error;
}
error.SetErrorString(

View File

@ -1126,7 +1126,8 @@ CommandObject::ArgumentTableEntry CommandObject::g_arguments_data[] = {
{ eArgRawInput, "raw-input", CommandCompletions::eNoCompletion, { nullptr, false }, "Free-form text passed to a command without prior interpretation, allowing spaces without requiring quotes. To pass arguments and free form text put two dashes ' -- ' between the last argument and any raw input." },
{ eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command." },
{ eArgTypeColumnNum, "column", CommandCompletions::eNoCompletion, { nullptr, false }, "Column number in a source file." },
{ eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." }
{ eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." },
{ eArgTypeSaveCoreStyle, "corefile-style", CommandCompletions::eNoCompletion, { nullptr, false }, "The type of corefile that lldb will try to create, dependant on this target's capabilities." }
// clang-format on
};

View File

@ -23,6 +23,7 @@
#include "lldb/Host/Host.h"
#include "lldb/Host/SafeMachO.h"
#include "lldb/Symbol/DWARFCallFrameInfo.h"
#include "lldb/Symbol/LocateSymbolFile.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/MemoryRegionInfo.h"
@ -6204,11 +6205,257 @@ bool ObjectFileMachO::SetLoadAddress(Target &target, lldb::addr_t value,
return num_loaded_sections > 0;
}
struct all_image_infos_header {
uint32_t version; // currently 1
uint32_t imgcount; // number of binary images
uint64_t entries_fileoff; // file offset in the corefile of where the array of
// struct entry's begin.
uint32_t entries_size; // size of 'struct entry'.
uint32_t unused;
};
struct image_entry {
uint64_t filepath_offset; // offset in corefile to c-string of the file path,
// UINT64_MAX if unavailable.
uuid_t uuid; // uint8_t[16]. should be set to all zeroes if
// uuid is unknown.
uint64_t load_address; // UINT64_MAX if unknown.
uint64_t seg_addrs_offset; // offset to the array of struct segment_vmaddr's.
uint32_t segment_count; // The number of segments for this binary.
uint32_t unused;
image_entry() {
filepath_offset = UINT64_MAX;
memset(&uuid, 0, sizeof(uuid_t));
segment_count = 0;
load_address = UINT64_MAX;
seg_addrs_offset = UINT64_MAX;
unused = 0;
}
image_entry(const image_entry &rhs) {
filepath_offset = rhs.filepath_offset;
memcpy(&uuid, &rhs.uuid, sizeof(uuid_t));
segment_count = rhs.segment_count;
seg_addrs_offset = rhs.seg_addrs_offset;
load_address = rhs.load_address;
unused = rhs.unused;
}
};
struct segment_vmaddr {
char segname[16];
uint64_t vmaddr;
uint64_t unused;
segment_vmaddr() {
memset(&segname, 0, 16);
vmaddr = UINT64_MAX;
unused = 0;
}
segment_vmaddr(const segment_vmaddr &rhs) {
memcpy(&segname, &rhs.segname, 16);
vmaddr = rhs.vmaddr;
unused = rhs.unused;
}
};
// Write the payload for the "all image infos" LC_NOTE into
// the supplied all_image_infos_payload, assuming that this
// will be written into the corefile starting at
// initial_file_offset.
//
// The placement of this payload is a little tricky. We're
// laying this out as
//
// 1. header (struct all_image_info_header)
// 2. Array of fixed-size (struct image_entry)'s, one
// per binary image present in the process.
// 3. Arrays of (struct segment_vmaddr)'s, a varying number
// for each binary image.
// 4. Variable length c-strings of binary image filepaths,
// one per binary.
//
// To compute where everything will be laid out in the
// payload, we need to iterate over the images and calculate
// how many segment_vmaddr structures each image will need,
// and how long each image's filepath c-string is. There
// are some multiple passes over the image list while calculating
// everything.
static offset_t
CreateAllImageInfosPayload(const lldb::ProcessSP &process_sp,
offset_t initial_file_offset,
StreamString &all_image_infos_payload) {
Target &target = process_sp->GetTarget();
const ModuleList &modules = target.GetImages();
size_t modules_count = modules.GetSize();
std::set<std::string> executing_uuids;
ThreadList &thread_list(process_sp->GetThreadList());
for (uint32_t i = 0; i < thread_list.GetSize(); i++) {
ThreadSP thread_sp = thread_list.GetThreadAtIndex(i);
uint32_t stack_frame_count = thread_sp->GetStackFrameCount();
for (uint32_t j = 0; j < stack_frame_count; j++) {
StackFrameSP stack_frame_sp = thread_sp->GetStackFrameAtIndex(j);
Address pc = stack_frame_sp->GetFrameCodeAddress();
ModuleSP module_sp = pc.GetModule();
if (module_sp) {
UUID uuid = module_sp->GetUUID();
if (uuid.IsValid()) {
executing_uuids.insert(uuid.GetAsString());
}
}
}
}
struct all_image_infos_header infos;
infos.version = 1;
infos.imgcount = modules_count;
infos.entries_size = sizeof(image_entry);
infos.entries_fileoff = initial_file_offset + sizeof(all_image_infos_header);
infos.unused = 0;
all_image_infos_payload.PutHex32(infos.version);
all_image_infos_payload.PutHex32(infos.imgcount);
all_image_infos_payload.PutHex64(infos.entries_fileoff);
all_image_infos_payload.PutHex32(infos.entries_size);
all_image_infos_payload.PutHex32(infos.unused);
// First create the structures for all of the segment name+vmaddr vectors
// for each module, so we will know the size of them as we add the
// module entries.
std::vector<std::vector<segment_vmaddr>> modules_segment_vmaddrs;
for (size_t i = 0; i < modules_count; i++) {
ModuleSP module = modules.GetModuleAtIndex(i);
SectionList *sections = module->GetSectionList();
size_t sections_count = sections->GetSize();
std::vector<segment_vmaddr> segment_vmaddrs;
for (size_t j = 0; j < sections_count; j++) {
SectionSP section = sections->GetSectionAtIndex(j);
if (!section->GetParent().get()) {
addr_t vmaddr = section->GetLoadBaseAddress(&target);
if (vmaddr == LLDB_INVALID_ADDRESS)
continue;
ConstString name = section->GetName();
segment_vmaddr seg_vmaddr;
strncpy(seg_vmaddr.segname, name.AsCString(),
sizeof(seg_vmaddr.segname));
seg_vmaddr.vmaddr = vmaddr;
seg_vmaddr.unused = 0;
segment_vmaddrs.push_back(seg_vmaddr);
}
}
modules_segment_vmaddrs.push_back(segment_vmaddrs);
}
offset_t size_of_vmaddr_structs = 0;
for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) {
size_of_vmaddr_structs +=
modules_segment_vmaddrs[i].size() * sizeof(segment_vmaddr);
}
offset_t size_of_filepath_cstrings = 0;
for (size_t i = 0; i < modules_count; i++) {
ModuleSP module_sp = modules.GetModuleAtIndex(i);
size_of_filepath_cstrings += module_sp->GetFileSpec().GetPath().size() + 1;
}
// Calculate the file offsets of our "all image infos" payload in the
// corefile. initial_file_offset the original value passed in to this method.
offset_t start_of_entries =
initial_file_offset + sizeof(all_image_infos_header);
offset_t start_of_seg_vmaddrs =
start_of_entries + sizeof(image_entry) * modules_count;
offset_t start_of_filenames = start_of_seg_vmaddrs + size_of_vmaddr_structs;
offset_t final_file_offset = start_of_filenames + size_of_filepath_cstrings;
// Now write the one-per-module 'struct image_entry' into the
// StringStream; keep track of where the struct segment_vmaddr
// entries for each module will end up in the corefile.
offset_t current_string_offset = start_of_filenames;
offset_t current_segaddrs_offset = start_of_seg_vmaddrs;
std::vector<struct image_entry> image_entries;
for (size_t i = 0; i < modules_count; i++) {
ModuleSP module_sp = modules.GetModuleAtIndex(i);
struct image_entry ent;
memcpy(&ent.uuid, module_sp->GetUUID().GetBytes().data(), sizeof(ent.uuid));
if (modules_segment_vmaddrs[i].size() > 0) {
ent.segment_count = modules_segment_vmaddrs[i].size();
ent.seg_addrs_offset = current_segaddrs_offset;
}
ent.filepath_offset = current_string_offset;
ObjectFile *objfile = module_sp->GetObjectFile();
if (objfile) {
Address base_addr(objfile->GetBaseAddress());
if (base_addr.IsValid()) {
ent.load_address = base_addr.GetLoadAddress(&target);
}
}
all_image_infos_payload.PutHex64(ent.filepath_offset);
all_image_infos_payload.PutRawBytes(ent.uuid, sizeof(ent.uuid));
all_image_infos_payload.PutHex64(ent.load_address);
all_image_infos_payload.PutHex64(ent.seg_addrs_offset);
all_image_infos_payload.PutHex32(ent.segment_count);
if (executing_uuids.find(module_sp->GetUUID().GetAsString()) !=
executing_uuids.end())
all_image_infos_payload.PutHex32(1);
else
all_image_infos_payload.PutHex32(0);
current_segaddrs_offset += ent.segment_count * sizeof(segment_vmaddr);
current_string_offset += module_sp->GetFileSpec().GetPath().size() + 1;
}
// Now write the struct segment_vmaddr entries into the StringStream.
for (size_t i = 0; i < modules_segment_vmaddrs.size(); i++) {
if (modules_segment_vmaddrs[i].size() == 0)
continue;
for (struct segment_vmaddr segvm : modules_segment_vmaddrs[i]) {
all_image_infos_payload.PutRawBytes(segvm.segname, sizeof(segvm.segname));
all_image_infos_payload.PutHex64(segvm.vmaddr);
all_image_infos_payload.PutHex64(segvm.unused);
}
}
for (size_t i = 0; i < modules_count; i++) {
ModuleSP module_sp = modules.GetModuleAtIndex(i);
std::string filepath = module_sp->GetFileSpec().GetPath();
all_image_infos_payload.PutRawBytes(filepath.data(), filepath.size() + 1);
}
return final_file_offset;
}
// Temp struct used to combine contiguous memory regions with
// identical permissions.
struct page_object {
addr_t addr;
addr_t size;
uint32_t prot;
};
bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
const FileSpec &outfile, Status &error) {
const FileSpec &outfile,
lldb::SaveCoreStyle &core_style, Status &error) {
if (!process_sp)
return false;
// For Mach-O, we can only create full corefiles or dirty-page-only
// corefiles. The default is dirty-page-only.
if (core_style != SaveCoreStyle::eSaveCoreFull) {
core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
} else {
core_style = SaveCoreStyle::eSaveCoreFull;
}
Target &target = process_sp->GetTarget();
const ArchSpec target_arch = target.GetArchitecture();
const llvm::Triple &target_triple = target_arch.GetTriple();
@ -6242,14 +6489,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
Status range_error = process_sp->GetMemoryRegionInfo(0, range_info);
const uint32_t addr_byte_size = target_arch.GetAddressByteSize();
const ByteOrder byte_order = target_arch.GetByteOrder();
std::vector<page_object> pages_to_copy;
if (range_error.Success()) {
while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) {
const addr_t addr = range_info.GetRange().GetRangeBase();
const addr_t size = range_info.GetRange().GetByteSize();
if (size == 0)
break;
// Calculate correct protections
uint32_t prot = 0;
if (range_info.GetReadable() == MemoryRegionInfo::eYes)
@ -6259,32 +6502,28 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
if (range_info.GetExecutable() == MemoryRegionInfo::eYes)
prot |= VM_PROT_EXECUTE;
const addr_t addr = range_info.GetRange().GetRangeBase();
const addr_t size = range_info.GetRange().GetByteSize();
if (size == 0)
break;
if (prot != 0) {
uint32_t cmd_type = LC_SEGMENT_64;
uint32_t segment_size = sizeof(llvm::MachO::segment_command_64);
if (addr_byte_size == 4) {
cmd_type = LC_SEGMENT;
segment_size = sizeof(llvm::MachO::segment_command);
addr_t pagesize = range_info.GetPageSize();
const llvm::Optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
if (core_style == SaveCoreStyle::eSaveCoreDirtyOnly &&
dirty_page_list.hasValue()) {
core_style = SaveCoreStyle::eSaveCoreDirtyOnly;
for (addr_t dirtypage : dirty_page_list.getValue()) {
page_object obj = {
.addr = dirtypage, .size = pagesize, .prot = prot};
pages_to_copy.push_back(obj);
}
} else {
page_object obj = {.addr = addr, .size = size, .prot = prot};
pages_to_copy.push_back(obj);
}
llvm::MachO::segment_command_64 segment = {
cmd_type, // uint32_t cmd;
segment_size, // uint32_t cmdsize;
{0}, // char segname[16];
addr, // uint64_t vmaddr; // uint32_t for 32-bit Mach-O
size, // uint64_t vmsize; // uint32_t for 32-bit Mach-O
0, // uint64_t fileoff; // uint32_t for 32-bit Mach-O
size, // uint64_t filesize; // uint32_t for 32-bit Mach-O
prot, // uint32_t maxprot;
prot, // uint32_t initprot;
0, // uint32_t nsects;
0}; // uint32_t flags;
segment_load_commands.push_back(segment);
} else {
// No protections and a size of 1 used to be returned from old
// debugservers when we asked about a region that was past the
// last memory region and it indicates the end...
if (size == 1)
break;
}
range_error = process_sp->GetMemoryRegionInfo(
@ -6293,6 +6532,51 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
break;
}
// Combine contiguous entries that have the same
// protections so we don't have an excess of
// load commands.
std::vector<page_object> combined_page_objects;
page_object last_obj;
last_obj.addr = LLDB_INVALID_ADDRESS;
for (page_object obj : pages_to_copy) {
if (last_obj.addr == LLDB_INVALID_ADDRESS) {
last_obj = obj;
continue;
}
if (last_obj.addr + last_obj.size == obj.addr &&
last_obj.prot == obj.prot) {
last_obj.size += obj.size;
continue;
}
combined_page_objects.push_back(last_obj);
last_obj = obj;
}
for (page_object obj : combined_page_objects) {
uint32_t cmd_type = LC_SEGMENT_64;
uint32_t segment_size = sizeof(llvm::MachO::segment_command_64);
if (addr_byte_size == 4) {
cmd_type = LC_SEGMENT;
segment_size = sizeof(llvm::MachO::segment_command);
}
llvm::MachO::segment_command_64 segment = {
cmd_type, // uint32_t cmd;
segment_size, // uint32_t cmdsize;
{0}, // char segname[16];
obj.addr, // uint64_t vmaddr; // uint32_t for 32-bit
// Mach-O
obj.size, // uint64_t vmsize; // uint32_t for 32-bit
// Mach-O
0, // uint64_t fileoff; // uint32_t for 32-bit Mach-O
obj.size, // uint64_t filesize; // uint32_t for 32-bit
// Mach-O
obj.prot, // uint32_t maxprot;
obj.prot, // uint32_t initprot;
0, // uint32_t nsects;
0}; // uint32_t flags;
segment_load_commands.push_back(segment);
}
StreamString buffer(Stream::eBinary, addr_byte_size, byte_order);
llvm::MachO::mach_header_64 mach_header;
@ -6363,6 +6647,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
mach_header.sizeofcmds += 8 + LC_THREAD_data.GetSize();
}
// LC_NOTE "all image infos"
mach_header.ncmds++;
mach_header.sizeofcmds += sizeof(llvm::MachO::note_command);
// Write the mach header
buffer.PutHex32(mach_header.magic);
buffer.PutHex32(mach_header.cputype);
@ -6378,10 +6666,33 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
// Skip the mach header and all load commands and align to the next
// 0x1000 byte boundary
addr_t file_offset = buffer.GetSize() + mach_header.sizeofcmds;
if (file_offset & 0x00000fff) {
file_offset += 0x00001000ull;
file_offset &= (~0x00001000ull + 1);
}
file_offset = llvm::alignTo(file_offset, 16);
// Create the "all image infos" LC_NOTE payload
StreamString all_image_infos_payload(Stream::eBinary, addr_byte_size,
byte_order);
offset_t all_image_infos_payload_start = file_offset;
file_offset = CreateAllImageInfosPayload(process_sp, file_offset,
all_image_infos_payload);
// Add the "all image infos" LC_NOTE load command
llvm::MachO::note_command all_image_info_note = {
LC_NOTE, /* uint32_t cmd */
sizeof(llvm::MachO::note_command), /* uint32_t cmdsize */
"all image infos", /* char data_owner[16] */
all_image_infos_payload_start, /* uint64_t offset */
file_offset - all_image_infos_payload_start /* uint64_t size */
};
buffer.PutHex32(all_image_info_note.cmd);
buffer.PutHex32(all_image_info_note.cmdsize);
buffer.PutRawBytes(all_image_info_note.data_owner,
sizeof(all_image_info_note.data_owner));
buffer.PutHex64(all_image_info_note.offset);
buffer.PutHex64(all_image_info_note.size);
// Align to 4096-byte page boundary for the LC_SEGMENTs.
file_offset = llvm::alignTo(file_offset, 4096);
for (auto &segment : segment_load_commands) {
segment.fileoff = file_offset;
@ -6398,14 +6709,6 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
// Write out all of the segment load commands
for (const auto &segment : segment_load_commands) {
printf("0x%8.8x 0x%8.8x [0x%16.16" PRIx64 " - 0x%16.16" PRIx64
") [0x%16.16" PRIx64 " 0x%16.16" PRIx64
") 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x]\n",
segment.cmd, segment.cmdsize, segment.vmaddr,
segment.vmaddr + segment.vmsize, segment.fileoff,
segment.filesize, segment.maxprot, segment.initprot,
segment.nsects, segment.flags);
buffer.PutHex32(segment.cmd);
buffer.PutHex32(segment.cmdsize);
buffer.PutRawBytes(segment.segname, sizeof(segment.segname));
@ -6440,6 +6743,20 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
error =
core_file.get()->Write(buffer.GetString().data(), bytes_written);
if (error.Success()) {
if (core_file.get()->SeekFromStart(all_image_info_note.offset) ==
-1) {
error.SetErrorStringWithFormat(
"Unable to seek to corefile pos to write all iamge infos");
return false;
}
bytes_written = all_image_infos_payload.GetString().size();
error = core_file.get()->Write(
all_image_infos_payload.GetString().data(), bytes_written);
if (!error.Success())
return false;
// Now write the file data for all memory segments in the process
for (const auto &segment : segment_load_commands) {
if (core_file.get()->SeekFromStart(segment.fileoff) == -1) {
@ -6449,9 +6766,10 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
break;
}
printf("Saving %" PRId64
" bytes of data for memory region at 0x%" PRIx64 "\n",
segment.vmsize, segment.vmaddr);
target.GetDebugger().GetAsyncOutputStream()->Printf(
"Saving %" PRId64
" bytes of data for memory region at 0x%" PRIx64 "\n",
segment.vmsize, segment.vmaddr);
addr_t bytes_left = segment.vmsize;
addr_t addr = segment.vmaddr;
Status memory_read_error;
@ -6493,3 +6811,121 @@ bool ObjectFileMachO::SaveCore(const lldb::ProcessSP &process_sp,
}
return false;
}
ObjectFileMachO::MachOCorefileAllImageInfos
ObjectFileMachO::GetCorefileAllImageInfos() {
MachOCorefileAllImageInfos image_infos;
// Look for an "all image infos" LC_NOTE.
lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic);
for (uint32_t i = 0; i < m_header.ncmds; ++i) {
const uint32_t cmd_offset = offset;
llvm::MachO::load_command lc;
if (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr)
break;
if (lc.cmd == LC_NOTE) {
char data_owner[17];
m_data.CopyData(offset, 16, data_owner);
data_owner[16] = '\0';
offset += 16;
uint64_t fileoff = m_data.GetU64_unchecked(&offset);
offset += 4; /* size unused */
if (strcmp("all image infos", data_owner) == 0) {
offset = fileoff;
// Read the struct all_image_infos_header.
uint32_t version = m_data.GetU32(&offset);
if (version != 1) {
return image_infos;
}
uint32_t imgcount = m_data.GetU32(&offset);
uint64_t entries_fileoff = m_data.GetU64(&offset);
offset += 4; // uint32_t entries_size;
offset += 4; // uint32_t unused;
offset = entries_fileoff;
for (uint32_t i = 0; i < imgcount; i++) {
// Read the struct image_entry.
offset_t filepath_offset = m_data.GetU64(&offset);
uuid_t uuid;
memcpy(&uuid, m_data.GetData(&offset, sizeof(uuid_t)),
sizeof(uuid_t));
uint64_t load_address = m_data.GetU64(&offset);
offset_t seg_addrs_offset = m_data.GetU64(&offset);
uint32_t segment_count = m_data.GetU32(&offset);
uint32_t currently_executing = m_data.GetU32(&offset);
MachOCorefileImageEntry image_entry;
image_entry.filename = (const char *)m_data.GetCStr(&filepath_offset);
image_entry.uuid = UUID::fromData(uuid, sizeof(uuid_t));
image_entry.load_address = load_address;
image_entry.currently_executing = currently_executing;
offset_t seg_vmaddrs_offset = seg_addrs_offset;
for (uint32_t j = 0; j < segment_count; j++) {
char segname[17];
m_data.CopyData(seg_vmaddrs_offset, 16, segname);
segname[16] = '\0';
seg_vmaddrs_offset += 16;
uint64_t vmaddr = m_data.GetU64(&seg_vmaddrs_offset);
seg_vmaddrs_offset += 8; /* unused */
std::tuple<ConstString, addr_t> new_seg{ConstString(segname),
vmaddr};
image_entry.segment_load_addresses.push_back(new_seg);
}
image_infos.all_image_infos.push_back(image_entry);
}
}
}
offset = cmd_offset + lc.cmdsize;
}
return image_infos;
}
bool ObjectFileMachO::LoadCoreFileImages(lldb_private::Process &process) {
MachOCorefileAllImageInfos image_infos = GetCorefileAllImageInfos();
bool added_images = false;
if (image_infos.IsValid()) {
for (const MachOCorefileImageEntry &image : image_infos.all_image_infos) {
ModuleSpec module_spec;
module_spec.GetUUID() = image.uuid;
module_spec.GetFileSpec() = FileSpec(image.filename.c_str());
if (image.currently_executing) {
Symbols::DownloadObjectAndSymbolFile(module_spec, true);
if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
process.GetTarget().GetOrCreateModule(module_spec, false);
}
}
Status error;
ModuleSP module_sp =
process.GetTarget().GetOrCreateModule(module_spec, false, &error);
if (!module_sp.get() || !module_sp->GetObjectFile()) {
if (image.load_address != LLDB_INVALID_ADDRESS) {
module_sp = process.ReadModuleFromMemory(module_spec.GetFileSpec(),
image.load_address);
}
}
if (module_sp.get() && module_sp->GetObjectFile()) {
added_images = true;
if (module_sp->GetObjectFile()->GetType() ==
ObjectFile::eTypeExecutable) {
process.GetTarget().SetExecutableModule(module_sp, eLoadDependentsNo);
}
for (auto name_vmaddr_tuple : image.segment_load_addresses) {
SectionList *sectlist = module_sp->GetObjectFile()->GetSectionList();
if (sectlist) {
SectionSP sect_sp =
sectlist->FindSectionByName(std::get<0>(name_vmaddr_tuple));
if (sect_sp) {
process.GetTarget().SetSectionLoadAddress(
sect_sp, std::get<1>(name_vmaddr_tuple));
}
}
}
}
}
}
return added_images;
}

View File

@ -58,6 +58,7 @@ public:
static bool SaveCore(const lldb::ProcessSP &process_sp,
const lldb_private::FileSpec &outfile,
lldb::SaveCoreStyle &core_style,
lldb_private::Status &error);
static bool MagicBytesMatch(lldb::DataBufferSP &data_sp, lldb::addr_t offset,
@ -116,6 +117,8 @@ public:
lldb_private::UUID &uuid,
ObjectFile::BinaryType &type) override;
bool LoadCoreFileImages(lldb_private::Process &process) override;
lldb::RegisterContextSP
GetThreadContextAtIndex(uint32_t idx, lldb_private::Thread &thread) override;
@ -209,6 +212,31 @@ protected:
bool SectionIsLoadable(const lldb_private::Section *section);
/// A corefile may include metadata about all of the binaries that were
/// present in the process when the corefile was taken. This is only
/// implemented for Mach-O files for now; we'll generalize it when we
/// have other systems that can include the same.
struct MachOCorefileImageEntry {
std::string filename;
lldb_private::UUID uuid;
lldb::addr_t load_address = LLDB_INVALID_ADDRESS;
bool currently_executing;
std::vector<std::tuple<lldb_private::ConstString, lldb::addr_t>>
segment_load_addresses;
};
struct MachOCorefileAllImageInfos {
std::vector<MachOCorefileImageEntry> all_image_infos;
bool IsValid() { return all_image_infos.size() > 0; }
};
/// Get the list of binary images that were present in the process
/// when the corefile was produced.
/// \return
/// The MachOCorefileAllImageInfos object returned will have
/// IsValid() == false if the information is unavailable.
MachOCorefileAllImageInfos GetCorefileAllImageInfos();
llvm::MachO::mach_header m_header;
static lldb_private::ConstString GetSegmentNameTEXT();
static lldb_private::ConstString GetSegmentNameDATA();

View File

@ -189,7 +189,9 @@ size_t ObjectFilePECOFF::GetModuleSpecifications(
bool ObjectFilePECOFF::SaveCore(const lldb::ProcessSP &process_sp,
const lldb_private::FileSpec &outfile,
lldb::SaveCoreStyle &core_style,
lldb_private::Status &error) {
core_style = eSaveCoreFull;
return SaveMiniDump(process_sp, outfile, error);
}

View File

@ -79,6 +79,7 @@ public:
static bool SaveCore(const lldb::ProcessSP &process_sp,
const lldb_private::FileSpec &outfile,
lldb::SaveCoreStyle &core_style,
lldb_private::Status &error);
static bool MagicBytesMatch(lldb::DataBufferSP &data_sp);

View File

@ -16,6 +16,7 @@
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/StringConvert.h"
#include "lldb/Host/XML.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/MemoryRegionInfo.h"
@ -285,6 +286,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
m_gdb_server_name.clear();
m_gdb_server_version = UINT32_MAX;
m_default_packet_timeout = seconds(0);
m_target_vm_page_size = 0;
m_max_packet_size = 0;
m_qSupported_response.clear();
m_supported_async_json_packets_is_valid = false;
@ -1192,6 +1194,12 @@ bool GDBRemoteCommunicationClient::GetHostInfo(bool force) {
SetPacketTimeout(m_default_packet_timeout);
++num_keys_decoded;
}
} else if (name.equals("vm-page-size")) {
int page_size;
if (!value.getAsInteger(0, page_size)) {
m_target_vm_page_size = page_size;
++num_keys_decoded;
}
}
}
@ -1503,9 +1511,30 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
// Now convert the HEX bytes into a string value
error_extractor.GetHexByteString(error_string);
error.SetErrorString(error_string.c_str());
} else if (name.equals("dirty-pages")) {
std::vector<addr_t> dirty_page_list;
std::string comma_sep_str = value.str();
size_t comma_pos;
addr_t page;
while ((comma_pos = comma_sep_str.find(',')) != std::string::npos) {
comma_sep_str[comma_pos] = '\0';
page = StringConvert::ToUInt64(comma_sep_str.c_str(),
LLDB_INVALID_ADDRESS, 16);
if (page != LLDB_INVALID_ADDRESS)
dirty_page_list.push_back(page);
comma_sep_str.erase(0, comma_pos + 1);
}
page = StringConvert::ToUInt64(comma_sep_str.c_str(),
LLDB_INVALID_ADDRESS, 16);
if (page != LLDB_INVALID_ADDRESS)
dirty_page_list.push_back(page);
region_info.SetDirtyPageList(dirty_page_list);
}
}
if (m_target_vm_page_size != 0)
region_info.SetPageSize(m_target_vm_page_size);
if (region_info.GetRange().IsValid()) {
// We got a valid address range back but no permissions -- which means
// this is an unmapped page

View File

@ -592,6 +592,7 @@ protected:
UINT32_MAX; // from reply to qGDBServerVersion, zero if
// qGDBServerVersion is not supported
std::chrono::seconds m_default_packet_timeout;
int m_target_vm_page_size = 0; // target system VM page size; 0 unspecified
uint64_t m_max_packet_size = 0; // as returned by qSupported
std::string m_qSupported_response; // the complete response to qSupported

View File

@ -332,7 +332,6 @@ Status ProcessMachCore::DoLoadCore() {
m_core_range_infos.Sort();
}
bool found_main_binary_definitively = false;
addr_t objfile_binary_addr;
@ -414,6 +413,14 @@ Status ProcessMachCore::DoLoadCore() {
}
}
// If we have a "all image infos" LC_NOTE, try to load all of the
// binaries listed, and set their Section load addresses in the Target.
if (found_main_binary_definitively == false &&
core_objfile->LoadCoreFileImages(*this)) {
m_dyld_plugin_name = DynamicLoaderDarwinKernel::GetPluginNameStatic();
found_main_binary_definitively = true;
}
if (!found_main_binary_definitively &&
(m_dyld_addr == LLDB_INVALID_ADDRESS ||
m_mach_kernel_addr == LLDB_INVALID_ADDRESS)) {

View File

@ -0,0 +1,65 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from gdbclientutils import *
class TestMemoryRegionDirtyPages(GDBRemoteTestBase):
@skipIfXmlSupportMissing
def test(self):
class MyResponder(MockGDBServerResponder):
def qHostInfo(self):
return "ptrsize:8;endian:little;vm-page-size:4096;"
def qMemoryRegionInfo(self, addr):
if addr == 0:
return "start:0;size:100000000;"
if addr == 0x100000000:
return "start:100000000;size:4000;permissions:rx;dirty-pages:;"
if addr == 0x100004000:
return "start:100004000;size:4000;permissions:r;dirty-pages:0x100004000;"
if addr == 0x1000a2000:
return "start:1000a2000;size:5000;permissions:r;dirty-pages:0x1000a2000,0x1000a3000,0x1000a4000,0x1000a5000,0x1000a6000;"
self.server.responder = MyResponder()
target = self.dbg.CreateTarget('')
if self.TraceOn():
self.runCmd("log enable gdb-remote packets")
self.addTearDownHook(
lambda: self.runCmd("log disable gdb-remote packets"))
process = self.connect(target)
# A memory region where we don't know anything about dirty pages
region = lldb.SBMemoryRegionInfo()
err = process.GetMemoryRegionInfo(0, region)
self.assertTrue(err.Success())
self.assertFalse(region.HasDirtyMemoryPageList())
self.assertEqual(region.GetNumDirtyPages(), 0)
region.Clear()
# A memory region with dirty page information -- and zero dirty pages
err = process.GetMemoryRegionInfo(0x100000000, region)
self.assertTrue(err.Success())
self.assertTrue(region.HasDirtyMemoryPageList())
self.assertEqual(region.GetNumDirtyPages(), 0)
self.assertEqual(region.GetPageSize(), 4096)
region.Clear()
# A memory region with one dirty page
err = process.GetMemoryRegionInfo(0x100004000, region)
self.assertTrue(err.Success())
self.assertTrue(region.HasDirtyMemoryPageList())
self.assertEqual(region.GetNumDirtyPages(), 1)
self.assertEqual(region.GetDirtyPageAddressAtIndex(0), 0x100004000)
region.Clear()
# A memory region with multple dirty pages
err = process.GetMemoryRegionInfo(0x1000a2000, region)
self.assertTrue(err.Success())
self.assertTrue(region.HasDirtyMemoryPageList())
self.assertEqual(region.GetNumDirtyPages(), 5)
self.assertEqual(region.GetDirtyPageAddressAtIndex(4), 0x1000a6000)
region.Clear()

View File

@ -166,7 +166,7 @@ class MockGDBServerResponder:
if packet == "QListThreadsInStopReply":
return self.QListThreadsInStopReply()
if packet.startswith("qMemoryRegionInfo:"):
return self.qMemoryRegionInfo()
return self.qMemoryRegionInfo(int(packet.split(':')[1], 16))
if packet == "qQueryGDBServer":
return self.qQueryGDBServer()
if packet == "qHostInfo":
@ -282,7 +282,7 @@ class MockGDBServerResponder:
def QListThreadsInStopReply(self):
return ""
def qMemoryRegionInfo(self):
def qMemoryRegionInfo(self, addr):
return ""
def qPathComplete(self):

View File

@ -0,0 +1,15 @@
LD_EXTRAS = -L. -lto-be-removed -lpresent
C_SOURCES = main.c
include Makefile.rules
a.out: libto-be-removed libpresent
libto-be-removed: libpresent
$(MAKE) -f $(MAKEFILE_RULES) \
DYLIB_ONLY=YES DYLIB_C_SOURCES=to-be-removed.c DYLIB_NAME=to-be-removed \
LD_EXTRAS="-L. -lpresent"
libpresent:
$(MAKE) -f $(MAKEFILE_RULES) \
DYLIB_ONLY=YES DYLIB_C_SOURCES=present.c DYLIB_NAME=present

View File

@ -0,0 +1,162 @@
"""Test that lldb can create a skinny corefile, and load all available libraries correctly."""
import os
import re
import subprocess
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestFirmwareCorefiles(TestBase):
mydir = TestBase.compute_mydir(__file__)
@skipIf(debug_info=no_match(["dsym"]), bugnumber="This test is looking explicitly for a dSYM")
@skipUnlessDarwin
def test_lc_note(self):
self.build()
self.aout_exe = self.getBuildArtifact("a.out")
self.aout_dsym = self.getBuildArtifact("a.out.dSYM")
self.to_be_removed_dylib = self.getBuildArtifact("libto-be-removed.dylib")
self.to_be_removed_dsym = self.getBuildArtifact("libto-be-removed.dylib.dSYM")
self.corefile = self.getBuildArtifact("process.core")
self.dsym_for_uuid = self.getBuildArtifact("dsym-for-uuid.sh")
# After the corefile is created, we'll move a.out and a.out.dSYM
# into hide.noindex and lldb will have to use the
# LLDB_APPLE_DSYMFORUUID_EXECUTABLE script to find them.
self.hide_dir = self.getBuildArtifact("hide.noindex")
lldbutil.mkdir_p(self.hide_dir)
self.hide_aout_exe = self.getBuildArtifact("hide.noindex/a.out")
self.hide_aout_dsym = self.getBuildArtifact("hide.noindex/a.out.dSYM")
# We can hook in our dsym-for-uuid shell script to lldb with
# this env var instead of requiring a defaults write.
os.environ['LLDB_APPLE_DSYMFORUUID_EXECUTABLE'] = self.dsym_for_uuid
self.addTearDownHook(lambda: os.environ.pop('LLDB_APPLE_DSYMFORUUID_EXECUTABLE', None))
dwarfdump_uuid_regex = re.compile(
'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
dwarfdump_cmd_output = subprocess.check_output(
('/usr/bin/dwarfdump --uuid "%s"' % self.aout_exe), shell=True).decode("utf-8")
aout_uuid = None
for line in dwarfdump_cmd_output.splitlines():
match = dwarfdump_uuid_regex.search(line)
if match:
aout_uuid = match.group(1)
self.assertNotEqual(aout_uuid, None, "Could not get uuid of built a.out")
### Create our dsym-for-uuid shell script which returns self.hide_aout_exe.
shell_cmds = [
'#! /bin/sh',
'# the last argument is the uuid',
'while [ $# -gt 1 ]',
'do',
' shift',
'done',
'ret=0',
'echo "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>"',
'echo "<!DOCTYPE plist PUBLIC \\"-//Apple//DTD PLIST 1.0//EN\\" \\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\">"',
'echo "<plist version=\\"1.0\\">"',
'',
'if [ "$1" = "%s" ]' % aout_uuid,
'then',
' uuid=%s' % aout_uuid,
' bin=%s' % self.hide_aout_exe,
' dsym=%s.dSYM/Contents/Resources/DWARF/%s' % (self.hide_aout_exe, os.path.basename(self.hide_aout_exe)),
'fi',
'if [ -z "$uuid" -o -z "$bin" -o ! -f "$bin" ]',
'then',
' echo "<key>DBGError</key><string>not found</string>"',
' echo "</plist>"',
' exit 1',
'fi',
'echo "<dict><key>$uuid</key><dict>"',
'',
'echo "<key>DBGArchitecture</key><string>x86_64</string>"',
'echo "<key>DBGDSYMPath</key><string>$dsym</string>"',
'echo "<key>DBGSymbolRichExecutable</key><string>$bin</string>"',
'echo "</dict></dict></plist>"',
'exit $ret'
]
with open(self.dsym_for_uuid, "w") as writer:
for l in shell_cmds:
writer.write(l + '\n')
os.chmod(self.dsym_for_uuid, 0o755)
# Launch a live process with a.out, libto-be-removed.dylib,
# libpresent.dylib all in their original locations, create
# a corefile at the breakpoint.
(target, process, t, bp) = lldbutil.run_to_source_breakpoint (
self, "break here", lldb.SBFileSpec('present.c'))
self.assertTrue(process.IsValid())
if self.TraceOn():
self.runCmd("bt")
self.runCmd("image list")
self.runCmd("process save-core " + self.corefile)
process.Kill()
target.Clear()
# Move the main binary and its dSYM into the hide.noindex
# directory. Now the only way lldb can find them is with
# the LLDB_APPLE_DSYMFORUUID_EXECUTABLE shell script -
# so we're testing that this dSYM discovery method works.
os.rename(self.aout_exe, self.hide_aout_exe)
os.rename(self.aout_dsym, self.hide_aout_dsym)
# Completely remove the libto-be-removed.dylib, so we're
# testing that lldb handles an unavailable binary correctly,
# and non-dirty memory from this binary (e.g. the executing
# instructions) are NOT included in the corefile.
os.unlink(self.to_be_removed_dylib)
shutil.rmtree(self.to_be_removed_dsym)
# Now load the corefile
self.target = self.dbg.CreateTarget('')
self.process = self.target.LoadCore(self.corefile)
self.assertTrue(self.process.IsValid())
if self.TraceOn():
self.runCmd("image list")
self.runCmd("bt")
self.assertTrue(self.process.IsValid())
self.assertTrue(self.process.GetSelectedThread().IsValid())
# f0 is present() in libpresent.dylib
f0 = self.process.GetSelectedThread().GetFrameAtIndex(0)
to_be_removed_dirty_data = f0.FindVariable("to_be_removed_dirty_data")
self.assertEqual(to_be_removed_dirty_data.GetValueAsUnsigned(), 20)
present_heap_buf = f0.FindVariable("present_heap_buf")
self.assertTrue("have ints 5 20 20 5" in present_heap_buf.GetSummary())
# f1 is to_be_removed() in libto-be-removed.dylib
# it has been removed since the corefile was created,
# and the instructions for this frame should NOT be included
# in the corefile. They were not dirty pages.
f1 = self.process.GetSelectedThread().GetFrameAtIndex(1)
err = lldb.SBError()
uint = self.process.ReadUnsignedFromMemory(f1.GetPC(), 4, err)
self.assertTrue(err.Fail())
# TODO Future testing could check that read-only constant data
# (main_const_data, present_const_data) can be read both as an
# SBValue and in an expression -- which means lldb needs to read
# them out of the binaries, they are not present in the corefile.
# And checking file-scope dirty data (main_dirty_data,
# present_dirty_data) the same way would be good, instead of just
# checking the heap and stack like are being done right now.

View File

@ -0,0 +1,20 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "present.h"
#include "to-be-removed.h"
const int main_const_data = 5;
int main_dirty_data = 10;
int main(int argc, char **argv) {
to_be_removed_init(argc);
present_init(argc);
main_dirty_data += argc;
char *heap_buf = (char *)malloc(80);
strcpy(heap_buf, "this is a string on the heap");
return to_be_removed(heap_buf, main_const_data, main_dirty_data);
}

View File

@ -0,0 +1,22 @@
#include <stdio.h>
#include <stdlib.h>
#include "present.h"
const int present_const_data = 5;
int present_dirty_data = 10;
void present_init(int in) { present_dirty_data += 10; }
int present(char *to_be_removed_heap_buf, int to_be_removed_const_data,
int to_be_removed_dirty_data) {
char *present_heap_buf = (char *)malloc(256);
sprintf(present_heap_buf, "have ints %d %d %d %d", to_be_removed_const_data,
to_be_removed_dirty_data, present_dirty_data, present_const_data);
printf("%s\n", present_heap_buf);
puts(to_be_removed_heap_buf);
puts("break here");
return present_const_data + present_dirty_data;
}

View File

@ -0,0 +1,2 @@
void present_init (int in);
int present (char *to_be_removed_heap_buf, int to_be_removed_const_data, int to_be_removed_dirty_data);

View File

@ -0,0 +1,21 @@
#include <stdio.h>
#include <stdlib.h>
#include "present.h"
#include "to-be-removed.h"
const int to_be_removed_const_data = 5;
int to_be_removed_dirty_data = 10;
void to_be_removed_init(int in) { to_be_removed_dirty_data += 10; }
int to_be_removed(char *main_heap_buf, int main_const_data,
int main_dirty_data) {
char *to_be_removed_heap_buf = (char *)malloc(256);
sprintf(to_be_removed_heap_buf, "got string '%s' have int %d %d %d",
main_heap_buf, to_be_removed_dirty_data, main_const_data,
main_dirty_data);
printf("%s\n", to_be_removed_heap_buf);
return present(to_be_removed_heap_buf, to_be_removed_const_data,
to_be_removed_dirty_data);
}

View File

@ -0,0 +1,2 @@
void to_be_removed_init (int in);
int to_be_removed (char *main_heap_buf, int main_const_data, int main_dirty_data);

View File

@ -30,6 +30,7 @@ class TestGdbRemoteHostInfo(GdbRemoteTestCaseBase):
"ptrsize",
"triple",
"vendor",
"vm-page-size",
"watchpoint_exceptions_received",
])

View File

@ -18,6 +18,7 @@
#include <cstdio>
#include <sys/syslimits.h>
#include <unistd.h>
#include <vector>
// Define nub_addr_t and the invalid address value from the architecture
#if defined(__x86_64__) || defined(__arm64__) || defined(__aarch64__)
@ -316,9 +317,12 @@ struct DNBExecutableImageInfo {
};
struct DNBRegionInfo {
public:
DNBRegionInfo() : addr(0), size(0), permissions(0), dirty_pages() {}
nub_addr_t addr;
nub_addr_t size;
uint32_t permissions;
std::vector<nub_addr_t> dirty_pages;
};
enum DNBProfileDataScanType {

View File

@ -72,6 +72,49 @@ nub_size_t MachVMMemory::MaxBytesLeftInPage(task_t task, nub_addr_t addr,
return count;
}
#define MAX_STACK_ALLOC_DISPOSITIONS \
(16 * 1024 / sizeof(int)) // 16K of allocations
std::vector<nub_addr_t> get_dirty_pages(task_t task, mach_vm_address_t addr,
mach_vm_size_t size) {
std::vector<nub_addr_t> dirty_pages;
int pages_to_query = size / vm_page_size;
// Don't try to fetch too many pages' dispositions in a single call or we
// could blow our stack out.
mach_vm_size_t dispositions_size =
std::min(pages_to_query, (int)MAX_STACK_ALLOC_DISPOSITIONS);
int dispositions[dispositions_size];
mach_vm_size_t chunk_count =
((pages_to_query + MAX_STACK_ALLOC_DISPOSITIONS - 1) /
MAX_STACK_ALLOC_DISPOSITIONS);
for (mach_vm_size_t cur_disposition_chunk = 0;
cur_disposition_chunk < chunk_count; cur_disposition_chunk++) {
mach_vm_size_t dispositions_already_queried =
cur_disposition_chunk * MAX_STACK_ALLOC_DISPOSITIONS;
mach_vm_size_t chunk_pages_to_query = std::min(
pages_to_query - dispositions_already_queried, dispositions_size);
mach_vm_address_t chunk_page_aligned_start_addr =
addr + (dispositions_already_queried * vm_page_size);
kern_return_t kr = mach_vm_page_range_query(
task, chunk_page_aligned_start_addr,
chunk_pages_to_query * vm_page_size, (mach_vm_address_t)dispositions,
&chunk_pages_to_query);
if (kr != KERN_SUCCESS)
return dirty_pages;
for (mach_vm_size_t i = 0; i < chunk_pages_to_query; i++) {
uint64_t dirty_addr = chunk_page_aligned_start_addr + (i * vm_page_size);
if (dispositions[i] & VM_PAGE_QUERY_PAGE_DIRTY)
dirty_pages.push_back(dirty_addr);
}
}
return dirty_pages;
}
nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
DNBRegionInfo *region_info) {
MachVMRegion vmRegion(task);
@ -80,6 +123,8 @@ nub_bool_t MachVMMemory::GetMemoryRegionInfo(task_t task, nub_addr_t address,
region_info->addr = vmRegion.StartAddress();
region_info->size = vmRegion.GetByteSize();
region_info->permissions = vmRegion.GetDNBPermissions();
region_info->dirty_pages =
get_dirty_pages(task, vmRegion.StartAddress(), vmRegion.GetByteSize());
} else {
region_info->addr = address;
region_info->size = 0;

View File

@ -19,6 +19,7 @@
#include <libproc.h>
#include <mach-o/loader.h>
#include <mach/exception_types.h>
#include <mach/mach_vm.h>
#include <mach/task_info.h>
#include <pwd.h>
#include <sys/stat.h>
@ -4447,7 +4448,7 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) {
__FILE__, __LINE__, p, "Invalid address in qMemoryRegionInfo packet");
}
DNBRegionInfo region_info = {0, 0, 0};
DNBRegionInfo region_info;
DNBProcessMemoryRegionInfo(m_ctx.ProcessID(), address, &region_info);
std::ostringstream ostrm;
@ -4467,6 +4468,18 @@ rnb_err_t RNBRemote::HandlePacket_MemoryRegionInfo(const char *p) {
if (region_info.permissions & eMemoryPermissionsExecutable)
ostrm << 'x';
ostrm << ';';
ostrm << "dirty-pages:";
if (region_info.dirty_pages.size() > 0) {
bool first = true;
for (nub_addr_t addr : region_info.dirty_pages) {
if (!first)
ostrm << ",";
first = false;
ostrm << "0x" << std::hex << addr;
}
}
ostrm << ";";
}
return SendPacket(ostrm.str());
}
@ -4993,6 +5006,8 @@ rnb_err_t RNBRemote::HandlePacket_qHostInfo(const char *p) {
strm << "default_packet_timeout:10;";
#endif
strm << "vm-page-size:" << std::dec << vm_page_size << ";";
return SendPacket(strm.str());
}