Author: David Spickett
Date: 2023-06-21T08:48:18Z
New Revision: ba85f206fe6f2fec9dd5fee55bf2a1e966fc37aa

URL: 
https://github.com/llvm/llvm-project/commit/ba85f206fe6f2fec9dd5fee55bf2a1e966fc37aa
DIFF: 
https://github.com/llvm/llvm-project/commit/ba85f206fe6f2fec9dd5fee55bf2a1e966fc37aa.diff

LOG: [lldb] Add "register info" command

This adds a new command that will show all the information lldb
knows about a register.
```
(lldb) register info s0
       Name: s0
       Size: 4 bytes (32 bits)
Invalidates: v0, d0
  Read from: v0
    In sets: Floating Point Registers (index 1)
```

Currently it only allows a single register, and we get the
information from the RegisterInfo structure.

For those of us who know the architecture well, this information
is all pretty obvious. For those who don't, it's nice to have it
at a glance without leaving the debugger.

I hope to have more in depth information to show here in the future,
which will be of wider use.

Reviewed By: jasonmolenda

Differential Revision: https://reviews.llvm.org/D152916

Added: 
    lldb/include/lldb/Core/DumpRegisterInfo.h
    lldb/source/Core/DumpRegisterInfo.cpp
    lldb/unittests/Core/DumpRegisterInfoTest.cpp

Modified: 
    lldb/source/Commands/CommandObjectRegister.cpp
    lldb/source/Core/CMakeLists.txt
    lldb/test/API/commands/register/register/register_command/TestRegisters.py
    lldb/unittests/Core/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Core/DumpRegisterInfo.h 
b/lldb/include/lldb/Core/DumpRegisterInfo.h
new file mode 100644
index 0000000000000..e257581864b5a
--- /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 <stdint.h>
+#include <utility>
+#include <vector>
+
+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<const char *> &invalidates,
+    const std::vector<const char *> &read_from,
+    const std::vector<std::pair<const char *, uint32_t>> &in_sets);
+
+} // namespace lldb_private
+
+#endif // LLDB_CORE_DUMPREGISTERINFO_H

diff  --git a/lldb/source/Commands/CommandObjectRegister.cpp 
b/lldb/source/Commands/CommandObjectRegister.cpp
index c288dbef33499..814219d186186 100644
--- 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 @@ class CommandObjectRegisterWrite : public 
CommandObjectParsed {
   }
 };
 
+// "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
+
diff erent for the same register when connected to 
diff erent 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: <reg-name>");
+      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
index 74ad4556c9ed7..f5b44d3a3d73a 100644
--- a/lldb/source/Core/CMakeLists.txt
+++ b/lldb/source/Core/CMakeLists.txt
@@ -33,6 +33,7 @@ add_lldb_library(lldbCore
   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
index 0000000000000..e7083e5938f52
--- /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<const char *, uint32_t>;
+
+void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
+                                    const RegisterInfo &info) {
+  std::vector<const char *> 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<SetInfo> 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<const char *> 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 <typename ElementType>
+static void DumpList(Stream &strm, const char *title,
+                     const std::vector<ElementType> &list,
+                     std::function<void(Stream &, ElementType)> 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<const char *> &invalidates,
+    const std::vector<const char *> &read_from,
+    const std::vector<SetInfo> &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<void(Stream &, const char *)> 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<void(Stream &, SetInfo)> 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
index 269b48be6a9e8..dfb71438dc23b 100644
--- 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 @@ def test_write_unknown_register(self):
             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
index bda0ac750fe84..b3cddd150635b 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -2,6 +2,7 @@ add_lldb_unittest(LLDBCoreTests
   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
index 0000000000000..4e9cb53cb637c
--- /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)");
+}


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to