Skip to content

Commit ba85f20

Browse files
committed
[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
1 parent edc9e2c commit ba85f20

File tree

7 files changed

+333
-1
lines changed

7 files changed

+333
-1
lines changed
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===-- DumpRegisterInfo.h --------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_CORE_DUMPREGISTERINFO_H
10+
#define LLDB_CORE_DUMPREGISTERINFO_H
11+
12+
#include <stdint.h>
13+
#include <utility>
14+
#include <vector>
15+
16+
namespace lldb_private {
17+
18+
class Stream;
19+
class RegisterContext;
20+
struct RegisterInfo;
21+
22+
void DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
23+
const RegisterInfo &info);
24+
25+
// For testing only. Use DumpRegisterInfo instead.
26+
void DoDumpRegisterInfo(
27+
Stream &strm, const char *name, const char *alt_name, uint32_t byte_size,
28+
const std::vector<const char *> &invalidates,
29+
const std::vector<const char *> &read_from,
30+
const std::vector<std::pair<const char *, uint32_t>> &in_sets);
31+
32+
} // namespace lldb_private
33+
34+
#endif // LLDB_CORE_DUMPREGISTERINFO_H

lldb/source/Commands/CommandObjectRegister.cpp

+68-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "CommandObjectRegister.h"
1010
#include "lldb/Core/Debugger.h"
11+
#include "lldb/Core/DumpRegisterInfo.h"
1112
#include "lldb/Core/DumpRegisterValue.h"
1213
#include "lldb/Host/OptionParser.h"
1314
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
@@ -398,16 +399,82 @@ class CommandObjectRegisterWrite : public CommandObjectParsed {
398399
}
399400
};
400401

402+
// "register info"
403+
class CommandObjectRegisterInfo : public CommandObjectParsed {
404+
public:
405+
CommandObjectRegisterInfo(CommandInterpreter &interpreter)
406+
: CommandObjectParsed(interpreter, "register info",
407+
"View information about a register.", nullptr,
408+
eCommandRequiresRegContext |
409+
eCommandProcessMustBeLaunched) {
410+
SetHelpLong(R"(
411+
Name The name lldb uses for the register, optionally with an alias.
412+
Size The size of the register in bytes and again in bits.
413+
Invalidates (*) The registers that would be changed if you wrote this
414+
register. For example, writing to a narrower alias of a wider
415+
register would change the value of the wider register.
416+
Read from (*) The registers that the value of this register is constructed
417+
from. For example, a narrower alias of a wider register will be
418+
read from the wider register.
419+
In sets (*) The register sets that contain this register. For example the
420+
PC will be in the "General Purpose Register" set.
421+
422+
Fields marked with (*) may not always be present. Some information may be
423+
different for the same register when connected to different debug servers.)");
424+
425+
CommandArgumentData register_arg;
426+
register_arg.arg_type = eArgTypeRegisterName;
427+
register_arg.arg_repetition = eArgRepeatPlain;
428+
429+
CommandArgumentEntry arg1;
430+
arg1.push_back(register_arg);
431+
m_arguments.push_back(arg1);
432+
}
433+
434+
~CommandObjectRegisterInfo() override = default;
435+
436+
void
437+
HandleArgumentCompletion(CompletionRequest &request,
438+
OptionElementVector &opt_element_vector) override {
439+
if (!m_exe_ctx.HasProcessScope() || request.GetCursorIndex() != 0)
440+
return;
441+
CommandCompletions::InvokeCommonCompletionCallbacks(
442+
GetCommandInterpreter(), lldb::eRegisterCompletion, request, nullptr);
443+
}
444+
445+
protected:
446+
bool DoExecute(Args &command, CommandReturnObject &result) override {
447+
if (command.GetArgumentCount() != 1) {
448+
result.AppendError("register info takes exactly 1 argument: <reg-name>");
449+
return result.Succeeded();
450+
}
451+
452+
llvm::StringRef reg_name = command[0].ref();
453+
RegisterContext *reg_ctx = m_exe_ctx.GetRegisterContext();
454+
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
455+
if (reg_info) {
456+
DumpRegisterInfo(result.GetOutputStream(), *reg_ctx, *reg_info);
457+
result.SetStatus(eReturnStatusSuccessFinishResult);
458+
} else
459+
result.AppendErrorWithFormat("No register found with name '%s'.\n",
460+
reg_name.str().c_str());
461+
462+
return result.Succeeded();
463+
}
464+
};
465+
401466
// CommandObjectRegister constructor
402467
CommandObjectRegister::CommandObjectRegister(CommandInterpreter &interpreter)
403468
: CommandObjectMultiword(interpreter, "register",
404469
"Commands to access registers for the current "
405470
"thread and stack frame.",
406-
"register [read|write] ...") {
471+
"register [read|write|info] ...") {
407472
LoadSubCommand("read",
408473
CommandObjectSP(new CommandObjectRegisterRead(interpreter)));
409474
LoadSubCommand("write",
410475
CommandObjectSP(new CommandObjectRegisterWrite(interpreter)));
476+
LoadSubCommand("info",
477+
CommandObjectSP(new CommandObjectRegisterInfo(interpreter)));
411478
}
412479

413480
CommandObjectRegister::~CommandObjectRegister() = default;

lldb/source/Core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_lldb_library(lldbCore
3333
Disassembler.cpp
3434
DumpDataExtractor.cpp
3535
DumpRegisterValue.cpp
36+
DumpRegisterInfo.cpp
3637
DynamicLoader.cpp
3738
EmulateInstruction.cpp
3839
FileLineResolver.cpp

lldb/source/Core/DumpRegisterInfo.cpp

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===-- DumpRegisterInfo.cpp ----------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lldb/Core/DumpRegisterInfo.h"
10+
#include "lldb/Target/RegisterContext.h"
11+
#include "lldb/Utility/Stream.h"
12+
13+
using namespace lldb;
14+
using namespace lldb_private;
15+
16+
using SetInfo = std::pair<const char *, uint32_t>;
17+
18+
void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
19+
const RegisterInfo &info) {
20+
std::vector<const char *> invalidates;
21+
if (info.invalidate_regs) {
22+
for (uint32_t *inv_regs = info.invalidate_regs;
23+
*inv_regs != LLDB_INVALID_REGNUM; ++inv_regs) {
24+
const RegisterInfo *inv_info =
25+
ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *inv_regs);
26+
assert(
27+
inv_info &&
28+
"Register invalidate list refers to a register that does not exist.");
29+
invalidates.push_back(inv_info->name);
30+
}
31+
}
32+
33+
// We include the index here so that you can use it with "register read -s".
34+
std::vector<SetInfo> in_sets;
35+
for (uint32_t set_idx = 0; set_idx < ctx.GetRegisterSetCount(); ++set_idx) {
36+
const RegisterSet *set = ctx.GetRegisterSet(set_idx);
37+
assert(set && "Register set should be valid.");
38+
for (uint32_t reg_idx = 0; reg_idx < set->num_registers; ++reg_idx) {
39+
const RegisterInfo *set_reg_info =
40+
ctx.GetRegisterInfoAtIndex(set->registers[reg_idx]);
41+
assert(set_reg_info && "Register info should be valid.");
42+
43+
if (set_reg_info == &info) {
44+
in_sets.push_back({set->name, set_idx});
45+
break;
46+
}
47+
}
48+
}
49+
50+
std::vector<const char *> read_from;
51+
if (info.value_regs) {
52+
for (uint32_t *read_regs = info.value_regs;
53+
*read_regs != LLDB_INVALID_REGNUM; ++read_regs) {
54+
const RegisterInfo *read_info =
55+
ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *read_regs);
56+
assert(read_info && "Register value registers list refers to a register "
57+
"that does not exist.");
58+
read_from.push_back(read_info->name);
59+
}
60+
}
61+
62+
DoDumpRegisterInfo(strm, info.name, info.alt_name, info.byte_size,
63+
invalidates, read_from, in_sets);
64+
}
65+
66+
template <typename ElementType>
67+
static void DumpList(Stream &strm, const char *title,
68+
const std::vector<ElementType> &list,
69+
std::function<void(Stream &, ElementType)> emitter) {
70+
if (list.empty())
71+
return;
72+
73+
strm.EOL();
74+
strm << title;
75+
bool first = true;
76+
for (ElementType elem : list) {
77+
if (!first)
78+
strm << ", ";
79+
first = false;
80+
emitter(strm, elem);
81+
}
82+
}
83+
84+
void lldb_private::DoDumpRegisterInfo(
85+
Stream &strm, const char *name, const char *alt_name, uint32_t byte_size,
86+
const std::vector<const char *> &invalidates,
87+
const std::vector<const char *> &read_from,
88+
const std::vector<SetInfo> &in_sets) {
89+
strm << " Name: " << name;
90+
if (alt_name)
91+
strm << " (" << alt_name << ")";
92+
strm.EOL();
93+
94+
// Size in bits may seem obvious for the usual 32 or 64 bit registers.
95+
// When we get to vector registers, then scalable vector registers, it is very
96+
// useful to know without the user doing extra work.
97+
strm.Printf(" Size: %d bytes (%d bits)", byte_size, byte_size * 8);
98+
99+
std::function<void(Stream &, const char *)> emit_str =
100+
[](Stream &strm, const char *s) { strm << s; };
101+
DumpList(strm, "Invalidates: ", invalidates, emit_str);
102+
DumpList(strm, " Read from: ", read_from, emit_str);
103+
104+
std::function<void(Stream &, SetInfo)> emit_set = [](Stream &strm,
105+
SetInfo info) {
106+
strm.Printf("%s (index %d)", info.first, info.second);
107+
};
108+
DumpList(strm, " In sets: ", in_sets, emit_set);
109+
}

lldb/test/API/commands/register/register/register_command/TestRegisters.py

+38
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,41 @@ def test_write_unknown_register(self):
567567
error=True,
568568
substrs=["error: Register not found for 'blub'."],
569569
)
570+
571+
def test_info_unknown_register(self):
572+
self.build()
573+
self.common_setup()
574+
575+
self.expect("register info blub", error=True,
576+
substrs=["error: No register found with name 'blub'."])
577+
578+
def test_info_many_registers(self):
579+
self.build()
580+
self.common_setup()
581+
582+
# Only 1 register allowed at this time.
583+
self.expect("register info abc def", error=True,
584+
substrs=["error: register info takes exactly 1 argument"])
585+
586+
@skipIf(archs=no_match(["aarch64"]))
587+
def test_info_register(self):
588+
# The behaviour of this command is generic but the specific registers
589+
# are not, so this is written for AArch64 only.
590+
# Text alignment and ordering are checked in the DumpRegisterInfo unit tests.
591+
self.build()
592+
self.common_setup()
593+
594+
# Standard register. Doesn't invalidate anything, doesn't have an alias.
595+
self.expect("register info x1", substrs=[
596+
"Name: x1",
597+
"Size: 8 bytes (64 bits)",
598+
"In sets: General Purpose Registers"])
599+
self.expect("register info x1", substrs=["Invalidates:", "Name: x1 ("],
600+
matching=False)
601+
602+
# These registers invalidate others as they are subsets of those registers.
603+
self.expect("register info w1", substrs=["Invalidates: x1"])
604+
self.expect("register info s0", substrs=["Invalidates: v0, d0"])
605+
606+
# This has an alternative name according to the ABI.
607+
self.expect("register info x30", substrs=["Name: lr (x30)"])

lldb/unittests/Core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_lldb_unittest(LLDBCoreTests
22
CommunicationTest.cpp
33
DiagnosticEventTest.cpp
44
DumpDataExtractorTest.cpp
5+
DumpRegisterInfoTest.cpp
56
FileSpecListTest.cpp
67
FormatEntityTest.cpp
78
MangledTest.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===-- DumpRegisterInfoTest.cpp ------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lldb/Core/DumpRegisterInfo.h"
10+
#include "lldb/Utility/StreamString.h"
11+
#include "gtest/gtest.h"
12+
13+
using namespace lldb_private;
14+
15+
TEST(DoDumpRegisterInfoTest, MinimumInfo) {
16+
StreamString strm;
17+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {});
18+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
19+
" Size: 4 bytes (32 bits)");
20+
}
21+
22+
TEST(DoDumpRegisterInfoTest, AltName) {
23+
StreamString strm;
24+
DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {});
25+
ASSERT_EQ(strm.GetString(), " Name: foo (bar)\n"
26+
" Size: 4 bytes (32 bits)");
27+
}
28+
29+
TEST(DoDumpRegisterInfoTest, Invalidates) {
30+
StreamString strm;
31+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {});
32+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
33+
" Size: 4 bytes (32 bits)\n"
34+
"Invalidates: foo2");
35+
36+
strm.Clear();
37+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3", "foo4"}, {}, {});
38+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
39+
" Size: 4 bytes (32 bits)\n"
40+
"Invalidates: foo2, foo3, foo4");
41+
}
42+
43+
TEST(DoDumpRegisterInfoTest, ReadFrom) {
44+
StreamString strm;
45+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {});
46+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
47+
" Size: 4 bytes (32 bits)\n"
48+
" Read from: foo1");
49+
50+
strm.Clear();
51+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1", "foo2", "foo3"}, {});
52+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
53+
" Size: 4 bytes (32 bits)\n"
54+
" Read from: foo1, foo2, foo3");
55+
}
56+
57+
TEST(DoDumpRegisterInfoTest, InSets) {
58+
StreamString strm;
59+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {{"set1", 101}});
60+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
61+
" Size: 4 bytes (32 bits)\n"
62+
" In sets: set1 (index 101)");
63+
64+
strm.Clear();
65+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {},
66+
{{"set1", 0}, {"set2", 1}, {"set3", 2}});
67+
ASSERT_EQ(strm.GetString(),
68+
" Name: foo\n"
69+
" Size: 4 bytes (32 bits)\n"
70+
" In sets: set1 (index 0), set2 (index 1), set3 (index 2)");
71+
}
72+
73+
TEST(DoDumpRegisterInfoTest, MaxInfo) {
74+
StreamString strm;
75+
DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3"},
76+
{"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}});
77+
ASSERT_EQ(strm.GetString(), " Name: foo\n"
78+
" Size: 4 bytes (32 bits)\n"
79+
"Invalidates: foo2, foo3\n"
80+
" Read from: foo3, foo4\n"
81+
" In sets: set1 (index 1), set2 (index 2)");
82+
}

0 commit comments

Comments
 (0)