diff --git a/tools/standalone_miri/miri.cpp b/tools/standalone_miri/miri.cpp index 6a4de893..5bc895fa 100644 --- a/tools/standalone_miri/miri.cpp +++ b/tools/standalone_miri/miri.cpp @@ -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; diff --git a/tools/standalone_miri/miri_extern.cpp b/tools/standalone_miri/miri_extern.cpp index 79049ab2..a94e1512 100644 --- a/tools/standalone_miri/miri_extern.cpp +++ b/tools/standalone_miri/miri_extern.cpp @@ -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( 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(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& args, size_t cur_arg) { @@ -101,26 +122,27 @@ ::std::string format_string(const char* fmt, const ::std::vector& 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; @@ -131,27 +153,6 @@ ::std::string format_string(const char* fmt, const ::std::vector& args, s bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, const ::std::string& abi, ::std::vector 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( 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(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; @@ -866,6 +867,13 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c // // // + 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); @@ -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); @@ -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 @@ -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); @@ -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(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(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(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(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 { diff --git a/tools/standalone_miri/miri_intrinsic.cpp b/tools/standalone_miri/miri_intrinsic.cpp index 928f18fa..bf2e4ebc 100644 --- a/tools/standalone_miri/miri_intrinsic.cpp +++ b/tools/standalone_miri/miri_intrinsic.cpp @@ -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); diff --git a/tools/standalone_miri/value.cpp b/tools/standalone_miri/value.cpp index 4d73d370..e774aaf1 100644 --- a/tools/standalone_miri/value.cpp +++ b/tools/standalone_miri/value.cpp @@ -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 ) @@ -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; } } @@ -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"); @@ -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(reloc.ffi().ptr_value) + ofs; + return reinterpret_cast(reloc.ffi().ptr_value()) + ofs; } } throw ""; @@ -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; @@ -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(m_alloc.ffi().ptr_value) + m_offset + ofs, size); + rv.write_bytes(0, reinterpret_cast(m_alloc.ffi().ptr_value()) + m_offset + ofs, size); return rv; } default: diff --git a/tools/standalone_miri/value.hpp b/tools/standalone_miri/value.hpp index cdc46b04..c7f4ba12 100644 --- a/tools/standalone_miri/value.hpp +++ b/tools/standalone_miri/value.hpp @@ -24,20 +24,36 @@ class Allocation; struct Value; struct ValueRef; -struct FfiLayout +struct FfiPointerInner { struct Range { size_t len; bool is_valid; bool is_writable; }; - ::std::vector ranges; - static FfiLayout new_const_bytes(size_t s); + // FFI pointers require the following: + // - A tag indicating where they're valid/from + // - A data format (e.g. size of allocation, internal data format) + // - If the data format is unspecified (null) then it's a void pointer + // - An actual pointer + // TODO: Add extra metadata + + // Pointer value, returned by the FFI + void* ptr_value; + // Tag name, used for validity checking by FFI hooks + const char* tag_name; + + ::std::vector layout; + + void release() { + ptr_value = nullptr; + tag_name = "#released"; + } size_t get_size() const { size_t rv = 0; - for(const auto& r : ranges) + for(const auto& r : layout) rv += r.len; return rv; } @@ -45,29 +61,24 @@ struct FfiLayout }; struct FFIPointer { - // FFI pointers require the following: - // - A tag indicating where they're valid/from - // - A data format (e.g. size of allocation, internal data format) - // - If the data format is unspecified (null) then it's a void pointer - // - An actual pointer - // TODO: Add extra metadata + ::std::shared_ptr inner; - // Pointer value, returned by the FFI - void* ptr_value; - // Tag name, used for validty checking by FFI hooks - const char* tag_name; - ::std::shared_ptr layout; + const char* tag_name() const { return inner->tag_name; } + void* ptr_value() const { return inner->ptr_value; } + size_t get_size() const { return inner->get_size(); } + bool is_valid_read(size_t o, size_t s) const { + return inner->is_valid_read(o,s); + } + void release() { + inner->release(); + } static FFIPointer new_void(const char* name, const void* v) { - return FFIPointer { const_cast(v), name, ::std::make_shared() }; + return FFIPointer{ ::std::make_shared(FfiPointerInner{ const_cast(v), name, {} }) }; } static FFIPointer new_const_bytes(const char* name, const void* s, size_t size) { - return FFIPointer { const_cast(s), name, ::std::make_shared(FfiLayout::new_const_bytes(size)) }; + return FFIPointer{ ::std::make_shared(FfiPointerInner{ const_cast(s), name, { FfiPointerInner::Range { size, true, false } }}) }; }; - - size_t get_size() const { - return (layout ? layout->get_size() : 0); - } }; class AllocationHandle @@ -174,6 +185,11 @@ class RelocationPtr assert(get_ty() == Ty::StdString); return *static_cast(get_ptr()); } + FFIPointer& ffi() { + assert(*this); + assert(get_ty() == Ty::FfiPointer); + return *static_cast(get_ptr()); + } const FFIPointer& ffi() const { assert(*this); assert(get_ty() == Ty::FfiPointer); @@ -629,8 +645,8 @@ struct ValueRef: ::std::memcpy(dst, m_alloc.str().data() + m_offset + ofs, size); break; case RelocationPtr::Ty::FfiPointer: - assert( m_alloc.ffi().layout->is_valid_read(m_offset + ofs, size) ); - ::std::memcpy(dst, (const char*)m_alloc.ffi().ptr_value + m_offset + ofs, size); + assert( m_alloc.ffi().is_valid_read(m_offset + ofs, size) ); + ::std::memcpy(dst, (const char*)m_alloc.ffi().ptr_value() + m_offset + ofs, size); break; default: //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << );