| //===-- CommandObjectMemory.cpp ---------------------------------*- C++ -*-===// | |
| // | |
| // 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/Debugger.h" | |
| #include "lldb/Core/DumpDataExtractor.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/Core/ValueObjectMemory.h" | |
| #include "lldb/DataFormatters/ValueObjectPrinter.h" | |
| #include "lldb/Expression/ExpressionVariable.h" | |
| #include "lldb/Host/OptionParser.h" | |
| #include "lldb/Interpreter/CommandInterpreter.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 "lldb/lldb-private.h" | |
| #include <cinttypes> | |
| #include <memory> | |
| 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<OptionDefinition> 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<lldb_private::SymbolFile *> 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<LanguageType> languages_to_check; | |
| if (language_for_type != eLanguageTypeUnknown) { | |
| languages_to_check.insert(language_for_type); | |
| } else { | |
| languages_to_check = Language::GetSupportedLanguages(); | |
| } | |
| std::set<CompilerType> user_defined_types; | |
| for (auto lang : languages_to_check) { | |
| if (auto *persistent_vars = | |
| target->GetPersistentExpressionStateForLanguage(lang)) { | |
| if (llvm::Optional<CompilerType> 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<uint64_t> 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<uint64_t> 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<DataBufferHeap>(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<DataBufferHeap>( | |
| (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<DataBufferHeap>(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<Stream> 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<StreamFile>(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<OptionDefinition> 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<uint64_t> 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<size_t> 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<OptionDefinition> 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; } | |
| bool UIntValueIsValidForSize(uint64_t uval64, size_t total_byte_size) { | |
| if (total_byte_size > 8) | |
| return false; | |
| if (total_byte_size == 8) | |
| return true; | |
| const uint64_t max = ((uint64_t)1 << (uint64_t)(total_byte_size * 8)) - 1; | |
| return uval64 <= max; | |
| } | |
| bool SIntValueIsValidForSize(int64_t sval64, size_t total_byte_size) { | |
| if (total_byte_size > 8) | |
| return false; | |
| if (total_byte_size == 8) | |
| return true; | |
| const int64_t max = ((int64_t)1 << (uint64_t)(total_byte_size * 8 - 1)) - 1; | |
| const int64_t min = ~(max); | |
| return min <= sval64 && sval64 <= max; | |
| } | |
| 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 (!UIntValueIsValidForSize(uval64, item_byte_size)) { | |
| 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 (!UIntValueIsValidForSize(uval64, item_byte_size)) { | |
| 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 (!SIntValueIsValidForSize(sval64, item_byte_size)) { | |
| 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 (!UIntValueIsValidForSize(uval64, item_byte_size)) { | |
| 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 (!UIntValueIsValidForSize(uval64, item_byte_size)) { | |
| 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) { | |
| 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); | |
| } else { | |
| if (command.GetArgumentCount() == 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); | |
| } | |
| } | |
| 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.AppendMessageWithFormat( | |
| "[0x%16.16" PRIx64 "-0x%16.16" PRIx64 ") %c%c%c%s%s%s%s\n", | |
| range_info.GetRange().GetRangeBase(), | |
| range_info.GetRange().GetRangeEnd(), | |
| range_info.GetReadable() ? 'r' : '-', | |
| range_info.GetWritable() ? 'w' : '-', | |
| range_info.GetExecutable() ? 'x' : '-', | |
| name ? " " : "", name.AsCString(""), | |
| section_name ? " " : "", section_name.AsCString("")); | |
| m_prev_end_addr = range_info.GetRange().GetRangeEnd(); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| result.SetStatus(eReturnStatusFailed); | |
| result.AppendErrorWithFormat("%s\n", error.AsCString()); | |
| } | |
| } | |
| } else { | |
| m_prev_end_addr = LLDB_INVALID_ADDRESS; | |
| result.AppendError("invalid process"); | |
| result.SetStatus(eReturnStatusFailed); | |
| } | |
| return result.Succeeded(); | |
| } | |
| 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 <subcommand> [<subcommand-options>]") { | |
| 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; |