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;
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.
// 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.
/// 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;
GetObjectFileCreateMemoryCallbackForPluginName(ConstString name);
static Status SaveCore(const lldb::ProcessSP &process_sp,
- const FileSpec &outfile);
+ const FileSpec &outfile,
+ lldb::SaveCoreStyle &core_style);
// ObjectContainer
static bool
/// 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;
#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 {
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; }
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;
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,
eArgTypeCommand,
eArgTypeColumnNum,
eArgTypeModuleUUID,
+ eArgTypeSaveCoreStyle,
eArgTypeLastArg // Always keep this entry as the last entry in this
// enumeration!!
};
/// 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
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,
"permissions",
"flags",
"name",
- "error"])
+ "error",
+ "dirty-pages"])
self.assertIsNotNone(val)
mem_region_dict["name"] = seven.unhexlify(mem_region_dict.get("name", ""))
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);
}
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);
}
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;
// 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(
return result.Succeeded();
}
+
+ CommandOptions m_options;
};
// CommandObjectProcessStatus
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. "
}
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(
{ 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
};
#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"
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();
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)
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(
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;
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);
// 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;
// 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));
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) {
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;
}
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;
+}
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,
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;
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();
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);
}
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);
#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"
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;
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;
+ }
}
}
// 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
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
m_core_range_infos.Sort();
}
-
bool found_main_binary_definitively = false;
addr_t objfile_binary_addr;
}
}
+ // 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)) {
--- /dev/null
+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()
+
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":
def QListThreadsInStopReply(self):
return ""
- def qMemoryRegionInfo(self):
+ def qMemoryRegionInfo(self, addr):
return ""
def qPathComplete(self):
--- /dev/null
+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
--- /dev/null
+"""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.
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+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);
--- /dev/null
+#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);
+}
--- /dev/null
+void to_be_removed_init (int in);
+int to_be_removed (char *main_heap_buf, int main_const_data, int main_dirty_data);
"ptrsize",
"triple",
"vendor",
+ "vm-page-size",
"watchpoint_exceptions_received",
])
#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__)
};
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 {
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);
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;
#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>
__FILE__, __LINE__, p, "Invalid address in qMemoryRegionInfo packet");
}
- DNBRegionInfo region_info = {0, 0, 0};
+ DNBRegionInfo region_info;
DNBProcessMemoryRegionInfo(m_ctx.ProcessID(), address, ®ion_info);
std::ostringstream ostrm;
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());
}
strm << "default_packet_timeout:10;";
#endif
+ strm << "vm-page-size:" << std::dec << vm_page_size << ";";
+
return SendPacket(strm.str());
}