//===-- CommandObjectMemory.cpp -------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "CommandObjectMemory.h" #include "lldb/Core/DumpDataExtractor.h" #include "lldb/Core/Section.h" #include "lldb/Core/ValueObjectMemory.h" #include "lldb/Expression/ExpressionVariable.h" #include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionArgParser.h" #include "lldb/Interpreter/OptionGroupFormat.h" #include "lldb/Interpreter/OptionGroupOutputFile.h" #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" #include "lldb/Interpreter/OptionValueLanguage.h" #include "lldb/Interpreter/OptionValueString.h" #include "lldb/Interpreter/Options.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Target/Language.h" #include "lldb/Target/MemoryHistory.h" #include "lldb/Target/MemoryRegionInfo.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/DataBufferLLVM.h" #include "lldb/Utility/StreamString.h" #include "llvm/Support/MathExtras.h" #include #include using namespace lldb; using namespace lldb_private; #define LLDB_OPTIONS_memory_read #include "CommandOptions.inc" class OptionGroupReadMemory : public OptionGroup { public: OptionGroupReadMemory() : m_num_per_line(1, 1), m_output_as_binary(false), m_view_as_type(), m_offset(0, 0), m_language_for_type(eLanguageTypeUnknown) {} ~OptionGroupReadMemory() override = default; llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_memory_read_options); } Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, ExecutionContext *execution_context) override { Status error; const int short_option = g_memory_read_options[option_idx].short_option; switch (short_option) { case 'l': error = m_num_per_line.SetValueFromString(option_value); if (m_num_per_line.GetCurrentValue() == 0) error.SetErrorStringWithFormat( "invalid value for --num-per-line option '%s'", option_value.str().c_str()); break; case 'b': m_output_as_binary = true; break; case 't': error = m_view_as_type.SetValueFromString(option_value); break; case 'r': m_force = true; break; case 'x': error = m_language_for_type.SetValueFromString(option_value); break; case 'E': error = m_offset.SetValueFromString(option_value); break; default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { m_num_per_line.Clear(); m_output_as_binary = false; m_view_as_type.Clear(); m_force = false; m_offset.Clear(); m_language_for_type.Clear(); } Status FinalizeSettings(Target *target, OptionGroupFormat &format_options) { Status error; OptionValueUInt64 &byte_size_value = format_options.GetByteSizeValue(); OptionValueUInt64 &count_value = format_options.GetCountValue(); const bool byte_size_option_set = byte_size_value.OptionWasSet(); const bool num_per_line_option_set = m_num_per_line.OptionWasSet(); const bool count_option_set = format_options.GetCountValue().OptionWasSet(); switch (format_options.GetFormat()) { default: break; case eFormatBoolean: if (!byte_size_option_set) byte_size_value = 1; if (!num_per_line_option_set) m_num_per_line = 1; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatCString: break; case eFormatInstruction: if (count_option_set) byte_size_value = target->GetArchitecture().GetMaximumOpcodeByteSize(); m_num_per_line = 1; break; case eFormatAddressInfo: if (!byte_size_option_set) byte_size_value = target->GetArchitecture().GetAddressByteSize(); m_num_per_line = 1; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatPointer: byte_size_value = target->GetArchitecture().GetAddressByteSize(); if (!num_per_line_option_set) m_num_per_line = 4; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatBinary: case eFormatFloat: case eFormatOctal: case eFormatDecimal: case eFormatEnum: case eFormatUnicode8: case eFormatUnicode16: case eFormatUnicode32: case eFormatUnsigned: case eFormatHexFloat: if (!byte_size_option_set) byte_size_value = 4; if (!num_per_line_option_set) m_num_per_line = 1; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatBytes: case eFormatBytesWithASCII: if (byte_size_option_set) { if (byte_size_value > 1) error.SetErrorStringWithFormat( "display format (bytes/bytes with ASCII) conflicts with the " "specified byte size %" PRIu64 "\n" "\tconsider using a different display format or don't specify " "the byte size.", byte_size_value.GetCurrentValue()); } else byte_size_value = 1; if (!num_per_line_option_set) m_num_per_line = 16; if (!count_option_set) format_options.GetCountValue() = 32; break; case eFormatCharArray: case eFormatChar: case eFormatCharPrintable: if (!byte_size_option_set) byte_size_value = 1; if (!num_per_line_option_set) m_num_per_line = 32; if (!count_option_set) format_options.GetCountValue() = 64; break; case eFormatComplex: if (!byte_size_option_set) byte_size_value = 8; if (!num_per_line_option_set) m_num_per_line = 1; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatComplexInteger: if (!byte_size_option_set) byte_size_value = 8; if (!num_per_line_option_set) m_num_per_line = 1; if (!count_option_set) format_options.GetCountValue() = 8; break; case eFormatHex: if (!byte_size_option_set) byte_size_value = 4; if (!num_per_line_option_set) { switch (byte_size_value) { case 1: case 2: m_num_per_line = 8; break; case 4: m_num_per_line = 4; break; case 8: m_num_per_line = 2; break; default: m_num_per_line = 1; break; } } if (!count_option_set) count_value = 8; break; case eFormatVectorOfChar: case eFormatVectorOfSInt8: case eFormatVectorOfUInt8: case eFormatVectorOfSInt16: case eFormatVectorOfUInt16: case eFormatVectorOfSInt32: case eFormatVectorOfUInt32: case eFormatVectorOfSInt64: case eFormatVectorOfUInt64: case eFormatVectorOfFloat16: case eFormatVectorOfFloat32: case eFormatVectorOfFloat64: case eFormatVectorOfUInt128: if (!byte_size_option_set) byte_size_value = 128; if (!num_per_line_option_set) m_num_per_line = 1; if (!count_option_set) count_value = 4; break; } return error; } bool AnyOptionWasSet() const { return m_num_per_line.OptionWasSet() || m_output_as_binary || m_view_as_type.OptionWasSet() || m_offset.OptionWasSet() || m_language_for_type.OptionWasSet(); } OptionValueUInt64 m_num_per_line; bool m_output_as_binary; OptionValueString m_view_as_type; bool m_force; OptionValueUInt64 m_offset; OptionValueLanguage m_language_for_type; }; // Read memory from the inferior process class CommandObjectMemoryRead : public CommandObjectParsed { public: CommandObjectMemoryRead(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "memory read", "Read from the memory of the current target process.", nullptr, eCommandRequiresTarget | eCommandProcessMustBePaused), m_option_group(), m_format_options(eFormatBytesWithASCII, 1, 8), m_memory_options(), m_outfile_options(), m_varobj_options(), m_next_addr(LLDB_INVALID_ADDRESS), m_prev_byte_size(0), m_prev_format_options(eFormatBytesWithASCII, 1, 8), m_prev_memory_options(), m_prev_outfile_options(), m_prev_varobj_options() { CommandArgumentEntry arg1; CommandArgumentEntry arg2; CommandArgumentData start_addr_arg; CommandArgumentData end_addr_arg; // Define the first (and only) variant of this arg. start_addr_arg.arg_type = eArgTypeAddressOrExpression; start_addr_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg1.push_back(start_addr_arg); // Define the first (and only) variant of this arg. end_addr_arg.arg_type = eArgTypeAddressOrExpression; end_addr_arg.arg_repetition = eArgRepeatOptional; // There is only one variant this argument could be; put it into the // argument entry. arg2.push_back(end_addr_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg1); m_arguments.push_back(arg2); // Add the "--format" and "--count" options to group 1 and 3 m_option_group.Append(&m_format_options, OptionGroupFormat::OPTION_GROUP_FORMAT | OptionGroupFormat::OPTION_GROUP_COUNT, LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3); m_option_group.Append(&m_format_options, OptionGroupFormat::OPTION_GROUP_GDB_FMT, LLDB_OPT_SET_1 | LLDB_OPT_SET_3); // Add the "--size" option to group 1 and 2 m_option_group.Append(&m_format_options, OptionGroupFormat::OPTION_GROUP_SIZE, LLDB_OPT_SET_1 | LLDB_OPT_SET_2); m_option_group.Append(&m_memory_options); m_option_group.Append(&m_outfile_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1 | LLDB_OPT_SET_2 | LLDB_OPT_SET_3); m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_3); m_option_group.Finalize(); } ~CommandObjectMemoryRead() override = default; Options *GetOptions() override { return &m_option_group; } const char *GetRepeatCommand(Args ¤t_command_args, uint32_t index) override { return m_cmd_name.c_str(); } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "target" for validity as eCommandRequiresTarget ensures // it is valid Target *target = m_exe_ctx.GetTargetPtr(); const size_t argc = command.GetArgumentCount(); if ((argc == 0 && m_next_addr == LLDB_INVALID_ADDRESS) || argc > 2) { result.AppendErrorWithFormat("%s takes a start address expression with " "an optional end address expression.\n", m_cmd_name.c_str()); result.AppendRawWarning("Expressions should be quoted if they contain " "spaces or other special characters.\n"); result.SetStatus(eReturnStatusFailed); return false; } CompilerType compiler_type; Status error; const char *view_as_type_cstr = m_memory_options.m_view_as_type.GetCurrentValue(); if (view_as_type_cstr && view_as_type_cstr[0]) { // We are viewing memory as a type const bool exact_match = false; TypeList type_list; uint32_t reference_count = 0; uint32_t pointer_count = 0; size_t idx; #define ALL_KEYWORDS \ KEYWORD("const") \ KEYWORD("volatile") \ KEYWORD("restrict") \ KEYWORD("struct") \ KEYWORD("class") \ KEYWORD("union") #define KEYWORD(s) s, static const char *g_keywords[] = {ALL_KEYWORDS}; #undef KEYWORD #define KEYWORD(s) (sizeof(s) - 1), static const int g_keyword_lengths[] = {ALL_KEYWORDS}; #undef KEYWORD #undef ALL_KEYWORDS static size_t g_num_keywords = sizeof(g_keywords) / sizeof(const char *); std::string type_str(view_as_type_cstr); // Remove all instances of g_keywords that are followed by spaces for (size_t i = 0; i < g_num_keywords; ++i) { const char *keyword = g_keywords[i]; int keyword_len = g_keyword_lengths[i]; idx = 0; while ((idx = type_str.find(keyword, idx)) != std::string::npos) { if (type_str[idx + keyword_len] == ' ' || type_str[idx + keyword_len] == '\t') { type_str.erase(idx, keyword_len + 1); idx = 0; } else { idx += keyword_len; } } } bool done = type_str.empty(); // idx = type_str.find_first_not_of(" \t"); if (idx > 0 && idx != std::string::npos) type_str.erase(0, idx); while (!done) { // Strip trailing spaces if (type_str.empty()) done = true; else { switch (type_str[type_str.size() - 1]) { case '*': ++pointer_count; LLVM_FALLTHROUGH; case ' ': case '\t': type_str.erase(type_str.size() - 1); break; case '&': if (reference_count == 0) { reference_count = 1; type_str.erase(type_str.size() - 1); } else { result.AppendErrorWithFormat("invalid type string: '%s'\n", view_as_type_cstr); result.SetStatus(eReturnStatusFailed); return false; } break; default: done = true; break; } } } llvm::DenseSet searched_symbol_files; ConstString lookup_type_name(type_str.c_str()); StackFrame *frame = m_exe_ctx.GetFramePtr(); ModuleSP search_first; if (frame) { search_first = frame->GetSymbolContext(eSymbolContextModule).module_sp; } target->GetImages().FindTypes(search_first.get(), lookup_type_name, exact_match, 1, searched_symbol_files, type_list); if (type_list.GetSize() == 0 && lookup_type_name.GetCString()) { LanguageType language_for_type = m_memory_options.m_language_for_type.GetCurrentValue(); std::set languages_to_check; if (language_for_type != eLanguageTypeUnknown) { languages_to_check.insert(language_for_type); } else { languages_to_check = Language::GetSupportedLanguages(); } std::set user_defined_types; for (auto lang : languages_to_check) { if (auto *persistent_vars = target->GetPersistentExpressionStateForLanguage(lang)) { if (llvm::Optional type = persistent_vars->GetCompilerTypeFromPersistentDecl( lookup_type_name)) { user_defined_types.emplace(*type); } } } if (user_defined_types.size() > 1) { result.AppendErrorWithFormat( "Mutiple types found matching raw type '%s', please disambiguate " "by specifying the language with -x", lookup_type_name.GetCString()); result.SetStatus(eReturnStatusFailed); return false; } if (user_defined_types.size() == 1) { compiler_type = *user_defined_types.begin(); } } if (!compiler_type.IsValid()) { if (type_list.GetSize() == 0) { result.AppendErrorWithFormat("unable to find any types that match " "the raw type '%s' for full type '%s'\n", lookup_type_name.GetCString(), view_as_type_cstr); result.SetStatus(eReturnStatusFailed); return false; } else { TypeSP type_sp(type_list.GetTypeAtIndex(0)); compiler_type = type_sp->GetFullCompilerType(); } } while (pointer_count > 0) { CompilerType pointer_type = compiler_type.GetPointerType(); if (pointer_type.IsValid()) compiler_type = pointer_type; else { result.AppendError("unable make a pointer type\n"); result.SetStatus(eReturnStatusFailed); return false; } --pointer_count; } llvm::Optional size = compiler_type.GetByteSize(nullptr); if (!size) { result.AppendErrorWithFormat( "unable to get the byte size of the type '%s'\n", view_as_type_cstr); result.SetStatus(eReturnStatusFailed); return false; } m_format_options.GetByteSizeValue() = *size; if (!m_format_options.GetCountValue().OptionWasSet()) m_format_options.GetCountValue() = 1; } else { error = m_memory_options.FinalizeSettings(target, m_format_options); } // Look for invalid combinations of settings if (error.Fail()) { result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } lldb::addr_t addr; size_t total_byte_size = 0; if (argc == 0) { // Use the last address and byte size and all options as they were if no // options have been set addr = m_next_addr; total_byte_size = m_prev_byte_size; compiler_type = m_prev_compiler_type; if (!m_format_options.AnyOptionWasSet() && !m_memory_options.AnyOptionWasSet() && !m_outfile_options.AnyOptionWasSet() && !m_varobj_options.AnyOptionWasSet()) { m_format_options = m_prev_format_options; m_memory_options = m_prev_memory_options; m_outfile_options = m_prev_outfile_options; m_varobj_options = m_prev_varobj_options; } } size_t item_count = m_format_options.GetCountValue().GetCurrentValue(); // TODO For non-8-bit byte addressable architectures this needs to be // revisited to fully support all lldb's range of formatting options. // Furthermore code memory reads (for those architectures) will not be // correctly formatted even w/o formatting options. size_t item_byte_size = target->GetArchitecture().GetDataByteSize() > 1 ? target->GetArchitecture().GetDataByteSize() : m_format_options.GetByteSizeValue().GetCurrentValue(); const size_t num_per_line = m_memory_options.m_num_per_line.GetCurrentValue(); if (total_byte_size == 0) { total_byte_size = item_count * item_byte_size; if (total_byte_size == 0) total_byte_size = 32; } if (argc > 0) addr = OptionArgParser::ToAddress(&m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); if (addr == LLDB_INVALID_ADDRESS) { result.AppendError("invalid start address expression."); result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } if (argc == 2) { lldb::addr_t end_addr = OptionArgParser::ToAddress( &m_exe_ctx, command[1].ref(), LLDB_INVALID_ADDRESS, nullptr); if (end_addr == LLDB_INVALID_ADDRESS) { result.AppendError("invalid end address expression."); result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } else if (end_addr <= addr) { result.AppendErrorWithFormat( "end address (0x%" PRIx64 ") must be greater that the start address (0x%" PRIx64 ").\n", end_addr, addr); result.SetStatus(eReturnStatusFailed); return false; } else if (m_format_options.GetCountValue().OptionWasSet()) { result.AppendErrorWithFormat( "specify either the end address (0x%" PRIx64 ") or the count (--count %" PRIu64 "), not both.\n", end_addr, (uint64_t)item_count); result.SetStatus(eReturnStatusFailed); return false; } total_byte_size = end_addr - addr; item_count = total_byte_size / item_byte_size; } uint32_t max_unforced_size = target->GetMaximumMemReadSize(); if (total_byte_size > max_unforced_size && !m_memory_options.m_force) { result.AppendErrorWithFormat( "Normally, \'memory read\' will not read over %" PRIu32 " bytes of data.\n", max_unforced_size); result.AppendErrorWithFormat( "Please use --force to override this restriction just once.\n"); result.AppendErrorWithFormat("or set target.max-memory-read-size if you " "will often need a larger limit.\n"); return false; } DataBufferSP data_sp; size_t bytes_read = 0; if (compiler_type.GetOpaqueQualType()) { // Make sure we don't display our type as ASCII bytes like the default // memory read if (!m_format_options.GetFormatValue().OptionWasSet()) m_format_options.GetFormatValue().SetCurrentValue(eFormatDefault); llvm::Optional size = compiler_type.GetByteSize(nullptr); if (!size) { result.AppendError("can't get size of type"); return false; } bytes_read = *size * m_format_options.GetCountValue().GetCurrentValue(); if (argc > 0) addr = addr + (*size * m_memory_options.m_offset.GetCurrentValue()); } else if (m_format_options.GetFormatValue().GetCurrentValue() != eFormatCString) { data_sp = std::make_shared(total_byte_size, '\0'); if (data_sp->GetBytes() == nullptr) { result.AppendErrorWithFormat( "can't allocate 0x%" PRIx32 " bytes for the memory read buffer, specify a smaller size to read", (uint32_t)total_byte_size); result.SetStatus(eReturnStatusFailed); return false; } Address address(addr, nullptr); bytes_read = target->ReadMemory(address, false, data_sp->GetBytes(), data_sp->GetByteSize(), error); if (bytes_read == 0) { const char *error_cstr = error.AsCString(); if (error_cstr && error_cstr[0]) { result.AppendError(error_cstr); } else { result.AppendErrorWithFormat( "failed to read memory from 0x%" PRIx64 ".\n", addr); } result.SetStatus(eReturnStatusFailed); return false; } if (bytes_read < total_byte_size) result.AppendWarningWithFormat( "Not all bytes (%" PRIu64 "/%" PRIu64 ") were able to be read from 0x%" PRIx64 ".\n", (uint64_t)bytes_read, (uint64_t)total_byte_size, addr); } else { // we treat c-strings as a special case because they do not have a fixed // size if (m_format_options.GetByteSizeValue().OptionWasSet() && !m_format_options.HasGDBFormat()) item_byte_size = m_format_options.GetByteSizeValue().GetCurrentValue(); else item_byte_size = target->GetMaximumSizeOfStringSummary(); if (!m_format_options.GetCountValue().OptionWasSet()) item_count = 1; data_sp = std::make_shared( (item_byte_size + 1) * item_count, '\0'); // account for NULLs as necessary if (data_sp->GetBytes() == nullptr) { result.AppendErrorWithFormat( "can't allocate 0x%" PRIx64 " bytes for the memory read buffer, specify a smaller size to read", (uint64_t)((item_byte_size + 1) * item_count)); result.SetStatus(eReturnStatusFailed); return false; } uint8_t *data_ptr = data_sp->GetBytes(); auto data_addr = addr; auto count = item_count; item_count = 0; bool break_on_no_NULL = false; while (item_count < count) { std::string buffer; buffer.resize(item_byte_size + 1, 0); Status error; size_t read = target->ReadCStringFromMemory(data_addr, &buffer[0], item_byte_size + 1, error); if (error.Fail()) { result.AppendErrorWithFormat( "failed to read memory from 0x%" PRIx64 ".\n", addr); result.SetStatus(eReturnStatusFailed); return false; } if (item_byte_size == read) { result.AppendWarningWithFormat( "unable to find a NULL terminated string at 0x%" PRIx64 ".Consider increasing the maximum read length.\n", data_addr); --read; break_on_no_NULL = true; } else ++read; // account for final NULL byte memcpy(data_ptr, &buffer[0], read); data_ptr += read; data_addr += read; bytes_read += read; item_count++; // if we break early we know we only read item_count // strings if (break_on_no_NULL) break; } data_sp = std::make_shared(data_sp->GetBytes(), bytes_read + 1); } m_next_addr = addr + bytes_read; m_prev_byte_size = bytes_read; m_prev_format_options = m_format_options; m_prev_memory_options = m_memory_options; m_prev_outfile_options = m_outfile_options; m_prev_varobj_options = m_varobj_options; m_prev_compiler_type = compiler_type; std::unique_ptr output_stream_storage; Stream *output_stream_p = nullptr; const FileSpec &outfile_spec = m_outfile_options.GetFile().GetCurrentValue(); std::string path = outfile_spec.GetPath(); if (outfile_spec) { auto open_options = File::eOpenOptionWrite | File::eOpenOptionCanCreate; const bool append = m_outfile_options.GetAppend().GetCurrentValue(); if (append) open_options |= File::eOpenOptionAppend; auto outfile = FileSystem::Instance().Open(outfile_spec, open_options); if (outfile) { auto outfile_stream_up = std::make_unique(std::move(outfile.get())); if (m_memory_options.m_output_as_binary) { const size_t bytes_written = outfile_stream_up->Write(data_sp->GetBytes(), bytes_read); if (bytes_written > 0) { result.GetOutputStream().Printf( "%zi bytes %s to '%s'\n", bytes_written, append ? "appended" : "written", path.c_str()); return true; } else { result.AppendErrorWithFormat("Failed to write %" PRIu64 " bytes to '%s'.\n", (uint64_t)bytes_read, path.c_str()); result.SetStatus(eReturnStatusFailed); return false; } } else { // We are going to write ASCII to the file just point the // output_stream to our outfile_stream... output_stream_storage = std::move(outfile_stream_up); output_stream_p = output_stream_storage.get(); } } else { result.AppendErrorWithFormat("Failed to open file '%s' for %s:\n", path.c_str(), append ? "append" : "write"); result.AppendError(llvm::toString(outfile.takeError())); result.SetStatus(eReturnStatusFailed); return false; } } else { output_stream_p = &result.GetOutputStream(); } ExecutionContextScope *exe_scope = m_exe_ctx.GetBestExecutionContextScope(); if (compiler_type.GetOpaqueQualType()) { for (uint32_t i = 0; i < item_count; ++i) { addr_t item_addr = addr + (i * item_byte_size); Address address(item_addr); StreamString name_strm; name_strm.Printf("0x%" PRIx64, item_addr); ValueObjectSP valobj_sp(ValueObjectMemory::Create( exe_scope, name_strm.GetString(), address, compiler_type)); if (valobj_sp) { Format format = m_format_options.GetFormat(); if (format != eFormatDefault) valobj_sp->SetFormat(format); DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( eLanguageRuntimeDescriptionDisplayVerbosityFull, format)); valobj_sp->Dump(*output_stream_p, options); } else { result.AppendErrorWithFormat( "failed to create a value object for: (%s) %s\n", view_as_type_cstr, name_strm.GetData()); result.SetStatus(eReturnStatusFailed); return false; } } return true; } result.SetStatus(eReturnStatusSuccessFinishResult); DataExtractor data(data_sp, target->GetArchitecture().GetByteOrder(), target->GetArchitecture().GetAddressByteSize(), target->GetArchitecture().GetDataByteSize()); Format format = m_format_options.GetFormat(); if (((format == eFormatChar) || (format == eFormatCharPrintable)) && (item_byte_size != 1)) { // if a count was not passed, or it is 1 if (!m_format_options.GetCountValue().OptionWasSet() || item_count == 1) { // this turns requests such as // memory read -fc -s10 -c1 *charPtrPtr // which make no sense (what is a char of size 10?) into a request for // fetching 10 chars of size 1 from the same memory location format = eFormatCharArray; item_count = item_byte_size; item_byte_size = 1; } else { // here we passed a count, and it was not 1 so we have a byte_size and // a count we could well multiply those, but instead let's just fail result.AppendErrorWithFormat( "reading memory as characters of size %" PRIu64 " is not supported", (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } } assert(output_stream_p); size_t bytes_dumped = DumpDataExtractor( data, output_stream_p, 0, format, item_byte_size, item_count, num_per_line / target->GetArchitecture().GetDataByteSize(), addr, 0, 0, exe_scope); m_next_addr = addr + bytes_dumped; output_stream_p->EOL(); return true; } OptionGroupOptions m_option_group; OptionGroupFormat m_format_options; OptionGroupReadMemory m_memory_options; OptionGroupOutputFile m_outfile_options; OptionGroupValueObjectDisplay m_varobj_options; lldb::addr_t m_next_addr; lldb::addr_t m_prev_byte_size; OptionGroupFormat m_prev_format_options; OptionGroupReadMemory m_prev_memory_options; OptionGroupOutputFile m_prev_outfile_options; OptionGroupValueObjectDisplay m_prev_varobj_options; CompilerType m_prev_compiler_type; }; #define LLDB_OPTIONS_memory_find #include "CommandOptions.inc" // Find the specified data in memory class CommandObjectMemoryFind : public CommandObjectParsed { public: class OptionGroupFindMemory : public OptionGroup { public: OptionGroupFindMemory() : OptionGroup(), m_count(1), m_offset(0) {} ~OptionGroupFindMemory() override = default; llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_memory_find_options); } Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, ExecutionContext *execution_context) override { Status error; const int short_option = g_memory_find_options[option_idx].short_option; switch (short_option) { case 'e': m_expr.SetValueFromString(option_value); break; case 's': m_string.SetValueFromString(option_value); break; case 'c': if (m_count.SetValueFromString(option_value).Fail()) error.SetErrorString("unrecognized value for count"); break; case 'o': if (m_offset.SetValueFromString(option_value).Fail()) error.SetErrorString("unrecognized value for dump-offset"); break; default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { m_expr.Clear(); m_string.Clear(); m_count.Clear(); } OptionValueString m_expr; OptionValueString m_string; OptionValueUInt64 m_count; OptionValueUInt64 m_offset; }; CommandObjectMemoryFind(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "memory find", "Find a value in the memory of the current target process.", nullptr, eCommandRequiresProcess | eCommandProcessMustBeLaunched), m_option_group(), m_memory_options() { CommandArgumentEntry arg1; CommandArgumentEntry arg2; CommandArgumentData addr_arg; CommandArgumentData value_arg; // Define the first (and only) variant of this arg. addr_arg.arg_type = eArgTypeAddressOrExpression; addr_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg1.push_back(addr_arg); // Define the first (and only) variant of this arg. value_arg.arg_type = eArgTypeAddressOrExpression; value_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg2.push_back(value_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg1); m_arguments.push_back(arg2); m_option_group.Append(&m_memory_options); m_option_group.Finalize(); } ~CommandObjectMemoryFind() override = default; Options *GetOptions() override { return &m_option_group; } protected: class ProcessMemoryIterator { public: ProcessMemoryIterator(ProcessSP process_sp, lldb::addr_t base) : m_process_sp(process_sp), m_base_addr(base), m_is_valid(true) { lldbassert(process_sp.get() != nullptr); } bool IsValid() { return m_is_valid; } uint8_t operator[](lldb::addr_t offset) { if (!IsValid()) return 0; uint8_t retval = 0; Status error; if (0 == m_process_sp->ReadMemory(m_base_addr + offset, &retval, 1, error)) { m_is_valid = false; return 0; } return retval; } private: ProcessSP m_process_sp; lldb::addr_t m_base_addr; bool m_is_valid; }; bool DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "process" for validity as eCommandRequiresProcess // ensures it is valid Process *process = m_exe_ctx.GetProcessPtr(); const size_t argc = command.GetArgumentCount(); if (argc != 2) { result.AppendError("two addresses needed for memory find"); return false; } Status error; lldb::addr_t low_addr = OptionArgParser::ToAddress( &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); if (low_addr == LLDB_INVALID_ADDRESS || error.Fail()) { result.AppendError("invalid low address"); return false; } lldb::addr_t high_addr = OptionArgParser::ToAddress( &m_exe_ctx, command[1].ref(), LLDB_INVALID_ADDRESS, &error); if (high_addr == LLDB_INVALID_ADDRESS || error.Fail()) { result.AppendError("invalid high address"); return false; } if (high_addr <= low_addr) { result.AppendError( "starting address must be smaller than ending address"); return false; } lldb::addr_t found_location = LLDB_INVALID_ADDRESS; DataBufferHeap buffer; if (m_memory_options.m_string.OptionWasSet()) buffer.CopyData(m_memory_options.m_string.GetStringValue()); else if (m_memory_options.m_expr.OptionWasSet()) { StackFrame *frame = m_exe_ctx.GetFramePtr(); ValueObjectSP result_sp; if ((eExpressionCompleted == process->GetTarget().EvaluateExpression( m_memory_options.m_expr.GetStringValue(), frame, result_sp)) && result_sp) { uint64_t value = result_sp->GetValueAsUnsigned(0); llvm::Optional size = result_sp->GetCompilerType().GetByteSize(nullptr); if (!size) return false; switch (*size) { case 1: { uint8_t byte = (uint8_t)value; buffer.CopyData(&byte, 1); } break; case 2: { uint16_t word = (uint16_t)value; buffer.CopyData(&word, 2); } break; case 4: { uint32_t lword = (uint32_t)value; buffer.CopyData(&lword, 4); } break; case 8: { buffer.CopyData(&value, 8); } break; case 3: case 5: case 6: case 7: result.AppendError("unknown type. pass a string instead"); return false; default: result.AppendError( "result size larger than 8 bytes. pass a string instead"); return false; } } else { result.AppendError( "expression evaluation failed. pass a string instead"); return false; } } else { result.AppendError( "please pass either a block of text, or an expression to evaluate."); return false; } size_t count = m_memory_options.m_count.GetCurrentValue(); found_location = low_addr; bool ever_found = false; while (count) { found_location = FastSearch(found_location, high_addr, buffer.GetBytes(), buffer.GetByteSize()); if (found_location == LLDB_INVALID_ADDRESS) { if (!ever_found) { result.AppendMessage("data not found within the range.\n"); result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); } else result.AppendMessage("no more matches within the range.\n"); break; } result.AppendMessageWithFormat("data found at location: 0x%" PRIx64 "\n", found_location); DataBufferHeap dumpbuffer(32, 0); process->ReadMemory( found_location + m_memory_options.m_offset.GetCurrentValue(), dumpbuffer.GetBytes(), dumpbuffer.GetByteSize(), error); if (!error.Fail()) { DataExtractor data(dumpbuffer.GetBytes(), dumpbuffer.GetByteSize(), process->GetByteOrder(), process->GetAddressByteSize()); DumpDataExtractor( data, &result.GetOutputStream(), 0, lldb::eFormatBytesWithASCII, 1, dumpbuffer.GetByteSize(), 16, found_location + m_memory_options.m_offset.GetCurrentValue(), 0, 0); result.GetOutputStream().EOL(); } --count; found_location++; ever_found = true; } result.SetStatus(lldb::eReturnStatusSuccessFinishResult); return true; } lldb::addr_t FastSearch(lldb::addr_t low, lldb::addr_t high, uint8_t *buffer, size_t buffer_size) { const size_t region_size = high - low; if (region_size < buffer_size) return LLDB_INVALID_ADDRESS; std::vector bad_char_heuristic(256, buffer_size); ProcessSP process_sp = m_exe_ctx.GetProcessSP(); ProcessMemoryIterator iterator(process_sp, low); for (size_t idx = 0; idx < buffer_size - 1; idx++) { decltype(bad_char_heuristic)::size_type bcu_idx = buffer[idx]; bad_char_heuristic[bcu_idx] = buffer_size - idx - 1; } for (size_t s = 0; s <= (region_size - buffer_size);) { int64_t j = buffer_size - 1; while (j >= 0 && buffer[j] == iterator[s + j]) j--; if (j < 0) return low + s; else s += bad_char_heuristic[iterator[s + buffer_size - 1]]; } return LLDB_INVALID_ADDRESS; } OptionGroupOptions m_option_group; OptionGroupFindMemory m_memory_options; }; #define LLDB_OPTIONS_memory_write #include "CommandOptions.inc" // Write memory to the inferior process class CommandObjectMemoryWrite : public CommandObjectParsed { public: class OptionGroupWriteMemory : public OptionGroup { public: OptionGroupWriteMemory() : OptionGroup() {} ~OptionGroupWriteMemory() override = default; llvm::ArrayRef GetDefinitions() override { return llvm::makeArrayRef(g_memory_write_options); } Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, ExecutionContext *execution_context) override { Status error; const int short_option = g_memory_write_options[option_idx].short_option; switch (short_option) { case 'i': m_infile.SetFile(option_value, FileSpec::Style::native); FileSystem::Instance().Resolve(m_infile); if (!FileSystem::Instance().Exists(m_infile)) { m_infile.Clear(); error.SetErrorStringWithFormat("input file does not exist: '%s'", option_value.str().c_str()); } break; case 'o': { if (option_value.getAsInteger(0, m_infile_offset)) { m_infile_offset = 0; error.SetErrorStringWithFormat("invalid offset string '%s'", option_value.str().c_str()); } } break; default: llvm_unreachable("Unimplemented option"); } return error; } void OptionParsingStarting(ExecutionContext *execution_context) override { m_infile.Clear(); m_infile_offset = 0; } FileSpec m_infile; off_t m_infile_offset; }; CommandObjectMemoryWrite(CommandInterpreter &interpreter) : CommandObjectParsed( interpreter, "memory write", "Write to the memory of the current target process.", nullptr, eCommandRequiresProcess | eCommandProcessMustBeLaunched), m_option_group(), m_format_options(eFormatBytes, 1, UINT64_MAX), m_memory_options() { CommandArgumentEntry arg1; CommandArgumentEntry arg2; CommandArgumentData addr_arg; CommandArgumentData value_arg; // Define the first (and only) variant of this arg. addr_arg.arg_type = eArgTypeAddress; addr_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg1.push_back(addr_arg); // Define the first (and only) variant of this arg. value_arg.arg_type = eArgTypeValue; value_arg.arg_repetition = eArgRepeatPlus; // There is only one variant this argument could be; put it into the // argument entry. arg2.push_back(value_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg1); m_arguments.push_back(arg2); m_option_group.Append(&m_format_options, OptionGroupFormat::OPTION_GROUP_FORMAT, LLDB_OPT_SET_1); m_option_group.Append(&m_format_options, OptionGroupFormat::OPTION_GROUP_SIZE, LLDB_OPT_SET_1 | LLDB_OPT_SET_2); m_option_group.Append(&m_memory_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_2); m_option_group.Finalize(); } ~CommandObjectMemoryWrite() override = default; Options *GetOptions() override { return &m_option_group; } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "process" for validity as eCommandRequiresProcess // ensures it is valid Process *process = m_exe_ctx.GetProcessPtr(); const size_t argc = command.GetArgumentCount(); if (m_memory_options.m_infile) { if (argc < 1) { result.AppendErrorWithFormat( "%s takes a destination address when writing file contents.\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } } else if (argc < 2) { result.AppendErrorWithFormat( "%s takes a destination address and at least one value.\n", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } StreamString buffer( Stream::eBinary, process->GetTarget().GetArchitecture().GetAddressByteSize(), process->GetTarget().GetArchitecture().GetByteOrder()); OptionValueUInt64 &byte_size_value = m_format_options.GetByteSizeValue(); size_t item_byte_size = byte_size_value.GetCurrentValue(); Status error; lldb::addr_t addr = OptionArgParser::ToAddress( &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); if (addr == LLDB_INVALID_ADDRESS) { result.AppendError("invalid address expression\n"); result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } if (m_memory_options.m_infile) { size_t length = SIZE_MAX; if (item_byte_size > 1) length = item_byte_size; auto data_sp = FileSystem::Instance().CreateDataBuffer( m_memory_options.m_infile.GetPath(), length, m_memory_options.m_infile_offset); if (data_sp) { length = data_sp->GetByteSize(); if (length > 0) { Status error; size_t bytes_written = process->WriteMemory(addr, data_sp->GetBytes(), length, error); if (bytes_written == length) { // All bytes written result.GetOutputStream().Printf( "%" PRIu64 " bytes were written to 0x%" PRIx64 "\n", (uint64_t)bytes_written, addr); result.SetStatus(eReturnStatusSuccessFinishResult); } else if (bytes_written > 0) { // Some byte written result.GetOutputStream().Printf( "%" PRIu64 " bytes of %" PRIu64 " requested were written to 0x%" PRIx64 "\n", (uint64_t)bytes_written, (uint64_t)length, addr); result.SetStatus(eReturnStatusSuccessFinishResult); } else { result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString()); result.SetStatus(eReturnStatusFailed); } } } else { result.AppendErrorWithFormat("Unable to read contents of file.\n"); result.SetStatus(eReturnStatusFailed); } return result.Succeeded(); } else if (item_byte_size == 0) { if (m_format_options.GetFormat() == eFormatPointer) item_byte_size = buffer.GetAddressByteSize(); else item_byte_size = 1; } command.Shift(); // shift off the address argument uint64_t uval64; int64_t sval64; bool success = false; for (auto &entry : command) { switch (m_format_options.GetFormat()) { case kNumFormats: case eFormatFloat: // TODO: add support for floats soon case eFormatCharPrintable: case eFormatBytesWithASCII: case eFormatComplex: case eFormatEnum: case eFormatUnicode8: case eFormatUnicode16: case eFormatUnicode32: case eFormatVectorOfChar: case eFormatVectorOfSInt8: case eFormatVectorOfUInt8: case eFormatVectorOfSInt16: case eFormatVectorOfUInt16: case eFormatVectorOfSInt32: case eFormatVectorOfUInt32: case eFormatVectorOfSInt64: case eFormatVectorOfUInt64: case eFormatVectorOfFloat16: case eFormatVectorOfFloat32: case eFormatVectorOfFloat64: case eFormatVectorOfUInt128: case eFormatOSType: case eFormatComplexInteger: case eFormatAddressInfo: case eFormatHexFloat: case eFormatInstruction: case eFormatVoid: result.AppendError("unsupported format for writing memory"); result.SetStatus(eReturnStatusFailed); return false; case eFormatDefault: case eFormatBytes: case eFormatHex: case eFormatHexUppercase: case eFormatPointer: { // Decode hex bytes // Be careful, getAsInteger with a radix of 16 rejects "0xab" so we // have to special case that: bool success = false; if (entry.ref().startswith("0x")) success = !entry.ref().getAsInteger(0, uval64); if (!success) success = !entry.ref().getAsInteger(16, uval64); if (!success) { result.AppendErrorWithFormat( "'%s' is not a valid hex string value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } else if (!llvm::isUIntN(item_byte_size * 8, uval64)) { result.AppendErrorWithFormat("Value 0x%" PRIx64 " is too large to fit in a %" PRIu64 " byte unsigned integer value.\n", uval64, (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(uval64, item_byte_size); break; } case eFormatBoolean: uval64 = OptionArgParser::ToBoolean(entry.ref(), false, &success); if (!success) { result.AppendErrorWithFormat( "'%s' is not a valid boolean string value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(uval64, item_byte_size); break; case eFormatBinary: if (entry.ref().getAsInteger(2, uval64)) { result.AppendErrorWithFormat( "'%s' is not a valid binary string value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } else if (!llvm::isUIntN(item_byte_size * 8, uval64)) { result.AppendErrorWithFormat("Value 0x%" PRIx64 " is too large to fit in a %" PRIu64 " byte unsigned integer value.\n", uval64, (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(uval64, item_byte_size); break; case eFormatCharArray: case eFormatChar: case eFormatCString: { if (entry.ref().empty()) break; size_t len = entry.ref().size(); // Include the NULL for C strings... if (m_format_options.GetFormat() == eFormatCString) ++len; Status error; if (process->WriteMemory(addr, entry.c_str(), len, error) == len) { addr += len; } else { result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } break; } case eFormatDecimal: if (entry.ref().getAsInteger(0, sval64)) { result.AppendErrorWithFormat( "'%s' is not a valid signed decimal value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } else if (!llvm::isIntN(item_byte_size * 8, sval64)) { result.AppendErrorWithFormat( "Value %" PRIi64 " is too large or small to fit in a %" PRIu64 " byte signed integer value.\n", sval64, (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(sval64, item_byte_size); break; case eFormatUnsigned: if (!entry.ref().getAsInteger(0, uval64)) { result.AppendErrorWithFormat( "'%s' is not a valid unsigned decimal string value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } else if (!llvm::isUIntN(item_byte_size * 8, uval64)) { result.AppendErrorWithFormat("Value %" PRIu64 " is too large to fit in a %" PRIu64 " byte unsigned integer value.\n", uval64, (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(uval64, item_byte_size); break; case eFormatOctal: if (entry.ref().getAsInteger(8, uval64)) { result.AppendErrorWithFormat( "'%s' is not a valid octal string value.\n", entry.c_str()); result.SetStatus(eReturnStatusFailed); return false; } else if (!llvm::isUIntN(item_byte_size * 8, uval64)) { result.AppendErrorWithFormat("Value %" PRIo64 " is too large to fit in a %" PRIu64 " byte unsigned integer value.\n", uval64, (uint64_t)item_byte_size); result.SetStatus(eReturnStatusFailed); return false; } buffer.PutMaxHex64(uval64, item_byte_size); break; } } if (!buffer.GetString().empty()) { Status error; if (process->WriteMemory(addr, buffer.GetString().data(), buffer.GetString().size(), error) == buffer.GetString().size()) return true; else { result.AppendErrorWithFormat("Memory write to 0x%" PRIx64 " failed: %s.\n", addr, error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } } return true; } OptionGroupOptions m_option_group; OptionGroupFormat m_format_options; OptionGroupWriteMemory m_memory_options; }; // Get malloc/free history of a memory address. class CommandObjectMemoryHistory : public CommandObjectParsed { public: CommandObjectMemoryHistory(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "memory history", "Print recorded stack traces for " "allocation/deallocation events " "associated with an address.", nullptr, eCommandRequiresTarget | eCommandRequiresProcess | eCommandProcessMustBePaused | eCommandProcessMustBeLaunched) { CommandArgumentEntry arg1; CommandArgumentData addr_arg; // Define the first (and only) variant of this arg. addr_arg.arg_type = eArgTypeAddress; addr_arg.arg_repetition = eArgRepeatPlain; // There is only one variant this argument could be; put it into the // argument entry. arg1.push_back(addr_arg); // Push the data for the first argument into the m_arguments vector. m_arguments.push_back(arg1); } ~CommandObjectMemoryHistory() override = default; const char *GetRepeatCommand(Args ¤t_command_args, uint32_t index) override { return m_cmd_name.c_str(); } protected: bool DoExecute(Args &command, CommandReturnObject &result) override { const size_t argc = command.GetArgumentCount(); if (argc == 0 || argc > 1) { result.AppendErrorWithFormat("%s takes an address expression", m_cmd_name.c_str()); result.SetStatus(eReturnStatusFailed); return false; } Status error; lldb::addr_t addr = OptionArgParser::ToAddress( &m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error); if (addr == LLDB_INVALID_ADDRESS) { result.AppendError("invalid address expression"); result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } Stream *output_stream = &result.GetOutputStream(); const ProcessSP &process_sp = m_exe_ctx.GetProcessSP(); const MemoryHistorySP &memory_history = MemoryHistory::FindPlugin(process_sp); if (!memory_history) { result.AppendError("no available memory history provider"); result.SetStatus(eReturnStatusFailed); return false; } HistoryThreads thread_list = memory_history->GetHistoryThreads(addr); const bool stop_format = false; for (auto thread : thread_list) { thread->GetStatus(*output_stream, 0, UINT32_MAX, 0, stop_format); } result.SetStatus(eReturnStatusSuccessFinishResult); return true; } }; // CommandObjectMemoryRegion #pragma mark CommandObjectMemoryRegion class CommandObjectMemoryRegion : public CommandObjectParsed { public: CommandObjectMemoryRegion(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "memory region", "Get information on the memory region containing " "an address in the current target process.", "memory region ADDR", eCommandRequiresProcess | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched), m_prev_end_addr(LLDB_INVALID_ADDRESS) {} ~CommandObjectMemoryRegion() override = default; protected: bool DoExecute(Args &command, CommandReturnObject &result) override { ProcessSP process_sp = m_exe_ctx.GetProcessSP(); if (!process_sp) { m_prev_end_addr = LLDB_INVALID_ADDRESS; result.AppendError("invalid process"); result.SetStatus(eReturnStatusFailed); return false; } Status error; lldb::addr_t load_addr = m_prev_end_addr; m_prev_end_addr = LLDB_INVALID_ADDRESS; const size_t argc = command.GetArgumentCount(); if (argc > 1 || (argc == 0 && load_addr == LLDB_INVALID_ADDRESS)) { result.AppendErrorWithFormat("'%s' takes one argument:\nUsage: %s\n", m_cmd_name.c_str(), m_cmd_syntax.c_str()); result.SetStatus(eReturnStatusFailed); return false; } if (argc == 1) { auto load_addr_str = command[0].ref(); load_addr = OptionArgParser::ToAddress(&m_exe_ctx, load_addr_str, LLDB_INVALID_ADDRESS, &error); if (error.Fail() || load_addr == LLDB_INVALID_ADDRESS) { result.AppendErrorWithFormat("invalid address argument \"%s\": %s\n", command[0].c_str(), error.AsCString()); result.SetStatus(eReturnStatusFailed); return false; } } lldb_private::MemoryRegionInfo range_info; error = process_sp->GetMemoryRegionInfo(load_addr, range_info); if (error.Success()) { lldb_private::Address addr; ConstString name = range_info.GetName(); ConstString section_name; if (process_sp->GetTarget().ResolveLoadAddress(load_addr, addr)) { SectionSP section_sp(addr.GetSection()); if (section_sp) { // Got the top most section, not the deepest section while (section_sp->GetParent()) section_sp = section_sp->GetParent(); section_name = section_sp->GetName(); } } result.AppendMessageWithFormatv( "[{0:x16}-{1:x16}) {2:r}{3:w}{4:x}{5}{6}{7}{8}", range_info.GetRange().GetRangeBase(), range_info.GetRange().GetRangeEnd(), range_info.GetReadable(), range_info.GetWritable(), range_info.GetExecutable(), name ? " " : "", name, section_name ? " " : "", section_name); MemoryRegionInfo::OptionalBool memory_tagged = range_info.GetMemoryTagged(); if (memory_tagged == MemoryRegionInfo::OptionalBool::eYes) result.AppendMessage("memory tagging: enabled"); m_prev_end_addr = range_info.GetRange().GetRangeEnd(); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } result.SetStatus(eReturnStatusFailed); result.AppendErrorWithFormat("%s\n", error.AsCString()); return false; } const char *GetRepeatCommand(Args ¤t_command_args, uint32_t index) override { // If we repeat this command, repeat it without any arguments so we can // show the next memory range return m_cmd_name.c_str(); } lldb::addr_t m_prev_end_addr; }; // CommandObjectMemory CommandObjectMemory::CommandObjectMemory(CommandInterpreter &interpreter) : CommandObjectMultiword( interpreter, "memory", "Commands for operating on memory in the current target process.", "memory []") { LoadSubCommand("find", CommandObjectSP(new CommandObjectMemoryFind(interpreter))); LoadSubCommand("read", CommandObjectSP(new CommandObjectMemoryRead(interpreter))); LoadSubCommand("write", CommandObjectSP(new CommandObjectMemoryWrite(interpreter))); LoadSubCommand("history", CommandObjectSP(new CommandObjectMemoryHistory(interpreter))); LoadSubCommand("region", CommandObjectSP(new CommandObjectMemoryRegion(interpreter))); } CommandObjectMemory::~CommandObjectMemory() = default;