diff --git a/lldb/include/lldb/Core/DumpRegisterInfo.h b/lldb/include/lldb/Core/DumpRegisterInfo.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Core/DumpRegisterInfo.h @@ -0,0 +1,34 @@ +//===-- DumpRegisterInfo.h --------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_CORE_DUMPREGISTERINFO_H +#define LLDB_CORE_DUMPREGISTERINFO_H + +#include +#include +#include + +namespace lldb_private { + +class Stream; +class RegisterContext; +struct RegisterInfo; + +void DumpRegisterInfo(Stream &strm, RegisterContext &ctx, + const RegisterInfo &info); + +// For testing only. Use DumpRegisterInfo instead. +void DoDumpRegisterInfo( + Stream &strm, const char *name, const char *alt_name, uint32_t byte_size, + const std::vector &invalidates, + const std::vector &read_from, + const std::vector> &in_sets); + +} // namespace lldb_private + +#endif // LLDB_CORE_DUMPREGISTERINFO_H diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp --- a/lldb/source/Commands/CommandObjectRegister.cpp +++ b/lldb/source/Commands/CommandObjectRegister.cpp @@ -8,6 +8,7 @@ #include "CommandObjectRegister.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/DumpRegisterInfo.h" #include "lldb/Core/DumpRegisterValue.h" #include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandOptionArgumentTable.h" @@ -398,16 +399,82 @@ } }; +// "register info" +class CommandObjectRegisterInfo : public CommandObjectParsed { +public: + CommandObjectRegisterInfo(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "register info", + "View information about a register.", nullptr, + eCommandRequiresRegContext | + eCommandProcessMustBeLaunched) { + SetHelpLong(R"( +Name The name lldb uses for the register, optionally with an alias. +Size The size of the register in bytes and again in bits. +Invalidates (*) The registers that would be changed if you wrote this + register. For example, writing to a narrower alias of a wider + register would change the value of the wider register. +Read from (*) The registers that the value of this register is constructed + from. For example, a narrower alias of a wider register will be + read from the wider register. +In sets (*) The register sets that contain this register. For example the + PC will be in the "General Purpose Register" set. + +Fields marked with (*) may not always be present. Some information may be +different for the same register when connected to different debug servers.)"); + + CommandArgumentData register_arg; + register_arg.arg_type = eArgTypeRegisterName; + register_arg.arg_repetition = eArgRepeatPlain; + + CommandArgumentEntry arg1; + arg1.push_back(register_arg); + m_arguments.push_back(arg1); + } + + ~CommandObjectRegisterInfo() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + if (!m_exe_ctx.HasProcessScope() || request.GetCursorIndex() != 0) + return; + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), lldb::eRegisterCompletion, request, nullptr); + } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() != 1) { + result.AppendError("register info takes exactly 1 argument: "); + return result.Succeeded(); + } + + llvm::StringRef reg_name = command[0].ref(); + RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext(); + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name); + if (reg_info) { + DumpRegisterInfo(result.GetOutputStream(), *reg_ctx, *reg_info); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else + result.AppendErrorWithFormat("No register found with name '%s'.\n", + reg_name.str().c_str()); + + return result.Succeeded(); + } +}; + // CommandObjectRegister constructor CommandObjectRegister::CommandObjectRegister(CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "register", "Commands to access registers for the current " "thread and stack frame.", - "register [read|write] ...") { + "register [read|write|info] ...") { LoadSubCommand("read", CommandObjectSP(new CommandObjectRegisterRead(interpreter))); LoadSubCommand("write", CommandObjectSP(new CommandObjectRegisterWrite(interpreter))); + LoadSubCommand("info", + CommandObjectSP(new CommandObjectRegisterInfo(interpreter))); } CommandObjectRegister::~CommandObjectRegister() = default; diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -33,6 +33,7 @@ Disassembler.cpp DumpDataExtractor.cpp DumpRegisterValue.cpp + DumpRegisterInfo.cpp DynamicLoader.cpp EmulateInstruction.cpp FileLineResolver.cpp diff --git a/lldb/source/Core/DumpRegisterInfo.cpp b/lldb/source/Core/DumpRegisterInfo.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Core/DumpRegisterInfo.cpp @@ -0,0 +1,109 @@ +//===-- DumpRegisterInfo.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 "lldb/Core/DumpRegisterInfo.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +using SetInfo = std::pair; + +void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx, + const RegisterInfo &info) { + std::vector invalidates; + if (info.invalidate_regs) { + for (uint32_t *inv_regs = info.invalidate_regs; + *inv_regs != LLDB_INVALID_REGNUM; ++inv_regs) { + const RegisterInfo *inv_info = + ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *inv_regs); + assert( + inv_info && + "Register invalidate list refers to a register that does not exist."); + invalidates.push_back(inv_info->name); + } + } + + // We include the index here so that you can use it with "register read -s". + std::vector in_sets; + for (uint32_t set_idx = 0; set_idx < ctx.GetRegisterSetCount(); ++set_idx) { + const RegisterSet *set = ctx.GetRegisterSet(set_idx); + assert(set && "Register set should be valid."); + for (uint32_t reg_idx = 0; reg_idx < set->num_registers; ++reg_idx) { + const RegisterInfo *set_reg_info = + ctx.GetRegisterInfoAtIndex(set->registers[reg_idx]); + assert(set_reg_info && "Register info should be valid."); + + if (set_reg_info == &info) { + in_sets.push_back({set->name, set_idx}); + break; + } + } + } + + std::vector read_from; + if (info.value_regs) { + for (uint32_t *read_regs = info.value_regs; + *read_regs != LLDB_INVALID_REGNUM; ++read_regs) { + const RegisterInfo *read_info = + ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *read_regs); + assert(read_info && "Register value registers list refers to a register " + "that does not exist."); + read_from.push_back(read_info->name); + } + } + + DoDumpRegisterInfo(strm, info.name, info.alt_name, info.byte_size, + invalidates, read_from, in_sets); +} + +template +static void DumpList(Stream &strm, const char *title, + const std::vector &list, + std::function emitter) { + if (list.empty()) + return; + + strm.EOL(); + strm << title; + bool first = true; + for (ElementType elem : list) { + if (!first) + strm << ", "; + first = false; + emitter(strm, elem); + } +} + +void lldb_private::DoDumpRegisterInfo( + Stream &strm, const char *name, const char *alt_name, uint32_t byte_size, + const std::vector &invalidates, + const std::vector &read_from, + const std::vector &in_sets) { + strm << " Name: " << name; + if (alt_name) + strm << " (" << alt_name << ")"; + strm.EOL(); + + // Size in bits may seem obvious for the usual 32 or 64 bit registers. + // When we get to vector registers, then scalable vector registers, it is very + // useful to know without the user doing extra work. + strm.Printf(" Size: %d bytes (%d bits)", byte_size, byte_size * 8); + + std::function emit_str = + [](Stream &strm, const char *s) { strm << s; }; + DumpList(strm, "Invalidates: ", invalidates, emit_str); + DumpList(strm, " Read from: ", read_from, emit_str); + + std::function emit_set = [](Stream &strm, + SetInfo info) { + strm.Printf("%s (index %d)", info.first, info.second); + }; + DumpList(strm, " In sets: ", in_sets, emit_set); +} diff --git a/lldb/test/API/commands/register/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register/register_command/TestRegisters.py --- a/lldb/test/API/commands/register/register/register_command/TestRegisters.py +++ b/lldb/test/API/commands/register/register/register_command/TestRegisters.py @@ -567,3 +567,41 @@ error=True, substrs=["error: Register not found for 'blub'."], ) + + def test_info_unknown_register(self): + self.build() + self.common_setup() + + self.expect("register info blub", error=True, + substrs=["error: No register found with name 'blub'."]) + + def test_info_many_registers(self): + self.build() + self.common_setup() + + # Only 1 register allowed at this time. + self.expect("register info abc def", error=True, + substrs=["error: register info takes exactly 1 argument"]) + + @skipIf(archs=no_match(["aarch64"])) + def test_info_register(self): + # The behaviour of this command is generic but the specific registers + # are not, so this is written for AArch64 only. + # Text alignment and ordering are checked in the DumpRegisterInfo unit tests. + self.build() + self.common_setup() + + # Standard register. Doesn't invalidate anything, doesn't have an alias. + self.expect("register info x1", substrs=[ + "Name: x1", + "Size: 8 bytes (64 bits)", + "In sets: General Purpose Registers"]) + self.expect("register info x1", substrs=["Invalidates:", "Name: x1 ("], + matching=False) + + # These registers invalidate others as they are subsets of those registers. + self.expect("register info w1", substrs=["Invalidates: x1"]) + self.expect("register info s0", substrs=["Invalidates: v0, d0"]) + + # This has an alternative name according to the ABI. + self.expect("register info x30", substrs=["Name: lr (x30)"]) diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -2,6 +2,7 @@ CommunicationTest.cpp DiagnosticEventTest.cpp DumpDataExtractorTest.cpp + DumpRegisterInfoTest.cpp FileSpecListTest.cpp FormatEntityTest.cpp MangledTest.cpp diff --git a/lldb/unittests/Core/DumpRegisterInfoTest.cpp b/lldb/unittests/Core/DumpRegisterInfoTest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Core/DumpRegisterInfoTest.cpp @@ -0,0 +1,82 @@ +//===-- DumpRegisterInfoTest.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 "lldb/Core/DumpRegisterInfo.h" +#include "lldb/Utility/StreamString.h" +#include "gtest/gtest.h" + +using namespace lldb_private; + +TEST(DoDumpRegisterInfoTest, MinimumInfo) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)"); +} + +TEST(DoDumpRegisterInfoTest, AltName) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo (bar)\n" + " Size: 4 bytes (32 bits)"); +} + +TEST(DoDumpRegisterInfoTest, Invalidates) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + "Invalidates: foo2"); + + strm.Clear(); + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3", "foo4"}, {}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + "Invalidates: foo2, foo3, foo4"); +} + +TEST(DoDumpRegisterInfoTest, ReadFrom) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + " Read from: foo1"); + + strm.Clear(); + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1", "foo2", "foo3"}, {}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + " Read from: foo1, foo2, foo3"); +} + +TEST(DoDumpRegisterInfoTest, InSets) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {{"set1", 101}}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + " In sets: set1 (index 101)"); + + strm.Clear(); + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, + {{"set1", 0}, {"set2", 1}, {"set3", 2}}); + ASSERT_EQ(strm.GetString(), + " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + " In sets: set1 (index 0), set2 (index 1), set3 (index 2)"); +} + +TEST(DoDumpRegisterInfoTest, MaxInfo) { + StreamString strm; + DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3"}, + {"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}}); + ASSERT_EQ(strm.GetString(), " Name: foo\n" + " Size: 4 bytes (32 bits)\n" + "Invalidates: foo2, foo3\n" + " Read from: foo3, foo4\n" + " In sets: set1 (index 1), set2 (index 2)"); +}