[lldb] Add register field tables to the "register info" command

This teaches DumpRegisterInfo to generate a table from the register
flags type. It just calls a method on RegisterFlags.

As such, the extra tests are minimal and only show that the intergration
works. Exhaustive formatting tests are done with RegisterFlags itself.

Example:
```
(lldb) register info cpsr
       Name: cpsr
       Size: 4 bytes (32 bits)
    In sets: general (index 0)

| 31 | 30 | 29 | 28 | 27-26 | 25  | 24  | 23  | 22  | 21 | 20 | 19-13 |  12  | 11-10 | 9 | 8 | 7 | 6 | 5 |  4  | 3-2 | 1 | 0  |
|----|----|----|----|-------|-----|-----|-----|-----|----|----|-------|------|-------|---|---|---|---|---|-----|-----|---|----|
| N  | Z  | C  | V  |       | TCO | DIT | UAO | PAN | SS | IL |       | SSBS |       | D | A | I | F |   | nRW | EL  |   | SP |
```

LLDB limits the max terminal width to 80 chars by default.
So to get that full width output you will need to change the "term-width"
setting to something higher.

Reviewed By: jasonmolenda

Differential Revision: https://reviews.llvm.org/D152918
This commit is contained in:
David Spickett 2023-06-05 17:02:46 +00:00
parent 6f7c9d1e17
commit bcfe5a52a3
6 changed files with 81 additions and 16 deletions

View File

@ -18,16 +18,18 @@ namespace lldb_private {
class Stream;
class RegisterContext;
struct RegisterInfo;
class RegisterFlags;
void DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
const RegisterInfo &info);
const RegisterInfo &info, uint32_t terminal_width);
// 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);
const std::vector<std::pair<const char *, uint32_t>> &in_sets,
const RegisterFlags *flags_type, uint32_t terminal_width);
} // namespace lldb_private

View File

@ -11,6 +11,7 @@
#include "lldb/Core/DumpRegisterInfo.h"
#include "lldb/Core/DumpRegisterValue.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionGroupFormat.h"
@ -418,6 +419,8 @@ Read from (*) The registers that the value of this register is constructed
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 (*) A table of the names and bit positions of the values contained
in this register.
Fields marked with (*) may not always be present. Some information may be
different for the same register when connected to different debug servers.)");
@ -453,7 +456,9 @@ protected:
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);
DumpRegisterInfo(
result.GetOutputStream(), *reg_ctx, *reg_info,
GetCommandInterpreter().GetDebugger().GetTerminalWidth());
result.SetStatus(eReturnStatusSuccessFinishResult);
} else
result.AppendErrorWithFormat("No register found with name '%s'.\n",

View File

@ -8,6 +8,7 @@
#include "lldb/Core/DumpRegisterInfo.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/RegisterFlags.h"
#include "lldb/Utility/Stream.h"
using namespace lldb;
@ -16,7 +17,8 @@ using namespace lldb_private;
using SetInfo = std::pair<const char *, uint32_t>;
void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
const RegisterInfo &info) {
const RegisterInfo &info,
uint32_t terminal_width) {
std::vector<const char *> invalidates;
if (info.invalidate_regs) {
for (uint32_t *inv_regs = info.invalidate_regs;
@ -60,7 +62,8 @@ void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
}
DoDumpRegisterInfo(strm, info.name, info.alt_name, info.byte_size,
invalidates, read_from, in_sets);
invalidates, read_from, in_sets, info.flags_type,
terminal_width);
}
template <typename ElementType>
@ -85,7 +88,8 @@ 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) {
const std::vector<SetInfo> &in_sets, const RegisterFlags *flags_type,
uint32_t terminal_width) {
strm << " Name: " << name;
if (alt_name)
strm << " (" << alt_name << ")";
@ -106,4 +110,7 @@ void lldb_private::DoDumpRegisterInfo(
strm.Printf("%s (index %d)", info.first, info.second);
};
DumpList(strm, " In sets: ", in_sets, emit_set);
if (flags_type)
strm.Printf("\n\n%s", flags_type->AsTable(terminal_width).c_str());
}

View File

@ -587,7 +587,8 @@ class RegisterCommandsTestCase(TestBase):
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.
# Text alignment and ordering are checked in the DumpRegisterInfo and
# RegisterFlags unit tests.
self.build()
self.common_setup()

View File

@ -605,3 +605,31 @@ class TestXMLRegisterFlags(GDBRemoteTestBase):
self.expect("register read x0", substrs=["(correct = 1)"])
self.expect("register read x1", substrs=["(correct = 1)"])
@skipIfXmlSupportMissing
@skipIfRemote
def test_flags_in_register_info(self):
# See RegisterFlags for comprehensive formatting tests.
self.setup_flags_test(
'<field name="D" start="0" end="7"/>'
'<field name="C" start="8" end="15"/>'
'<field name="B" start="16" end="23"/>'
'<field name="A" start="24" end="31"/>'
)
# The table should split according to terminal width.
self.runCmd("settings set term-width 17")
self.expect("register info cpsr",
substrs=[
" Name: cpsr\n"
" Size: 4 bytes (32 bits)\n"
" In sets: general (index 0)\n"
"\n"
"| 31-24 | 23-16 |\n"
"|-------|-------|\n"
"| A | B |\n"
"\n"
"| 15-8 | 7-0 |\n"
"|------|-----|\n"
"| C | D |"])

View File

@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "lldb/Core/DumpRegisterInfo.h"
#include "lldb/Target/RegisterFlags.h"
#include "lldb/Utility/StreamString.h"
#include "gtest/gtest.h"
@ -14,27 +15,28 @@ using namespace lldb_private;
TEST(DoDumpRegisterInfoTest, MinimumInfo) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, nullptr, 0);
ASSERT_EQ(strm.GetString(), " Name: foo\n"
" Size: 4 bytes (32 bits)");
}
TEST(DoDumpRegisterInfoTest, AltName) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {});
DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {}, nullptr, 0);
ASSERT_EQ(strm.GetString(), " Name: foo (bar)\n"
" Size: 4 bytes (32 bits)");
}
TEST(DoDumpRegisterInfoTest, Invalidates) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {}, nullptr, 0);
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"}, {}, {});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3", "foo4"}, {}, {},
nullptr, 0);
ASSERT_EQ(strm.GetString(), " Name: foo\n"
" Size: 4 bytes (32 bits)\n"
"Invalidates: foo2, foo3, foo4");
@ -42,13 +44,14 @@ TEST(DoDumpRegisterInfoTest, Invalidates) {
TEST(DoDumpRegisterInfoTest, ReadFrom) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {}, nullptr, 0);
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"}, {});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1", "foo2", "foo3"}, {},
nullptr, 0);
ASSERT_EQ(strm.GetString(), " Name: foo\n"
" Size: 4 bytes (32 bits)\n"
" Read from: foo1, foo2, foo3");
@ -56,14 +59,15 @@ TEST(DoDumpRegisterInfoTest, ReadFrom) {
TEST(DoDumpRegisterInfoTest, InSets) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {{"set1", 101}});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {{"set1", 101}}, nullptr,
0);
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}});
{{"set1", 0}, {"set2", 1}, {"set3", 2}}, nullptr, 0);
ASSERT_EQ(strm.GetString(),
" Name: foo\n"
" Size: 4 bytes (32 bits)\n"
@ -73,10 +77,28 @@ TEST(DoDumpRegisterInfoTest, InSets) {
TEST(DoDumpRegisterInfoTest, MaxInfo) {
StreamString strm;
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3"},
{"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}});
{"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}}, nullptr, 0);
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)");
}
TEST(DoDumpRegisterInfoTest, FieldsTable) {
// This is thoroughly tested in RegisterFlags itself, only checking the
// integration here.
StreamString strm;
RegisterFlags flags(
"", 4,
{RegisterFlags::Field("A", 24, 31), RegisterFlags::Field("B", 16, 23),
RegisterFlags::Field("C", 8, 15), RegisterFlags::Field("D", 0, 7)});
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, &flags, 100);
ASSERT_EQ(strm.GetString(), " Name: foo\n"
" Size: 4 bytes (32 bits)\n"
"\n"
"| 31-24 | 23-16 | 15-8 | 7-0 |\n"
"|-------|-------|------|-----|\n"
"| A | B | C | D |");
}