This patch implements the initial support for upstreaming [llubi](https://github.com/dtcxzyw/llvm-ub-aware-interpreter). It only provides the minimal functionality to run a simple main function. I hope we can focus on the interface design in this PR, rather than trivial implementations for each instruction. RFC link: https://discourse.llvm.org/t/rfc-upstreaming-llvm-ub-aware-interpreter/89645 Excluding the driver `llubi.cpp`, this patch contains three components for better decoupling: + `Value.h/cpp`: Value representation + `Context.h/cpp`: Global state management (e.g., memory) and interpreter configuration + `Interpreter.cpp`: The main interpreter loop Compared to the out-of-tree version, the major differences are listed below: + The interpreter logic always returns the control to its caller, i.e., it never calls `exit/abort` when immediate UBs are triggered. + `EventHandler` provides an interface to dump the trace. It also allows callers to inspect the actual value and verify the correctness of analysis passes (e.g, KnownBits/SCEV). + The context is designed to be reentrant. That is, you can call `runFunction` multiple times. But its usefulness remains in doubt due to side effects made by previous calls. + `runFunction` handles function calls with a loop, instead of calling itself recursively. This makes it no longer bounded by the stack depth. + Uninitialized memory is planned to be approximated by returning random values each time an uninitialized byte is loaded.
130 lines
4.4 KiB
C++
130 lines
4.4 KiB
C++
//===- Context.cpp - State Tracking for llubi -----------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file tracks the global states (e.g., memory) of the interpreter.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Context.h"
|
|
#include "llvm/Support/MathExtras.h"
|
|
|
|
namespace llvm::ubi {
|
|
|
|
Context::Context(Module &M)
|
|
: Ctx(M.getContext()), M(M), DL(M.getDataLayout()),
|
|
TLIImpl(M.getTargetTriple()) {}
|
|
|
|
Context::~Context() = default;
|
|
|
|
AnyValue Context::getConstantValueImpl(Constant *C) {
|
|
if (isa<PoisonValue>(C))
|
|
return AnyValue::getPoisonValue(*this, C->getType());
|
|
|
|
// TODO: Handle ConstantInt vector.
|
|
if (auto *CI = dyn_cast<ConstantInt>(C))
|
|
return CI->getValue();
|
|
|
|
llvm_unreachable("Unrecognized constant");
|
|
}
|
|
|
|
const AnyValue &Context::getConstantValue(Constant *C) {
|
|
auto It = ConstCache.find(C);
|
|
if (It != ConstCache.end())
|
|
return It->second;
|
|
|
|
return ConstCache.emplace(C, getConstantValueImpl(C)).first->second;
|
|
}
|
|
|
|
MemoryObject::~MemoryObject() = default;
|
|
MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
|
|
unsigned AS, MemInitKind InitKind)
|
|
: Address(Addr), Size(Size), Name(Name), AS(AS),
|
|
State(InitKind != MemInitKind::Poisoned ? MemoryObjectState::Alive
|
|
: MemoryObjectState::Dead) {
|
|
switch (InitKind) {
|
|
case MemInitKind::Zeroed:
|
|
Bytes.resize(Size, Byte{0, ByteKind::Concrete});
|
|
break;
|
|
case MemInitKind::Uninitialized:
|
|
Bytes.resize(Size, Byte{0, ByteKind::Undef});
|
|
break;
|
|
case MemInitKind::Poisoned:
|
|
Bytes.resize(Size, Byte{0, ByteKind::Poison});
|
|
break;
|
|
}
|
|
}
|
|
|
|
IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
|
|
uint64_t Align,
|
|
StringRef Name, unsigned AS,
|
|
MemInitKind InitKind) {
|
|
if (MaxMem != 0 && SaturatingAdd(UsedMem, Size) >= MaxMem)
|
|
return nullptr;
|
|
uint64_t AlignedAddr = alignTo(AllocationBase, Align);
|
|
auto MemObj =
|
|
makeIntrusiveRefCnt<MemoryObject>(AlignedAddr, Size, Name, AS, InitKind);
|
|
MemoryObjects[AlignedAddr] = MemObj;
|
|
AllocationBase = AlignedAddr + Size;
|
|
UsedMem += Size;
|
|
return MemObj;
|
|
}
|
|
|
|
bool Context::free(uint64_t Address) {
|
|
auto It = MemoryObjects.find(Address);
|
|
if (It == MemoryObjects.end())
|
|
return false;
|
|
UsedMem -= It->second->getSize();
|
|
It->second->markAsFreed();
|
|
MemoryObjects.erase(It);
|
|
return true;
|
|
}
|
|
|
|
Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
|
|
assert(Obj && "Cannot determine the address space of a null memory object");
|
|
return Pointer(
|
|
Obj,
|
|
APInt(DL.getPointerSizeInBits(Obj->getAddressSpace()), Obj->getAddress()),
|
|
/*Offset=*/0);
|
|
}
|
|
|
|
void MemoryObject::markAsFreed() {
|
|
State = MemoryObjectState::Freed;
|
|
Bytes.clear();
|
|
}
|
|
|
|
void MemoryObject::writeRawBytes(uint64_t Offset, const void *Data,
|
|
uint64_t Length) {
|
|
assert(SaturatingAdd(Offset, Length) <= Size && "Write out of bounds");
|
|
const uint8_t *ByteData = static_cast<const uint8_t *>(Data);
|
|
for (uint64_t I = 0; I < Length; ++I)
|
|
Bytes[Offset + I].set(ByteData[I]);
|
|
}
|
|
|
|
void MemoryObject::writeInteger(uint64_t Offset, const APInt &Int,
|
|
const DataLayout &DL) {
|
|
uint64_t BitWidth = Int.getBitWidth();
|
|
uint64_t IntSize = divideCeil(BitWidth, 8);
|
|
assert(SaturatingAdd(Offset, IntSize) <= Size && "Write out of bounds");
|
|
for (uint64_t I = 0; I < IntSize; ++I) {
|
|
uint64_t ByteIndex = DL.isLittleEndian() ? I : (IntSize - 1 - I);
|
|
uint64_t Bits = std::min(BitWidth - ByteIndex * 8, uint64_t(8));
|
|
Bytes[Offset + I].set(Int.extractBitsAsZExtValue(Bits, ByteIndex * 8));
|
|
}
|
|
}
|
|
void MemoryObject::writeFloat(uint64_t Offset, const APFloat &Float,
|
|
const DataLayout &DL) {
|
|
writeInteger(Offset, Float.bitcastToAPInt(), DL);
|
|
}
|
|
void MemoryObject::writePointer(uint64_t Offset, const Pointer &Ptr,
|
|
const DataLayout &DL) {
|
|
writeInteger(Offset, Ptr.address(), DL);
|
|
// TODO: provenance
|
|
}
|
|
|
|
} // namespace llvm::ubi
|