diff --git a/llvm/include/llvm/DebugInfo/Symbolize/ProcessContext.h b/llvm/include/llvm/DebugInfo/Symbolize/ProcessContext.h --- a/llvm/include/llvm/DebugInfo/Symbolize/ProcessContext.h +++ b/llvm/include/llvm/DebugInfo/Symbolize/ProcessContext.h @@ -23,6 +23,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/Object/BuildID.h" +#include "llvm/Support/JSON.h" namespace llvm { @@ -32,6 +33,10 @@ namespace symbolize { +class ProcessContext; +bool fromJSON(const json::Value &Value, ProcessContext &Result, + json::Path Path); + /// A process context that allows converting between addresses and /// module-relative addresses. class ProcessContext { @@ -66,6 +71,9 @@ std::map MMapsByMRAddr; public: + /// Parses the mode string for an mmap element or corresponding JSON. + static std::optional parseMode(StringRef Str); + bool empty() const; void clear(); @@ -88,6 +96,9 @@ private: const MMap *getOverlappingMMap(const MMap &Map) const; const MMap *getOverlappingMRAddrMMap(const MMap &Map) const; + + friend bool fromJSON(const json::Value &Value, ProcessContext &Result, + json::Path Path); }; } // end namespace symbolize diff --git a/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp b/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp --- a/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp +++ b/llvm/lib/DebugInfo/Symbolize/MarkupFilter.cpp @@ -630,30 +630,11 @@ return BID; } -// Parses the mode string for an mmap element. std::optional MarkupFilter::parseMode(StringRef Str) const { - if (Str.empty()) { + std::optional Mode = ProcessContext::parseMode(Str); + if (!Mode) reportTypeError(Str, "mode"); - return std::nullopt; - } - - // Pop off each of r/R, w/W, and x/X from the front, in that order. - StringRef Remainder = Str; - if (!Remainder.empty() && tolower(Remainder.front()) == 'r') - Remainder = Remainder.drop_front(); - if (!Remainder.empty() && tolower(Remainder.front()) == 'w') - Remainder = Remainder.drop_front(); - if (!Remainder.empty() && tolower(Remainder.front()) == 'x') - Remainder = Remainder.drop_front(); - - // If anything remains, then the string wasn't a mode. - if (!Remainder.empty()) { - reportTypeError(Str, "mode"); - return std::nullopt; - } - - // Normalize the mode. - return Str.lower(); + return Mode; } std::optional diff --git a/llvm/lib/DebugInfo/Symbolize/ProcessContext.cpp b/llvm/lib/DebugInfo/Symbolize/ProcessContext.cpp --- a/llvm/lib/DebugInfo/Symbolize/ProcessContext.cpp +++ b/llvm/lib/DebugInfo/Symbolize/ProcessContext.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/StringSwitch.h" #include "llvm/DebugInfo/DIContext.h" #include "llvm/DebugInfo/Symbolize/Markup.h" +#include "llvm/DebugInfo/Symbolize/MarkupFilter.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/Debuginfod.h" #include "llvm/Demangle/Demangle.h" @@ -26,7 +27,6 @@ #include "llvm/Support/Error.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" -#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" @@ -35,6 +35,27 @@ using namespace llvm; using namespace llvm::symbolize; +std::optional ProcessContext::parseMode(StringRef Str) { + if (Str.empty()) + return std::nullopt; + + // Pop off each of r/R, w/W, and x/X from the front, in that order. + StringRef Remainder = Str; + if (!Remainder.empty() && tolower(Remainder.front()) == 'r') + Remainder = Remainder.drop_front(); + if (!Remainder.empty() && tolower(Remainder.front()) == 'w') + Remainder = Remainder.drop_front(); + if (!Remainder.empty() && tolower(Remainder.front()) == 'x') + Remainder = Remainder.drop_front(); + + // If anything remains, then the string wasn't a mode. + if (!Remainder.empty()) + return std::nullopt; + + // Normalize the mode. + return Str.lower(); +} + bool ProcessContext::empty() const { return Modules.empty() && MMaps.empty(); } void ProcessContext::clear() { @@ -70,6 +91,145 @@ }); } +static bool buildIDFromJSON(const json::Value &Value, BuildID &Result, + json::Path Path) { + std::optional BIDStr = Value.getAsString(); + if (!BIDStr) { + Path.report("expected string"); + return false; + } + Result = parseBuildID(*BIDStr); + if (Result.empty()) { + Path.report("invalid build ID"); + return false; + } + return true; +} + +namespace llvm { +namespace symbolize { + +bool fromJSON(const json::Value &Value, ProcessContext::Module &Result, + json::Path Path) { + json::ObjectMapper O(Value, Path); + if (!O || !O.map("id", Result.ID) || !O.map("name", Result.Name)) + return false; + + std::string Type; + if (!O.map("type", Type)) + return false; + if (Type != "elf") { + Path.field("type").report("unsupported type"); + return false; + } + + return buildIDFromJSON(*Value.getAsObject()->get("buildID"), Result.BuildID, + Path.field("buildID")); +} + +} // namespace symbolize +} // namespace llvm + +static bool mmapFromJSON(const json::Value &Value, ProcessContext::MMap &Result, + const ProcessContext &Context, json::Path Path) { + json::ObjectMapper O(Value, Path); + if (!O || !O.map("address", Result.Addr) || !O.map("size", Result.Size)) + return false; + + std::string Type; + if (!O.map("type", Type)) + return false; + if (Type != "load") { + Path.field("type").report("unsupported type"); + return false; + } + + uint64_t ModuleID; + if (!O.map("moduleID", ModuleID)) + return false; + + Result.Mod = Context.getModule(ModuleID); + if (!Result.Mod) { + Path.field("moduleID").report("unknown ID"); + return false; + } + + std::string ModeStr; + if (!O.map("mode", ModeStr)) + return false; + std::optional Mode = ProcessContext::parseMode(ModeStr); + if (!Mode) { + Path.field("mode").report("invalid mode"); + return false; + } + Result.Mode = std::move(*Mode); + + return O.map("moduleRelativeAddress", Result.ModuleRelativeAddr); +} + +static bool modulesFromJSON(const json::Value &Value, ProcessContext &Context, + json::Path Path) { + const json::Array *A = Value.getAsArray(); + if (!A) { + Path.report("expected array"); + return false; + } + for (const auto &[I, V] : llvm::enumerate(*A)) { + ProcessContext::Module M; + if (!fromJSON(V, M, Path.index(I))) + return false; + auto Res = Context.insertModule(std::move(M)); + if (!Res.second) { + Path.report("duplicate ID"); + return false; + } + } + return true; +} + +static bool mmapsFromJSON(const json::Value &Value, ProcessContext &Context, + json::Path Path) { + const json::Array *A = Value.getAsArray(); + if (!A) + Path.report("expected array"); + for (const auto &[I, V] : llvm::enumerate(*A)) { + ProcessContext::MMap MM; + if (!mmapFromJSON(V, MM, Context, Path.index(I))) + return false; + auto Res = Context.insertMMap(std::move(MM)); + if (!Res.second) { + Path.report("overlapping mmap"); + return false; + } + } + return true; +} + +bool llvm::symbolize::fromJSON(const json::Value &Value, ProcessContext &Result, + json::Path Path) { + const json::Value *V = &Value; + if (const json::Array *A = Value.getAsArray()) { + if (A->size() != 1) + Path.report("expected either an object or a singular array"); + V = &A->front(); + } + + const json::Value *ModulesV = V->getAsObject()->get("modules"); + if (!ModulesV) { + Path.field("modules").report("missing value"); + return false; + } + if (!modulesFromJSON(*ModulesV, Result, Path.field("modules"))) + return false; + + const json::Value *MMapsV = V->getAsObject()->get("mmaps"); + if (!MMapsV) { + Path.field("mmaps").report("missing value"); + return false; + } + return mmapsFromJSON(*MMapsV, Result, Path.field("mmaps")); +} + std::pair ProcessContext::insertModule(Module M) { const auto Res = diff --git a/llvm/unittests/DebugInfo/Symbolizer/CMakeLists.txt b/llvm/unittests/DebugInfo/Symbolizer/CMakeLists.txt --- a/llvm/unittests/DebugInfo/Symbolizer/CMakeLists.txt +++ b/llvm/unittests/DebugInfo/Symbolizer/CMakeLists.txt @@ -1,5 +1,6 @@ set(LLVM_LINK_COMPONENTS Symbolize) add_llvm_unittest(DebugInfoSymbolizerTests + ProcessContextTest.cpp MarkupTest.cpp ) target_link_libraries(DebugInfoSymbolizerTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/DebugInfo/Symbolizer/ProcessContextTest.cpp b/llvm/unittests/DebugInfo/Symbolizer/ProcessContextTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/DebugInfo/Symbolizer/ProcessContextTest.cpp @@ -0,0 +1,296 @@ + +//===- unittest/DebugInfo/Symbolizer/ProcessContextTest.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 "llvm/DebugInfo/Symbolize/ProcessContext.h" +#include "llvm/Support/JSON.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using namespace llvm; +using namespace llvm::symbolize; +using namespace testing; + +TEST(SymbolizerProcessContext, ParseEmptyContext) { + json::Object O; + O.try_emplace("modules", json::Array()); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + EXPECT_TRUE(fromJSON(std::move(O), C, R)); + + EXPECT_TRUE(C.empty()); +} + +TEST(SymbolizerProcessContext, RequiresModules) { + json::Object O; + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), "missing value at (root).modules"); +} + +TEST(SymbolizerProcessContext, RequiresMMaps) { + json::Object O; + O.try_emplace("modules", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), "missing value at (root).mmaps"); +} + +TEST(SymbolizerProcessContext, ParsesContextInArray) { + json::Object O; + O.try_emplace("modules", json::Array()); + O.try_emplace("mmaps", json::Array()); + json::Array A; + A.push_back(std::move(O)); + + ProcessContext C; + json::Path::Root R; + EXPECT_TRUE(fromJSON(std::move(A), C, R)); + + EXPECT_TRUE(C.empty()); +} + +TEST(SymbolizerProcessContext, ModulesMustBeArray) { + json::Object O; + O.try_emplace("modules", "foo"); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), "expected array at (root).modules"); +} + +TEST(SymbolizerProcessContext, ParsesModule) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + EXPECT_TRUE(fromJSON(std::move(O), C, R)); + + const ProcessContext::Module *ParsedModule = C.getModule(1); + ASSERT_TRUE(ParsedModule); + EXPECT_EQ(ParsedModule->ID, 1u); + EXPECT_EQ(ParsedModule->Name, "foo"); + EXPECT_EQ(ParsedModule->BuildID, object::BuildID({2})); +} + +TEST(SymbolizerProcessContext, RequiresModuleTypeElf) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "bad"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), + "unsupported type at (root).modules[0].type"); +} + +TEST(SymbolizerProcessContext, NonStringBuildID) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", 1); + O.try_emplace("modules", json::Array({std::move(Module)})); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), + "expected string at (root).modules[0].buildID"); +} + +TEST(SymbolizerProcessContext, InvalidBuildIDChars) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "g"); + O.try_emplace("modules", json::Array({std::move(Module)})); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), + "invalid build ID at (root).modules[0].buildID"); +} + +TEST(SymbolizerProcessContext, DuplicateModuleID) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + json::Object Duplicate = Module; + O.try_emplace("modules", + json::Array({std::move(Module), std::move(Duplicate)})); + O.try_emplace("mmaps", json::Array()); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + + EXPECT_EQ(toString(R.getError()), "duplicate ID at (root).modules"); +} + +TEST(SymbolizerProcessContext, ParsesMMap) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + json::Object MMap; + MMap.try_emplace("address", 2); + MMap.try_emplace("size", 3); + MMap.try_emplace("type", "load"); + MMap.try_emplace("moduleID", 1); + MMap.try_emplace("mode", "Rx"); + MMap.try_emplace("moduleRelativeAddress", 4); + O.try_emplace("mmaps", json::Array({std::move(MMap)})); + + ProcessContext C; + json::Path::Root R; + ASSERT_TRUE(fromJSON(std::move(O), C, R)); + + const ProcessContext::Module *ParsedModule = C.getModule(1); + ASSERT_TRUE(ParsedModule); + const ProcessContext::MMap *ParsedMMap = C.getContainingMMap(2); + ASSERT_TRUE(ParsedMMap); + EXPECT_EQ(ParsedMMap->Addr, 2u); + EXPECT_EQ(ParsedMMap->Size, 3u); + EXPECT_EQ(ParsedMMap->Mod, ParsedModule); + EXPECT_EQ(ParsedMMap->Mode, "rx"); + EXPECT_EQ(ParsedMMap->ModuleRelativeAddr, 4u); +} + +TEST(SymbolizerProcessContext, RequiresMMapTypeLoad) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + json::Object MMap; + MMap.try_emplace("address", 2); + MMap.try_emplace("size", 3); + MMap.try_emplace("type", "bad"); + MMap.try_emplace("moduleID", 1); + MMap.try_emplace("mode", "r"); + MMap.try_emplace("moduleRelativeAddress", 4); + O.try_emplace("mmaps", json::Array({std::move(MMap)})); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + EXPECT_EQ(toString(R.getError()), "unsupported type at (root).mmaps[0].type"); +} + +TEST(SymbolizerProcessContext, MMapWithUnknownModuleID) { + json::Object O; + O.try_emplace("modules", json::Array()); + json::Object MMap; + MMap.try_emplace("address", 2); + MMap.try_emplace("size", 3); + MMap.try_emplace("type", "load"); + MMap.try_emplace("moduleID", 1); + MMap.try_emplace("mode", "r"); + MMap.try_emplace("moduleRelativeAddress", 4); + O.try_emplace("mmaps", json::Array({std::move(MMap)})); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + EXPECT_EQ(toString(R.getError()), "unknown ID at (root).mmaps[0].moduleID"); +} + +TEST(SymbolizerProcessContext, MMapWithInvalidMode) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + json::Object MMap; + MMap.try_emplace("address", 2); + MMap.try_emplace("size", 3); + MMap.try_emplace("type", "load"); + MMap.try_emplace("moduleID", 1); + MMap.try_emplace("mode", "foo"); + MMap.try_emplace("moduleRelativeAddress", 4); + O.try_emplace("mmaps", json::Array({std::move(MMap)})); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + EXPECT_EQ(toString(R.getError()), "invalid mode at (root).mmaps[0].mode"); +} + +TEST(SymbolizerProcessContext, OverlappingMMaps) { + json::Object O; + json::Object Module; + Module.try_emplace("id", 1); + Module.try_emplace("name", "foo"); + Module.try_emplace("type", "elf"); + Module.try_emplace("buildID", "2"); + O.try_emplace("modules", json::Array({std::move(Module)})); + json::Object MMap; + MMap.try_emplace("address", 2); + MMap.try_emplace("size", 3); + MMap.try_emplace("type", "load"); + MMap.try_emplace("moduleID", 1); + MMap.try_emplace("mode", "r"); + MMap.try_emplace("moduleRelativeAddress", 4); + json::Object Duplicate = MMap; + O.try_emplace("mmaps", json::Array({std::move(MMap), std::move(Duplicate)})); + + ProcessContext C; + json::Path::Root R; + ASSERT_FALSE(fromJSON(std::move(O), C, R)); + EXPECT_EQ(toString(R.getError()), "overlapping mmap at (root).mmaps"); +} + +} // namespace