From 6344ce3feff235f6a3b1bc1d5eb1e50e5695d320 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Mon, 11 Mar 2024 10:18:51 +0000 Subject: [PATCH] [lldb] Add format eFormatEnumWithValues to ensure raw enum value is always shown When an enum is used to represent certain data it can be useful to know its name and the value of it. For instance, register fields are often set in source code as numbers, but in the debugger you'd like to see the meaning as well. (lldb) register read fpcr fpcr = 0x00000000 = (... RMode = RN (0), ...) Often you do just want the meaning but the value saves you having to manually decode it if you want to confirm what your source code has done, or try to replicate the current state in your source code. This also works for bitfield like enums, with the added change that if a bitfield like enum has the value 0, we will print 0 if asked to always show a value. Normally we don't print a 0 there because 0 means no flags are set. I did not intend to make this new format avaialable to the user, but it ended up being so. So you can do: expression --format "enumeration with values" -- foo So testing is mainly from c++ but I have added a couple to the Python tests. --- lldb/include/lldb/lldb-enumerations.h | 12 +++++--- lldb/source/Commands/CommandObjectMemory.cpp | 1 + lldb/source/Core/DumpDataExtractor.cpp | 1 + lldb/source/Core/ValueObject.cpp | 3 +- lldb/source/DataFormatters/FormatManager.cpp | 1 + lldb/source/DataFormatters/VectorType.cpp | 2 ++ .../TypeSystem/Clang/TypeSystemClang.cpp | 28 ++++++++++++++----- .../API/lang/c/enum_types/TestEnumTypes.py | 20 +++++++++++++ lldb/unittests/Core/DumpDataExtractorTest.cpp | 1 + .../DumpValueObjectOptionsTests.cpp | 16 +++++++++++ 10 files changed, 73 insertions(+), 12 deletions(-) diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 8e05f6ba9c8760..99b2ec820e608b 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -195,11 +195,15 @@ enum Format { ///< character arrays that can contain non printable ///< characters eFormatAddressInfo, ///< Describe what an address points to (func + offset - ///< with file/line, symbol + offset, data, etc) - eFormatHexFloat, ///< ISO C99 hex float string - eFormatInstruction, ///< Disassemble an opcode - eFormatVoid, ///< Do not print this + ///< with file/line, symbol + offset, data, etc) + eFormatHexFloat, ///< ISO C99 hex float string + eFormatInstruction, ///< Disassemble an opcode + eFormatVoid, ///< Do not print this eFormatUnicode8, + eFormatEnumWithValues, ///< Format as an enum but if the value matches one or + ///< more enumerators, print the enumerator name and + ///< value of those enumerators. For example "foo (1)" + ///< instead of "foo". kNumFormats }; diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp index b78a0492cca558..7748aa5b7f083d 100644 --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -1387,6 +1387,7 @@ class CommandObjectMemoryWrite : public CommandObjectParsed { case eFormatBytesWithASCII: case eFormatComplex: case eFormatEnum: + case eFormatEnumWithValues: case eFormatUnicode8: case eFormatUnicode16: case eFormatUnicode32: diff --git a/lldb/source/Core/DumpDataExtractor.cpp b/lldb/source/Core/DumpDataExtractor.cpp index 826edd7bab046e..e3fe2343b240d9 100644 --- a/lldb/source/Core/DumpDataExtractor.cpp +++ b/lldb/source/Core/DumpDataExtractor.cpp @@ -496,6 +496,7 @@ lldb::offset_t lldb_private::DumpDataExtractor( case eFormatEnum: // Print enum value as a signed integer when we don't get // the enum type + case eFormatEnumWithValues: case eFormatDecimal: if (item_byte_size <= 8) s->Printf("%" PRId64, diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp index 1443d9dfc32807..d816f33b005ec2 100644 --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -1189,7 +1189,8 @@ bool ValueObject::DumpPrintableRepresentation( return !error.Fail(); } - if (custom_format == eFormatEnum) + if (custom_format == eFormatEnum || + custom_format == eFormatEnumWithValues) return false; // this only works for arrays, because I have no way to know when the diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp index d7ba5b4b70c949..24ee2becabd3e6 100644 --- a/lldb/source/DataFormatters/FormatManager.cpp +++ b/lldb/source/DataFormatters/FormatManager.cpp @@ -71,6 +71,7 @@ static constexpr FormatInfo g_format_infos[] = { {eFormatInstruction, 'i', "instruction"}, {eFormatVoid, 'v', "void"}, {eFormatUnicode8, 'u', "unicode8"}, + {eFormatEnumWithValues, '\0', "enumeration with values"}, }; static_assert((sizeof(g_format_infos) / sizeof(g_format_infos[0])) == diff --git a/lldb/source/DataFormatters/VectorType.cpp b/lldb/source/DataFormatters/VectorType.cpp index 19de204c24353a..5a79574fb3563a 100644 --- a/lldb/source/DataFormatters/VectorType.cpp +++ b/lldb/source/DataFormatters/VectorType.cpp @@ -115,6 +115,7 @@ static CompilerType GetCompilerTypeForFormat(lldb::Format format, case lldb::eFormatComplexInteger: case lldb::eFormatDecimal: case lldb::eFormatEnum: + case lldb::eFormatEnumWithValues: case lldb::eFormatInstruction: case lldb::eFormatOSType: case lldb::eFormatVoid: @@ -150,6 +151,7 @@ static lldb::Format GetItemFormatForFormat(lldb::Format format, case lldb::eFormatComplexInteger: case lldb::eFormatDecimal: case lldb::eFormatEnum: + case lldb::eFormatEnumWithValues: case lldb::eFormatInstruction: case lldb::eFormatOSType: case lldb::eFormatVoid: diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 369ae46cf264a2..58d08e9c257f0d 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -8572,7 +8572,7 @@ void TypeSystemClang::DumpFromSymbolFile(Stream &s, static bool DumpEnumValue(const clang::QualType &qual_type, Stream &s, const DataExtractor &data, lldb::offset_t byte_offset, size_t byte_size, uint32_t bitfield_bit_offset, - uint32_t bitfield_bit_size) { + uint32_t bitfield_bit_size, bool always_show_value) { const clang::EnumType *enutype = llvm::cast(qual_type.getTypePtr()); const clang::EnumDecl *enum_decl = enutype->getDecl(); @@ -8599,7 +8599,11 @@ static bool DumpEnumValue(const clang::QualType &qual_type, Stream &s, ++num_enumerators; if (val == enum_svalue) { // Found an exact match, that's all we need to do. - s.PutCString(enumerator->getNameAsString()); + if (always_show_value) + s.Printf("%s (%" PRIi64 ")", enumerator->getNameAsString().c_str(), + enum_svalue); + else + s.PutCString(enumerator->getNameAsString()); return true; } } @@ -8634,17 +8638,23 @@ static bool DumpEnumValue(const clang::QualType &qual_type, Stream &s, return llvm::popcount(a.first) > llvm::popcount(b.first); }); + bool found_enumerator = false; for (const auto &val : values) { if ((remaining_value & val.first) != val.first) continue; + found_enumerator = true; remaining_value &= ~val.first; s.PutCString(val.second); + if (always_show_value) + s.Printf(" (0x%" PRIx64 ")", val.first); if (remaining_value) s.PutCString(" | "); } // If there is a remainder that is not covered by the value, print it as hex. - if (remaining_value) + // If we found no matching values but were asked to always print a value, + // print it as hex. + if (remaining_value || (!found_enumerator && always_show_value)) s.Printf("0x%" PRIx64, remaining_value); return true; @@ -8666,8 +8676,9 @@ bool TypeSystemClang::DumpTypeValue( if (type_class == clang::Type::Elaborated) { qual_type = llvm::cast(qual_type)->getNamedType(); - return DumpTypeValue(qual_type.getAsOpaquePtr(), s, format, data, byte_offset, byte_size, - bitfield_bit_size, bitfield_bit_offset, exe_scope); + return DumpTypeValue(qual_type.getAsOpaquePtr(), s, format, data, + byte_offset, byte_size, bitfield_bit_size, + bitfield_bit_offset, exe_scope); } switch (type_class) { @@ -8699,10 +8710,12 @@ bool TypeSystemClang::DumpTypeValue( case clang::Type::Enum: // If our format is enum or default, show the enumeration value as its // enumeration string value, else just display it as requested. - if ((format == eFormatEnum || format == eFormatDefault) && + if ((format == eFormatEnum || format == eFormatEnumWithValues || + format == eFormatDefault) && GetCompleteType(type)) return DumpEnumValue(qual_type, s, data, byte_offset, byte_size, - bitfield_bit_offset, bitfield_bit_size); + bitfield_bit_offset, bitfield_bit_size, + format == eFormatEnumWithValues); // format was not enum, just fall through and dump the value as // requested.... [[fallthrough]]; @@ -8722,6 +8735,7 @@ bool TypeSystemClang::DumpTypeValue( case eFormatCString: // NULL terminated C strings case eFormatDecimal: case eFormatEnum: + case eFormatEnumWithValues: case eFormatHex: case eFormatHexUppercase: case eFormatFloat: diff --git a/lldb/test/API/lang/c/enum_types/TestEnumTypes.py b/lldb/test/API/lang/c/enum_types/TestEnumTypes.py index 0015c8f4785783..26c3e8f2b54d20 100644 --- a/lldb/test/API/lang/c/enum_types/TestEnumTypes.py +++ b/lldb/test/API/lang/c/enum_types/TestEnumTypes.py @@ -23,13 +23,28 @@ def test_command_line(self): ) self.expect("fr var a", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = A$"]) + self.expect( + 'fr var --format "enumeration with values" -- a', + DATA_TYPES_DISPLAYED_CORRECTLY, + patterns=[" = A \(1\)$"], + ) self.expect("fr var b", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = B$"]) self.expect("fr var c", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = C$"]) self.expect("fr var ab", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = AB$"]) self.expect( "fr var ac", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = A \| C$"] ) + self.expect( + 'fr var --format "enumeration with values" -- ac', + DATA_TYPES_DISPLAYED_CORRECTLY, + patterns=[" = A \(0x1\) | C \(0x4\)$"], + ) self.expect("fr var all", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = ALL$"]) + self.expect( + 'fr var --format "enumeration with values" -- all', + DATA_TYPES_DISPLAYED_CORRECTLY, + patterns=[" = ALL \(7\)$"], + ) # Test that an enum that doesn't match the heuristic we use in # TypeSystemClang::DumpEnumValue, gets printed as a raw integer. self.expect("fr var omega", DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = 7$"]) @@ -41,6 +56,11 @@ def test_command_line(self): DATA_TYPES_DISPLAYED_CORRECTLY, patterns=[" = B \| C \| 0x10$"], ) + self.expect( + 'expression --format "enumeration with values" -- (enum bitfield)nonsense', + DATA_TYPES_DISPLAYED_CORRECTLY, + patterns=[" = B \(0x2\) \| C \(0x4\) \| 0x10$"], + ) # Break inside the main. bkpt_id = lldbutil.run_break_set_by_file_and_line( diff --git a/lldb/unittests/Core/DumpDataExtractorTest.cpp b/lldb/unittests/Core/DumpDataExtractorTest.cpp index 3d1e8bc5e4623f..2cb09f5011c557 100644 --- a/lldb/unittests/Core/DumpDataExtractorTest.cpp +++ b/lldb/unittests/Core/DumpDataExtractorTest.cpp @@ -160,6 +160,7 @@ TEST_F(DumpDataExtractorTest, Formats) { TestDump(99, lldb::Format::eFormatDecimal, "99"); // Just prints as a signed integer. TestDump(-1, lldb::Format::eFormatEnum, "-1"); + TestDump(-1, lldb::Format::eFormatEnumWithValues, "-1"); TestDump(0xcafef00d, lldb::Format::eFormatHex, "0xcafef00d"); TestDump(0xcafef00d, lldb::Format::eFormatHexUppercase, "0xCAFEF00D"); TestDump(0.456, lldb::Format::eFormatFloat, "0.45600000000000002"); diff --git a/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp b/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp index 31068a04d8dfef..b4101a0a41a9e0 100644 --- a/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp +++ b/lldb/unittests/ValueObject/DumpValueObjectOptionsTests.cpp @@ -129,8 +129,12 @@ TEST_F(ValueObjectMockProcessTest, Enum) { TestDumpValueObject( MakeEnumType({{"test_2", 2}, {"test_3", 3}}), {{0, {}, "(TestEnum) test_var = 0\n"}, + {0, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = 0\n"}, {1, {}, "(TestEnum) test_var = 1\n"}, {2, {}, "(TestEnum) test_var = test_2\n"}, + {2, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = test_2 (2)\n"}, {3, {}, "(TestEnum) test_var = test_3\n"}, {4, {}, "(TestEnum) test_var = 4\n"}, {5, {}, "(TestEnum) test_var = 5\n"}, @@ -141,6 +145,10 @@ TEST_F(ValueObjectMockProcessTest, Enum) { {1, DumpValueObjectOptions().SetHideName(true), "(TestEnum) 1\n"}, {1, DumpValueObjectOptions().SetHideValue(true), "(TestEnum) test_var =\n"}, + {1, + DumpValueObjectOptions().SetHideValue(true).SetFormat( + eFormatEnumWithValues), + "(TestEnum) test_var =\n"}, {1, DumpValueObjectOptions().SetHideName(true).SetHideValue(true), "(TestEnum) \n"}}); } @@ -154,11 +162,19 @@ TEST_F(ValueObjectMockProcessTest, BitFieldLikeEnum) { MakeEnumType({{"test_2", 2}, {"test_4", 4}}), { {0, {}, "(TestEnum) test_var =\n"}, + {0, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = 0x0\n"}, {1, {}, "(TestEnum) test_var = 0x1\n"}, {2, {}, "(TestEnum) test_var = test_2\n"}, + {2, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = test_2 (2)\n"}, {4, {}, "(TestEnum) test_var = test_4\n"}, {6, {}, "(TestEnum) test_var = test_2 | test_4\n"}, + {6, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = test_2 (0x2) | test_4 (0x4)\n"}, {7, {}, "(TestEnum) test_var = test_2 | test_4 | 0x1\n"}, + {7, DumpValueObjectOptions().SetFormat(eFormatEnumWithValues), + "(TestEnum) test_var = test_2 (0x2) | test_4 (0x4) | 0x1\n"}, {8, {}, "(TestEnum) test_var = 0x8\n"}, {1, DumpValueObjectOptions().SetHideRootName(true), "(TestEnum) 0x1\n"},