/// \file ConvertFunctionsToCABI.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include "revng/ABI/FunctionType/Conversion.h" #include "revng/ABI/FunctionType/Support.h" #include "revng/Model/Binary.h" #include "revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h" #include "revng/Model/VerifyHelper.h" #include "revng/Pipeline/Analysis.h" #include "revng/Pipeline/RegisterAnalysis.h" #include "revng/Pipes/Kinds.h" #include "revng/TupleTree/TupleTree.h" // TODO: dismiss using VerifyHelper for this verification. Introduce a // new class instead. static RecursiveCoroutine checkVectorRegisterSupport(model::VerifyHelper &VH, const model::Type &Type); static RecursiveCoroutine checkVectorRegisterSupport(model::VerifyHelper &VH, const model::QualifiedType &Type) { if (revng::is_contained_if(Type.Qualifiers(), model::Qualifier::isPointer)) { // If it's a pointer, it's acceptable no matter what it points to. rc_return true; } // `Array` and `Const` do not impact the type, so we can just ignore them. const model::Type *Unqualified = Type.UnqualifiedType().get(); revng_assert(Unqualified != nullptr); rc_return rc_recur checkVectorRegisterSupport(VH, *Unqualified); } static RecursiveCoroutine checkVectorRegisterSupport(model::VerifyHelper &VH, const model::PrimitiveTypeKind::Values &Kind) { rc_return VH.maybeFail(Kind != model::PrimitiveTypeKind::Float, "Floating Point primitive found."); } template inline RecursiveCoroutine underlyingHelper(model::VerifyHelper &VH, const model::Type &Value) { const RealType *Cast = llvm::cast(&Value); rc_return rc_recur checkVectorRegisterSupport(VH, Cast->UnderlyingType()); } static RecursiveCoroutine checkVectorRegisterSupport(model::VerifyHelper &VH, const model::TypePath &Reference) { const model::Type *Pointer = Reference.getConst(); revng_assert(Pointer != nullptr); rc_return rc_recur checkVectorRegisterSupport(VH, *Pointer); } static RecursiveCoroutine checkVectorRegisterSupport(model::VerifyHelper &VH, const model::Type &Type) { if (VH.isVerified(&Type)) rc_return true; // Ensure we never recur indefinitely if (VH.isVerificationInProgress(&Type)) rc_return VH.fail(); VH.verificationInProgress(&Type); bool Result = false; switch (Type.Kind()) { case model::TypeKind::PrimitiveType: { const auto &Kind = llvm::cast(&Type)->PrimitiveKind(); Result = rc_recur checkVectorRegisterSupport(VH, Kind); } break; case model::TypeKind::EnumType: Result = rc_recur underlyingHelper(VH, Type); break; case model::TypeKind::TypedefType: Result = rc_recur underlyingHelper(VH, Type); break; case model::TypeKind::StructType: Result = true; for (const auto &F : llvm::cast(&Type)->Fields()) Result = Result && rc_recur checkVectorRegisterSupport(VH, F.Type()); break; case model::TypeKind::UnionType: Result = true; for (const auto &F : llvm::cast(&Type)->Fields()) Result = Result && rc_recur checkVectorRegisterSupport(VH, F.Type()); break; case model::TypeKind::CABIFunctionType: { Result = true; using CABIFT = model::CABIFunctionType; for (const auto &A : llvm::cast(&Type)->Arguments()) Result = Result && rc_recur checkVectorRegisterSupport(VH, A.Type()); const auto &ReturnType = llvm::cast(&Type)->ReturnType(); Result = Result && rc_recur checkVectorRegisterSupport(VH, ReturnType); } break; case model::TypeKind::RawFunctionType: { Result = true; using RawFT = model::RawFunctionType; for (const auto &A : llvm::cast(&Type)->Arguments()) { auto Kind = model::Register::primitiveKind(A.Location()); Result = Result && rc_recur checkVectorRegisterSupport(VH, Kind); Result = Result && rc_recur checkVectorRegisterSupport(VH, A.Type()); } for (const auto &V : llvm::cast(&Type)->ReturnValues()) { auto Kind = model::Register::primitiveKind(V.Location()); Result = Result && rc_recur checkVectorRegisterSupport(VH, Kind); Result = Result && rc_recur checkVectorRegisterSupport(VH, V.Type()); } const auto &Stack = llvm::cast(&Type)->StackArgumentsType(); if (not Stack.empty()) Result = Result && rc_recur checkVectorRegisterSupport(VH, Stack); } break; default: revng_abort("Unknown type."); } if (Result) { VH.setVerified(&Type); VH.verificationCompleted(&Type); } rc_return VH.maybeFail(Result); } using namespace std::string_literals; static Logger Log("function-type-conversion-to-cabi-analysis"); class ConvertFunctionsToCABI { public: static constexpr auto Name = "convert-functions-to-cabi"; inline static const std::tuple Options = { // Allows overriding the default ABI with a specific value when invoking // the analysis. pipeline::Option("abi", "Invalid"), // Allows specifying the mode of operation, // - safe: only convert the function if ABI belongs to the "tested" list. // - unsafe: always convert the function. pipeline::Option("mode", "safe"), // Allows specifying the confidence we have in the ABI, which then leads to // different levels of strictness when doing the argument deductions // (different behaviour in cases where the function does not seem to comply // to the abi): // - low: use safe deduction that will avoid changing function in cases of // non-compliance. // - high: override/discard any information about the function that does not // comply with an ABI (i.e. an argument in a register that is not // dedicated for passing arguments, etc.). pipeline::Option("confidence", "low") }; std::vector> AcceptedKinds = {}; void run(pipeline::ExecutionContext &Context, std::string TargetABI, std::string Mode, std::string ABIConfidence) { auto &Model = revng::getWritableModelFromContext(Context); revng_assert(!TargetABI.empty()); model::ABI::Values ABI = model::ABI::fromName(TargetABI); if (ABI == model::ABI::Values::Invalid) { revng_log(Log, "No ABI explicitly specified for the conversion, using the " "`Model->DefaultABI()`."); ABI = Model->DefaultABI(); } // Minimize the negative impact on binaries with ABI that is not fully // supported by disabling the conversion by default. // // Use `--convert-functions-to-cabi-mode=unsafe` to force conversion even // when ABI is not considered fully tested. if (Mode != "safe") { // TODO: extend this list. static constexpr std::array ABIsTheConversionIsEnabledFor = { model::ABI::SystemV_x86_64, model::ABI::Microsoft_x86_64, model::ABI::Microsoft_x86_64_vectorcall, model::ABI::SystemV_x86, model::ABI::SystemV_x86_regparm_3, model::ABI::SystemV_x86_regparm_2, model::ABI::SystemV_x86_regparm_1, model::ABI::Microsoft_x86_cdecl, model::ABI::Microsoft_x86_cdecl_gcc, model::ABI::Microsoft_x86_fastcall, model::ABI::Microsoft_x86_fastcall_gcc, model::ABI::Microsoft_x86_stdcall, model::ABI::Microsoft_x86_stdcall_gcc, model::ABI::Microsoft_x86_thiscall, model::ABI::Microsoft_x86_vectorcall, model::ABI::AAPCS // There are known issues // model::ABI::AAPCS64, // There are known issues // model::ABI::SystemV_MIPS_o32, // model::ABI::SystemV_MIPSEL_o32 // Unable to reliably test: no easy access to a compiler // model::ABI::Pascal_x86, // Unable to reliably test: QEMU aborts // model::ABI::SystemZ_s390x, }; if (!llvm::is_contained(ABIsTheConversionIsEnabledFor, ABI)) { revng_log(Log, "Analysis was aborted because the `safe` (default) mode of " "the conversion was selected and the conversion for the " "current ABI ('" << model::ABI::getName(ABI).str() << "') is not considered stable."); return; } } // Determines the strictness of register state deductions bool SoftDeductions = (ABIConfidence == "low"); // This reuses the verification map within the `model::VerifyHelper` in // an incompatible manner. DO NOT pass this object into a normal // verification routine or things are going to break down. model::VerifyHelper VectorVH; // Choose the applicable functions and run the conversion for them. using abi::FunctionType::filterTypes; auto ToConvert = filterTypes(Model->Types()); for (model::RawFunctionType *Old : ToConvert) { if (!checkVectorRegisterSupport(VectorVH, Model->getTypePath(Old))) { // TODO: remove this check after `abi::FunctionType` supports vectors. revng_log(Log, "Skip a function conversion because it requires vector type " "support: " << serializeToString(Model->getTypePath(Old->key()))); continue; } revng_log(Log, "Converting a function: " << serializeToString(Model->getTypePath(Old->key()))); if (Log.isEnabled()) { model::TypePath Reference = Model->getTypePath(Old->key()); revng_assert(!Reference.empty()); std::string Message = ""; for (model::Function &Function : Model->Functions()) if (Function.Prototype() == Reference) Message += "'" + Function.name().str().str() + "', "; if (!Message.empty()) { Message.resize(Message.size() - 2); revng_log(Log, "It's a prototype of " << Message); } } namespace FT = abi::FunctionType; if (auto New = FT::tryConvertToCABI(*Old, Model, ABI, SoftDeductions)) { // If the conversion succeeds, make sure the returned type is valid, revng_assert(New->isValid()); // and verifies if (VerifyLog.isEnabled()) New->get()->verify(true); revng_log(Log, "Function Conversion Successful: " << serializeToString(*New)); } else { // Do nothing if the conversion failed (the model is not modified). // `RawFunctionType` is still used for those functions. // This might be an indication of an ABI misdetection. revng_log(Log, "Function Conversion Failed."); } } // Don't forget to clean up any possible remainders of removed types. purgeUnnamedAndUnreachableTypes(Model); } }; pipeline::RegisterAnalysis ToCABIAnalysis;