/// \file Verify.cpp /// \brief // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include "Verify.h" // // Internal state // namespace State { struct Register { llvm::StringRef Name; uint64_t Value; }; using Registers = llvm::SmallVector; struct StackValue { int64_t Offset; uint64_t Value; }; using Stack = llvm::SmallVector; struct Argument { llvm::StringRef Type; llvm::StringRef Value; }; using Arguments = llvm::SmallVector; struct SingleRun { llvm::StringRef Function; Registers Registers; Stack Stack; Arguments ReturnValues; Arguments Arguments; }; using Deserialized = llvm::SmallVector; } // namespace State // // `llvm::yaml` traits for the internal state. // template<> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, State::Register &N) { IO.mapRequired("Name", N.Name); IO.mapRequired("Value", N.Value); } }; template<> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, State::StackValue &N) { IO.mapRequired("Offset", N.Offset); IO.mapRequired("Value", N.Value); } }; template<> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, State::Argument &N) { IO.mapRequired("Type", N.Type); IO.mapRequired("Value", N.Value); } }; template<> struct llvm::yaml::MappingTraits { static void mapping(IO &IO, State::SingleRun &N) { IO.mapRequired("Function", N.Function); IO.mapRequired("Registers", N.Registers); IO.mapRequired("Stack", N.Stack); IO.mapRequired("Arguments", N.Arguments); IO.mapRequired("ReturnValues", N.ReturnValues); } }; template struct GenericSmallVectorYamlTrait { static size_t size(llvm::yaml::IO &, Type &Value) { return Value.size(); } static auto &element(llvm::yaml::IO &, Type &Value, size_t Index) { if (Index >= Value.size()) Value.resize(Index + 1); return Value[Index]; } }; template<> struct llvm::yaml::SequenceTraits : GenericSmallVectorYamlTrait {}; template<> struct llvm::yaml::SequenceTraits : GenericSmallVectorYamlTrait {}; template<> struct llvm::yaml::SequenceTraits : GenericSmallVectorYamlTrait {}; template<> struct llvm::yaml::SequenceTraits : GenericSmallVectorYamlTrait {}; // // Parsed State // namespace State { struct ModelRegister { model::Register::Values Name; uint64_t Value = 0; }; } // namespace State template<> struct KeyedObjectTraits { static model::Register::Values key(const State::ModelRegister &Object) { return Object.Name; } static State::ModelRegister fromKey(const model::Register::Values &Key) { return { Key }; } }; namespace State { using ModelRegisters = SortedVector; struct Iteration { ModelRegisters Registers; Stack Stack; Arguments ReturnValues; Arguments Arguments; }; struct FunctionArtifact { llvm::StringRef Name; llvm::SmallVector Iterations = {}; }; } // namespace State template<> struct KeyedObjectTraits { static llvm::StringRef key(const State::FunctionArtifact &Object) { return Object.Name; } static State::FunctionArtifact fromKey(const llvm::StringRef &Key) { return { Key }; } }; namespace State { using Parsed = SortedVector; } // namespace State static State::ModelRegisters toModelRegisters(const State::Registers &Input, model::Architecture::Values Arch) { State::ModelRegisters Result; for (const State::Register &Reg : Input) { auto &Output = Result[model::Register::fromRegisterName(Reg.Name, Arch)]; Output.Value = Reg.Value; } return Result; } static State::Iteration toIteration(const State::SingleRun &SingleRun, model::Architecture::Values Architecture) { return State::Iteration{ toModelRegisters(SingleRun.Registers, Architecture), SingleRun.Stack, SingleRun.ReturnValues, SingleRun.Arguments }; } static State::Parsed parse(llvm::StringRef RuntimeArtifact, model::Architecture::Values Architecture) { llvm::yaml::Input Reader(RuntimeArtifact); State::Deserialized Deserialized; Reader >> Deserialized; State::Parsed Result; for (const State::SingleRun &SingleRun : Deserialized) { const State::Iteration &Iteration = toIteration(SingleRun, Architecture); Result[SingleRun.Function].Iterations.emplace_back(Iteration); } return Result; } // // Verification // template static bool tryVerifyRegisterImpl(llvm::StringRef ArgumentValue, uint64_t RegisterValue) { ValueType Converted; bool Failed = ArgumentValue.getAsInteger(16, Converted); revng_assert(Failed == false); return static_cast(RegisterValue) == Converted; } static bool tryVerifyRegister(llvm::StringRef ArgumentValue, uint64_t RegisterValue, size_t RegisterSize) { bool Res = false; while (ArgumentValue.size() > RegisterSize * 2) { auto F8B = ArgumentValue.substr(0, RegisterSize * 2); Res = Res || tryVerifyRegister(F8B, RegisterValue, RegisterSize); ArgumentValue = ArgumentValue.substr(RegisterSize * 2); } switch (ArgumentValue.size()) { case 2: return Res || tryVerifyRegisterImpl(ArgumentValue, RegisterValue); case 4: return Res || tryVerifyRegisterImpl(ArgumentValue, RegisterValue); case 8: return Res || tryVerifyRegisterImpl(ArgumentValue, RegisterValue); case 16: return Res || tryVerifyRegisterImpl(ArgumentValue, RegisterValue); default: revng_abort("Unsupported argument size."); }; } using ArgumentIndices = std::set; static ArgumentIndices verifyRegister(model::Register::Values Location, const State::FunctionArtifact &Artifact, size_t RegisterSize) { ArgumentIndices Result; bool IsFirst = true; for (const State::Iteration &Iteration : Artifact.Iterations) { const State::ModelRegister &Register = Iteration.Registers.at(Location); ArgumentIndices CurrentIterationResult; for (size_t Index = 0; Index < Iteration.Arguments.size(); ++Index) { auto ArgumentValue = Iteration.Arguments[Index].Value; if (ArgumentValue.size() > 2 && ArgumentValue.substr(0, 2) == "0x") ArgumentValue = ArgumentValue.substr(2); if (tryVerifyRegister(ArgumentValue, Register.Value, RegisterSize)) CurrentIterationResult.insert(Index); } if (IsFirst) { std::swap(Result, CurrentIterationResult); IsFirst = false; } else { ArgumentIndices Temporary; std::set_intersection(Result.begin(), Result.end(), CurrentIterationResult.begin(), CurrentIterationResult.end(), std::inserter(Temporary, Temporary.begin())); std::swap(Result, Temporary); } } return Result; } static bool areArgumentsCompatible(const State::Argument &Expected, const model::Argument &Found, size_t RegisterSize) { llvm::StringRef ArgumentValue = Expected.Value; if (ArgumentValue.size() > 2 && ArgumentValue.substr(0, 2) == "0x") ArgumentValue = ArgumentValue.substr(2); std::optional MaybeSize = Found.Type.size(); revng_assert(MaybeSize != std::nullopt); if (ArgumentValue.size() == *MaybeSize * 2) return true; // Exact size. else if (*MaybeSize == RegisterSize && ArgumentValue.size() < RegisterSize * 2) return true; // Expanded to the register size. else return false; } ExitCode verifyABI(const TupleTree &Binary, llvm::StringRef RuntimeArtifact, model::ABI::Values ABI, llvm::raw_fd_ostream &OutputStream) { model::Architecture::Values Architecture = model::ABI::getArchitecture(ABI); size_t RegisterSize = model::Architecture::getPointerSize(Architecture); State::Parsed ParsedArtifact = parse(RuntimeArtifact, Architecture); for (auto &Type : Binary->Types) { ExitCode Result = ExitCode::Success; auto Visitor = [&](const UpcastedType &Upcasted) { using namespace model; if constexpr (std::is_same_v) { auto CurrentArtifact = ParsedArtifact.find(Upcasted.CustomName); if (CurrentArtifact != ParsedArtifact.end()) { for (auto &Iteration : CurrentArtifact->Iterations) { size_t ArgumentCount = Iteration.Arguments.size(); if (ArgumentCount != Upcasted.Arguments.size()) { size_t ItID = std::distance(CurrentArtifact->Iterations.begin(), &Iteration); std::string_view NameView = llvm::StringRef(Upcasted.CustomName); dbg << ArgumentCount << " arguments expected, but " << Upcasted.Arguments.size() << " were found instead " << "on the iteration #" << ItID << " of a function named \"" << NameView << "\":\n"; Upcasted.dump(); Result = ExitCode::FailedArgumentCountCheck; return; } for (size_t Index = 0; Index < ArgumentCount; ++Index) { if (!areArgumentsCompatible(Iteration.Arguments[Index], Upcasted.Arguments.at(Index), RegisterSize)) { size_t ItID = std::distance(CurrentArtifact->Iterations.begin(), &Iteration); std::string_view NmView = llvm::StringRef(Upcasted.CustomName); dbg << "Argument " << Index << " is not compatible with " << "the expected type on the iteration #" << ItID << " of a function named \"" << NmView << "\":\n"; Upcasted.dump(); Result = ExitCode::FailedArgumentCompatibilityCheck; return; } } } } } else if constexpr (std::is_same_v) { auto CurrentArtifact = ParsedArtifact.find(Upcasted.CustomName); if (CurrentArtifact != ParsedArtifact.end()) { for (auto &Argument : Upcasted.Arguments) { auto Indices = verifyRegister(Argument.Location, *CurrentArtifact, RegisterSize); if (Indices.empty()) { std::string_view NameView = llvm::StringRef(Upcasted.CustomName); dbg << "Cannot locate an argument in the artifact:\n"; Argument.dump(); dbg << "The function named \"" << NameView << "\":\n"; Upcasted.dump(); Result = ExitCode::FailedLocatingAnArgument; return; } if (Indices.size() > 1) { std::string_view NameView = llvm::StringRef(Upcasted.CustomName); dbg << "Multiple locations for an argument in the artifact:\n"; Argument.dump(); dbg << "The locations are: "; for (auto Index : Indices) dbg << Index << ' '; dbg << "\nThe function named \"" << NameView << "\":\n"; Upcasted.dump(); Result = ExitCode::FailedSelectingSingleArgumentLocation; return; } } } } }; Type.upcast(Visitor); if (Result != ExitCode::Success) return Result; } return ExitCode::Success; }