Skip to content

Commit

Permalink
standalone_miri - Add a bunch of C FFI hackery
Browse files Browse the repository at this point in the history
  • Loading branch information
thepowersgang committed Dec 10, 2023
1 parent 6cc28a5 commit 65d69e1
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 68 deletions.
7 changes: 6 additions & 1 deletion tools/standalone_miri/miri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1689,7 +1689,12 @@ bool InterpreterThread::step_one(Value& out_thread_result)
{
const auto& fe = te.fcn.as_Intrinsic();
auto ret_ty = state.get_lvalue_ty(te.ret_val);
if( !this->call_intrinsic(rv, ret_ty, fe.name, fe.params, ::std::move(sub_args)) )
if( fe.name == "va_start" ) {
// Initialise the return value with the number of formal arguments
//rv = Value::new_usize( cur_frame.fcn->m_args.size() );
LOG_TODO("va_start");
}
else if( !this->call_intrinsic(rv, ret_ty, fe.name, fe.params, ::std::move(sub_args)) )
{
// Early return, don't want to update stmt_idx yet
return false;
Expand Down
212 changes: 185 additions & 27 deletions tools/standalone_miri/miri_extern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ extern "C" {
ssize_t write(int, const void*, size_t);
}
#endif
namespace FfiHelpers {
static const char* read_cstr(const Value& v, size_t ptr_ofs, size_t* out_strlen=nullptr, size_t max_len=SIZE_MAX)
{
bool _is_mut;
size_t size;
// Get the base pointer and allocation size (checking for at least one valid byte to start with)
const char* ptr = reinterpret_cast<const char*>( v.read_pointer_unsafe(ptr_ofs, 1, /*out->*/ size, _is_mut) );
size_t len = 0;
// Seek until either out of space, or a NUL is found
while(size -- && *ptr && max_len --)
{
ptr ++;
len ++;
}
if( out_strlen )
{
*out_strlen = len;
}
return reinterpret_cast<const char*>(v.read_pointer_const(0, max_len == 0 ? len : len + 1)); // Final read will trigger an error if the NUL isn't there
}
}

// A very simple implementation of `printf`-style formatting, with internal checks
::std::string format_string(const char* fmt, const ::std::vector<Value>& args, size_t cur_arg) {
Expand Down Expand Up @@ -101,26 +122,27 @@ ::std::string format_string(const char* fmt, const ::std::vector<Value>& args, s
case 'd':
output << std::setfill(pad) << std::setw(width);
output << std::dec << H::read_signed(arg);
cur_arg += 1;
break;
case 'u':
output << std::setfill(pad) << std::setw(width);
output << std::dec << H::read_unsigned(arg);
cur_arg += 1;
break;
case 'x':
output << std::setfill(pad) << std::setw(width);
output << std::hex << H::read_unsigned(arg);
cur_arg += 1;
break;
case 's':
output << std::setfill(pad) << std::setw(width);
output << FfiHelpers::read_cstr(arg, 0);
break;
case 'p':
LOG_ASSERT(arg.size() == POINTER_SIZE, "Printf `%p` with wrong size integer - " << arg.size() << " != " << POINTER_SIZE);
output << std::hex << "0x" << args.at(cur_arg).read_usize(0);
cur_arg += 1;
output << std::hex << "0x" << arg.read_usize(0);
break;
default:
LOG_FATAL("Malformed printf string - unexpected character `" << *s << "`");
}
cur_arg += 1;
}
else {
output << *s;
Expand All @@ -131,27 +153,6 @@ ::std::string format_string(const char* fmt, const ::std::vector<Value>& args, s

bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, const ::std::string& abi, ::std::vector<Value> args)
{
struct FfiHelpers {
static const char* read_cstr(const Value& v, size_t ptr_ofs, size_t* out_strlen=nullptr)
{
bool _is_mut;
size_t size;
// Get the base pointer and allocation size (checking for at least one valid byte to start with)
const char* ptr = reinterpret_cast<const char*>( v.read_pointer_unsafe(0, 1, /*out->*/ size, _is_mut) );
size_t len = 0;
// Seek until either out of space, or a NUL is found
while(size -- && *ptr)
{
ptr ++;
len ++;
}
if( out_strlen )
{
*out_strlen = len;
}
return reinterpret_cast<const char*>(v.read_pointer_const(0, len + 1)); // Final read will trigger an error if the NUL isn't there
}
};
if( link_name == "__rust_allocate" || link_name == "__rust_alloc" || link_name == "__rust_alloc_zeroed" )
{
static unsigned s_alloc_count = 0;
Expand Down Expand Up @@ -866,6 +867,13 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c
//
// <stdlib.h>
//
else if( link_name == "atoi" )
{
// extern int atoi(const char *nptr);
size_t len = 0;
const char* nptr = FfiHelpers::read_cstr(args.at(0), 0, &len);
rv = Value::new_i32( atoi(nptr) );
}
else if( link_name == "malloc" )
{
auto size = args.at(0).read_usize(0);
Expand All @@ -887,6 +895,28 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c

rv = Value::new_pointer_ofs(rty, 0, RelocationPtr::new_alloc(::std::move(alloc)));
}
else if( link_name == "realloc" )
{
auto size = args.at(1).read_usize(0);
auto rty = ::HIR::TypeRef(RawType::Unit).wrap( TypeWrapper::Ty::Pointer, 0 );
auto alloc = Allocation::new_alloc(size, "realloc");
if( args.at(0).read_usize(0) == 0 ) {
}
else {
auto ptr = args.at(0).read_pointer_valref_mut(0, 0);
LOG_ASSERT(ptr.m_offset == 0, "`realloc` with pointer not to beginning of block");

LOG_ASSERT(ptr.m_alloc, "`realloc` with no backing allocation attached to pointer");
LOG_ASSERT(ptr.m_alloc.is_alloc(), "`realloc` with no backing allocation attached to pointer");
auto& old_alloc = ptr.m_alloc.alloc();

auto s = ::std::min(size, old_alloc.size());
auto ptr2 = args.at(0).read_pointer_valref_mut(0, s);
alloc->write_value(0, ptr2.read_value(0, s));
old_alloc.mark_as_freed();
}
rv = Value::new_pointer_ofs(rty, 0, RelocationPtr::new_alloc(::std::move(alloc)));
}
else if( link_name == "free" )
{
auto ptr = args.at(0).read_pointer_valref_mut(0, 0);
Expand Down Expand Up @@ -985,6 +1015,16 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c
rv.write_usize(0, 0);
}
}
else if( link_name == "strcpy" ) {
// strlen - custom implementation to ensure validity
size_t len = 0;
auto src = FfiHelpers::read_cstr(args.at(1), 0, &len);

auto vr = args.at(0).read_pointer_valref_mut(0, len+1).to_write();
memcpy(vr.data_ptr_mut(len+1), src, len+1);
vr.mark_bytes_valid(0, len+1);
rv = std::move(args.at(0));
}
else if( link_name == "strlen" )
{
// strlen - custom implementation to ensure validity
Expand All @@ -1000,10 +1040,56 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c
size_t len;
const char* a = FfiHelpers::read_cstr(args.at(0), 0, &len);
const char* b = FfiHelpers::read_cstr(args.at(1), 0, &len);
LOG_DEBUG("strcmp(\"" << a <<"\", \"" << b << "\")");

int rv_i = strcmp(a, b);
rv = Value::new_i32(rv_i);
}
else if( link_name == "strncmp" )
{
size_t len;
const char* a = FfiHelpers::read_cstr(args.at(0), 0, &len);
const char* b = FfiHelpers::read_cstr(args.at(1), 0, &len);
size_t max = args.at(2).read_usize(0);
LOG_DEBUG("strncmp(\"" << a <<"\", \"" << b << "\", " << max <<")");

int rv_i = strncmp(a, b, max);
rv = Value::new_i32(rv_i);
}
else if( link_name == "strdup" )
{
size_t len;
const char* a = FfiHelpers::read_cstr(args.at(0), 0, &len);

auto alloc = Allocation::new_alloc(len+1, "strdup");
auto rty = ::HIR::TypeRef(RawType::Unit).wrap( TypeWrapper::Ty::Pointer, 0 );

rv = Value::new_pointer_ofs(rty, 0, RelocationPtr::new_alloc(::std::move(alloc)));
{
auto vr = rv.read_pointer_valref_mut(0, len+1).to_write();
memcpy(vr.data_ptr_mut(len+1), a, len+1);
vr.mark_bytes_valid(0, len+1);
}
}
else if( link_name == "strndup" )
{
size_t max = args.at(1).read_usize(0);
size_t len;
const char* a = FfiHelpers::read_cstr(args.at(0), 0, &len, max);

auto alloc = Allocation::new_alloc(len+1, "strndup");
auto rty = ::HIR::TypeRef(RawType::Unit).wrap( TypeWrapper::Ty::Pointer, 0 );

rv = Value::new_pointer_ofs(rty, 0, RelocationPtr::new_alloc(::std::move(alloc)));
{
auto vr = rv.read_pointer_valref_mut(0, len+1).to_write();
auto p = vr.data_ptr_mut(len+1);
memcpy(p, a, len);
p[len] = 0;
vr.mark_bytes_valid(0, len+1);
}
}
// --- ?
else if( link_name == "getenv" )
{
const auto* name = FfiHelpers::read_cstr(args.at(0), 0);
Expand Down Expand Up @@ -1070,7 +1156,79 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c
{
const auto* path = FfiHelpers::read_cstr(args.at(0), 0);
const auto* mode = FfiHelpers::read_cstr(args.at(1), 0);
LOG_TODO("fopen(\"" << path << "\", \"" << mode << "\")");
FILE* fp = fopen(path, mode);
if(fp) {
rv = Value::new_ffiptr(FFIPointer::new_void("FILE", fp));
}
else {
rv = Value::new_usize(0);
}
}
else if( link_name == "fclose" )
{
FILE* fp = static_cast<FILE*>(args.at(0).read_pointer_tagged_nonnull(0, "FILE"));
int retval = fclose(fp);
args.at(0).get_relocation(0).ffi().release();
rv = Value::new_i32(retval);
}
else if( link_name == "fseek" )
{
// int fseek(FILE *stream, long offset, int whence);
FILE* fp = static_cast<FILE*>(args.at(0).read_pointer_tagged_nonnull(0, "FILE"));
auto offset = args.at(1).read_i64(0);
int whence_v = args.at(2).read_i32(0);
int whence;
switch(whence_v)
{
case -1: whence = SEEK_END; break;
case 0: whence = SEEK_CUR; break;
case 1: whence = SEEK_SET; break;
default:
rv = Value::new_i32(-1);
return true;
}

rv = Value::new_i32( fseek(fp, offset, whence) );
}
else if( link_name == "ftell" )
{
// long ftell(FILE *stream);
FILE* fp = static_cast<FILE*>(args.at(0).read_pointer_tagged_nonnull(0, "FILE"));
rv = Value::new_i64( ftell(fp) );
}
else if( link_name == "fread")
{
// size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
FILE* fp = static_cast<FILE*>(args.at(3).read_pointer_tagged_nonnull(0, "FILE"));
auto nmemb = args.at(2).read_usize(0);
auto size = args.at(1).read_usize(0);
auto ptr = args.at(0).read_pointer_valref_mut(0, size*nmemb).to_write();

int retval = fread(ptr.data_ptr_mut(size*nmemb), size, nmemb, fp);
if(retval > 0)
{
ptr.mark_bytes_valid(0, retval * size);
}
rv = Value::new_i64(retval);
}
// --- setjmp.h
else if( link_name == "setjmp" )
{
rv = Value::new_i32(0);
}
else if( link_name == "longjmp" )
{
LOG_TODO("Call `longjmp`");
}
// --- ctype.h
else if( link_name == "isspace" ) {
rv = Value::new_i32( isspace(args.at(0).read_i32(0)) );
}
else if( link_name == "isalpha" ) {
rv = Value::new_i32( isalpha(args.at(0).read_i32(0)) );
}
else if( link_name == "isalnum" ) {
rv = Value::new_i32( isalnum(args.at(0).read_i32(0)) );
}
else
{
Expand Down
16 changes: 16 additions & 0 deletions tools/standalone_miri/miri_intrinsic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ bool InterpreterThread::call_intrinsic(Value& rv, const HIR::TypeRef& ret_ty, co
rv.write_usize(0, new_ofs);
}
}
else if( name == "ptr_diff" )
{
auto ty_size = ty_params.tys.at(0).get_size();

const auto& ptr_a = args.at(0);
const auto& ptr_b = args.at(1);

auto alloc_a = ptr_a.get_relocation(0);
auto alloc_b = ptr_b.get_relocation(0);
auto ofs_a = ptr_a.read_usize(0);
auto ofs_b = ptr_b.read_usize(0);
if( alloc_a != alloc_b ) {
LOG_ERROR("`ptr_diff` with different allocations - " << alloc_a << " & " << alloc_b);
}
rv = Value::new_usize( (ofs_a - ofs_b) / ty_size );
}
else if( name == "ptr_guaranteed_eq" ) {
bool is_eq = true;
is_eq &= args.at(0).read_usize(0) == args.at(1).read_usize(0);
Expand Down
28 changes: 11 additions & 17 deletions tools/standalone_miri/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,9 @@ ::std::ostream& operator<<(::std::ostream& os, const Allocation* x)
return os;
}

FfiLayout FfiLayout::new_const_bytes(size_t s)
bool FfiPointerInner::is_valid_read(size_t o, size_t s) const
{
return FfiLayout {
{ Range {s, true, false} }
};
}
bool FfiLayout::is_valid_read(size_t o, size_t s) const
{
for(const auto& r : ranges)
for(const auto& r : layout)
{
if( o < r.len ) {
if( !r.is_valid )
Expand Down Expand Up @@ -257,7 +251,7 @@ ::std::ostream& operator<<(::std::ostream& os, const RelocationPtr& x)
os << "\"" << FmtEscaped(x.str()) << "\"";
break;
case RelocationPtr::Ty::FfiPointer:
os << "FFI '" << x.ffi().tag_name << "' " << x.ffi().ptr_value;
os << "FFI '" << x.ffi().tag_name() << "' " << x.ffi().ptr_value();
break;
}
}
Expand Down Expand Up @@ -307,11 +301,11 @@ void* ValueCommonRead::read_pointer_tagged_null(size_t rd_ofs, const char* tag)
{
case RelocationPtr::Ty::FfiPointer: {
const auto& f = reloc.ffi();
assert(f.tag_name);
assert(tag);
if( ::std::strcmp(f.tag_name, tag) != 0 )
LOG_FATAL("Expected a '" << tag << "' pointer, got a '" << f.tag_name << "' pointer");
return f.ptr_value;
LOG_ASSERT(f.tag_name(), "Expected a '" << tag << "' pointer, but no tag present");
if( ::std::strcmp(f.tag_name(), tag) != 0 )
LOG_FATAL("Expected a '" << tag << "' pointer, got a '" << f.tag_name() << "' pointer");
return f.ptr_value();
}
default:
LOG_FATAL("Reading a tagged pointer from non-FFI source");
Expand Down Expand Up @@ -367,7 +361,7 @@ void* ValueCommonRead::read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size
// TODO: Have an idea of mutability and available size from FFI
out_size = size - ofs;
out_is_mut = false;
return reinterpret_cast<char*>(reloc.ffi().ptr_value) + ofs;
return reinterpret_cast<char*>(reloc.ffi().ptr_value()) + ofs;
}
}
throw "";
Expand Down Expand Up @@ -961,12 +955,12 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v)
} break;
case RelocationPtr::Ty::FfiPointer:
const auto& p = alloc_ptr.ffi();
assert( in_bounds(v.m_offset, v.m_size, p.layout->get_size()) );
assert( in_bounds(v.m_offset, v.m_size, p.get_size()) );
auto flags = os.flags();
os << ::std::hex;
for(size_t i = v.m_offset; i < v.m_offset + v.m_size; i++)
{
os << ::std::setw(2) << ::std::setfill('0') << (int) ((char*)p.ptr_value)[i];
os << ::std::setw(2) << ::std::setfill('0') << (int) ((char*)p.ptr_value())[i];
}
os.flags(flags);
break;
Expand Down Expand Up @@ -1059,7 +1053,7 @@ Value ValueRef::read_value(size_t ofs, size_t size) const
case RelocationPtr::Ty::FfiPointer: {
LOG_ASSERT(in_bounds(m_offset + ofs, size, m_alloc.ffi().get_size()), "");
auto rv = Value::with_size(size, false);
rv.write_bytes(0, reinterpret_cast<const char*>(m_alloc.ffi().ptr_value) + m_offset + ofs, size);
rv.write_bytes(0, reinterpret_cast<const char*>(m_alloc.ffi().ptr_value()) + m_offset + ofs, size);
return rv;
}
default:
Expand Down
Loading

0 comments on commit 65d69e1

Please sign in to comment.