mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
297 lines
11 KiB
C++
297 lines
11 KiB
C++
/// \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<bool>
|
|
checkVectorRegisterSupport(model::VerifyHelper &VH, const model::Type &Type);
|
|
|
|
static RecursiveCoroutine<bool>
|
|
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<bool>
|
|
checkVectorRegisterSupport(model::VerifyHelper &VH,
|
|
const model::PrimitiveTypeKind::Values &Kind) {
|
|
rc_return VH.maybeFail(Kind != model::PrimitiveTypeKind::Float,
|
|
"Floating Point primitive found.");
|
|
}
|
|
|
|
template<typename RealType>
|
|
inline RecursiveCoroutine<bool>
|
|
underlyingHelper(model::VerifyHelper &VH, const model::Type &Value) {
|
|
const RealType *Cast = llvm::cast<RealType>(&Value);
|
|
rc_return rc_recur checkVectorRegisterSupport(VH, Cast->UnderlyingType());
|
|
}
|
|
|
|
static RecursiveCoroutine<bool>
|
|
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<bool>
|
|
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<model::PrimitiveType>(&Type)->PrimitiveKind();
|
|
Result = rc_recur checkVectorRegisterSupport(VH, Kind);
|
|
} break;
|
|
|
|
case model::TypeKind::EnumType:
|
|
Result = rc_recur underlyingHelper<model::EnumType>(VH, Type);
|
|
break;
|
|
|
|
case model::TypeKind::TypedefType:
|
|
Result = rc_recur underlyingHelper<model::TypedefType>(VH, Type);
|
|
break;
|
|
|
|
case model::TypeKind::StructType:
|
|
Result = true;
|
|
for (const auto &F : llvm::cast<model::StructType>(&Type)->Fields())
|
|
Result = Result && rc_recur checkVectorRegisterSupport(VH, F.Type());
|
|
break;
|
|
|
|
case model::TypeKind::UnionType:
|
|
Result = true;
|
|
for (const auto &F : llvm::cast<model::UnionType>(&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<CABIFT>(&Type)->Arguments())
|
|
Result = Result && rc_recur checkVectorRegisterSupport(VH, A.Type());
|
|
const auto &ReturnType = llvm::cast<CABIFT>(&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<RawFT>(&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<RawFT>(&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<RawFT>(&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<std::vector<pipeline::Kind *>> 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::RawFunctionType>(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<ConvertFunctionsToCABI> ToCABIAnalysis;
|