/* * Copyright (c) 2008, 2009, Wayne Meissner * * All rights reserved. * * This file is part of ruby-ffi. * * This code is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * version 3 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see . */ #include #include #include #include #include #include #include "rbffi.h" #include "compat.h" #include "AbstractMemory.h" #include "Pointer.h" #include "Function.h" static inline char* memory_address(VALUE self); VALUE rbffi_AbstractMemoryClass = Qnil; static VALUE NullPointerErrorClass = Qnil; static ID id_to_ptr = 0, id_plus = 0, id_call = 0; static VALUE memory_allocate(VALUE klass) { AbstractMemory* memory; VALUE obj; obj = Data_Make_Struct(klass, AbstractMemory, NULL, -1, memory); memory->flags = MEM_RD | MEM_WR; return obj; } #define VAL(x, swap) (unlikely(((memory->flags & MEM_SWAP) != 0)) ? swap((x)) : (x)) #define NUM_OP(name, type, toNative, fromNative, swap) \ static void memory_op_put_##name(AbstractMemory* memory, long off, VALUE value); \ static void \ memory_op_put_##name(AbstractMemory* memory, long off, VALUE value) \ { \ type tmp = (type) VAL(toNative(value), swap); \ checkWrite(memory); \ checkBounds(memory, off, sizeof(type)); \ memcpy(memory->address + off, &tmp, sizeof(tmp)); \ } \ static VALUE memory_put_##name(VALUE self, VALUE offset, VALUE value); \ static VALUE \ memory_put_##name(VALUE self, VALUE offset, VALUE value) \ { \ AbstractMemory* memory; \ Data_Get_Struct(self, AbstractMemory, memory); \ memory_op_put_##name(memory, NUM2LONG(offset), value); \ return self; \ } \ static VALUE memory_op_get_##name(AbstractMemory* memory, long off); \ static VALUE \ memory_op_get_##name(AbstractMemory* memory, long off) \ { \ type tmp; \ checkRead(memory); \ checkBounds(memory, off, sizeof(type)); \ memcpy(&tmp, memory->address + off, sizeof(tmp)); \ return fromNative(VAL(tmp, swap)); \ } \ static VALUE memory_get_##name(VALUE self, VALUE offset); \ static VALUE \ memory_get_##name(VALUE self, VALUE offset) \ { \ AbstractMemory* memory; \ Data_Get_Struct(self, AbstractMemory, memory); \ return memory_op_get_##name(memory, NUM2LONG(offset)); \ } \ static MemoryOp memory_op_##name = { memory_op_get_##name, memory_op_put_##name }; \ \ static VALUE memory_put_array_of_##name(VALUE self, VALUE offset, VALUE ary); \ static VALUE \ memory_put_array_of_##name(VALUE self, VALUE offset, VALUE ary) \ { \ long count = RARRAY_LEN(ary); \ long off = NUM2LONG(offset); \ AbstractMemory* memory = MEMORY(self); \ long i; \ checkWrite(memory); \ checkBounds(memory, off, count * sizeof(type)); \ for (i = 0; i < count; i++) { \ type tmp = (type) VAL(toNative(RARRAY_PTR(ary)[i]), swap); \ memcpy(memory->address + off + (i * sizeof(type)), &tmp, sizeof(tmp)); \ } \ return self; \ } \ static VALUE memory_get_array_of_##name(VALUE self, VALUE offset, VALUE length); \ static VALUE \ memory_get_array_of_##name(VALUE self, VALUE offset, VALUE length) \ { \ long count = NUM2LONG(length); \ long off = NUM2LONG(offset); \ AbstractMemory* memory = MEMORY(self); \ long i; \ checkRead(memory); \ checkBounds(memory, off, count * sizeof(type)); \ VALUE retVal = rb_ary_new2(count); \ for (i = 0; i < count; ++i) { \ type tmp; \ memcpy(&tmp, memory->address + off + (i * sizeof(type)), sizeof(tmp)); \ rb_ary_push(retVal, fromNative(VAL(tmp, swap))); \ } \ return retVal; \ } #define NOSWAP(x) (x) #define bswap16(x) (((x) >> 8) & 0xff) | (((x) << 8) & 0xff00); static inline int16_t SWAPS16(int16_t x) { return bswap16(x); } static inline uint16_t SWAPU16(uint16_t x) { return bswap16(x); } #define SWAP16(x) (x) #if __GNUC__ < 4 #define bswap32(x) \ (((x << 24) & 0xff000000) | \ ((x << 8) & 0x00ff0000) | \ ((x >> 8) & 0x0000ff00) | \ ((x >> 24) & 0x000000ff)) #define bswap64(x) \ (((x << 56) & 0xff00000000000000ULL) | \ ((x << 40) & 0x00ff000000000000ULL) | \ ((x << 24) & 0x0000ff0000000000ULL) | \ ((x << 8) & 0x000000ff00000000ULL) | \ ((x >> 8) & 0x00000000ff000000ULL) | \ ((x >> 24) & 0x0000000000ff0000ULL) | \ ((x >> 40) & 0x000000000000ff00ULL) | \ ((x >> 56) & 0x00000000000000ffULL)) static inline int32_t SWAPS32(int32_t x) { return bswap32(x); } static inline uint32_t SWAPU32(uint32_t x) { return bswap32(x); } static inline int64_t SWAPS64(int64_t x) { return bswap64(x); } static inline uint64_t SWAPU64(uint64_t x) { return bswap64(x); } #else # define SWAPU32(x) __builtin_bswap32(x) # define SWAPS32(x) __builtin_bswap32(x) # define SWAPS64(x) __builtin_bswap64(x) # define SWAPU64(x) __builtin_bswap64(x) #endif #if LONG_MAX > INT_MAX # define SWAPSLONG SWAPS64 # define SWAPULONG SWAPU64 #else # define SWAPSLONG SWAPS32 # define SWAPULONG SWAPU32 #endif NUM_OP(int8, int8_t, NUM2INT, INT2NUM, NOSWAP); NUM_OP(uint8, uint8_t, NUM2UINT, UINT2NUM, NOSWAP); NUM_OP(int16, int16_t, NUM2INT, INT2NUM, SWAPS16); NUM_OP(uint16, uint16_t, NUM2UINT, UINT2NUM, SWAPU16); NUM_OP(int32, int32_t, NUM2INT, INT2NUM, SWAPS32); NUM_OP(uint32, uint32_t, NUM2UINT, UINT2NUM, SWAPU32); NUM_OP(int64, int64_t, NUM2LL, LL2NUM, SWAPS64); NUM_OP(uint64, uint64_t, NUM2ULL, ULL2NUM, SWAPU64); NUM_OP(long, long, NUM2LONG, LONG2NUM, SWAPSLONG); NUM_OP(ulong, unsigned long, NUM2ULONG, ULONG2NUM, SWAPULONG); NUM_OP(float32, float, NUM2DBL, rb_float_new, NOSWAP); NUM_OP(float64, double, NUM2DBL, rb_float_new, NOSWAP); static inline void* get_pointer_value(VALUE value) { const int type = TYPE(value); if (type == T_DATA && rb_obj_is_kind_of(value, rbffi_PointerClass)) { return memory_address(value); } else if (type == T_NIL) { return NULL; } else if (type == T_FIXNUM) { return (void *) (uintptr_t) FIX2INT(value); } else if (type == T_BIGNUM) { return (void *) (uintptr_t) NUM2ULL(value); } else if (rb_respond_to(value, id_to_ptr)) { return MEMORY_PTR(rb_funcall2(value, id_to_ptr, 0, NULL)); } else { rb_raise(rb_eArgError, "value is not a pointer"); return NULL; } } NUM_OP(pointer, void *, get_pointer_value, rbffi_Pointer_NewInstance, NOSWAP); static VALUE memory_clear(VALUE self) { AbstractMemory* ptr = MEMORY(self); memset(ptr->address, 0, ptr->size); return self; } static VALUE memory_size(VALUE self) { AbstractMemory* ptr; Data_Get_Struct(self, AbstractMemory, ptr); return LONG2NUM(ptr->size); } static VALUE memory_get_string(int argc, VALUE* argv, VALUE self) { VALUE length = Qnil, offset = Qnil; AbstractMemory* ptr = MEMORY(self); long off, len; char* end; int nargs = rb_scan_args(argc, argv, "11", &offset, &length); off = NUM2LONG(offset); len = nargs > 1 && length != Qnil ? NUM2LONG(length) : (ptr->size - off); checkRead(ptr); checkBounds(ptr, off, len); end = memchr(ptr->address + off, 0, len); return rb_tainted_str_new((char *) ptr->address + off, (end != NULL ? end - ptr->address - off : len)); } static VALUE memory_get_array_of_string(int argc, VALUE* argv, VALUE self) { VALUE offset = Qnil, countnum = Qnil, retVal = Qnil; AbstractMemory* ptr; long off; int count; rb_scan_args(argc, argv, "11", &offset, &countnum); off = NUM2LONG(offset); count = (countnum == Qnil ? 0 : NUM2INT(countnum)); retVal = rb_ary_new2(count); Data_Get_Struct(self, AbstractMemory, ptr); checkRead(ptr); if (countnum != Qnil) { int i; checkBounds(ptr, off, count * sizeof (char*)); for (i = 0; i < count; ++i) { const char* strptr = *((const char**) (ptr->address + off) + i); rb_ary_push(retVal, (strptr == NULL ? Qnil : rb_tainted_str_new2(strptr))); } } else { checkBounds(ptr, off, sizeof (char*)); for ( ; off < ptr->size - (long) sizeof (void *); off += (long) sizeof (void *)) { const char* strptr = *(const char**) (ptr->address + off); if (strptr == NULL) { break; } rb_ary_push(retVal, rb_tainted_str_new2(strptr)); } } return retVal; } static VALUE memory_put_string(VALUE self, VALUE offset, VALUE str) { AbstractMemory* ptr = MEMORY(self); long off, len; Check_Type(str, T_STRING); off = NUM2LONG(offset); len = RSTRING_LEN(str); checkWrite(ptr); checkBounds(ptr, off, len + 1); if (rb_safe_level() >= 1 && OBJ_TAINTED(str)) { rb_raise(rb_eSecurityError, "Writing unsafe string to memory"); return Qnil; } memcpy(ptr->address + off, RSTRING_PTR(str), len); *((char *) ptr->address + off + len) = '\0'; return self; } static VALUE memory_get_bytes(VALUE self, VALUE offset, VALUE length) { AbstractMemory* ptr = MEMORY(self); long off, len; off = NUM2LONG(offset); len = NUM2LONG(length); checkRead(ptr); checkBounds(ptr, off, len); return rb_tainted_str_new((char *) ptr->address + off, len); } static VALUE memory_put_bytes(int argc, VALUE* argv, VALUE self) { AbstractMemory* ptr = MEMORY(self); VALUE offset = Qnil, str = Qnil, rbIndex = Qnil, rbLength = Qnil; long off, len, idx; int nargs = rb_scan_args(argc, argv, "22", &offset, &str, &rbIndex, &rbLength); Check_Type(str, T_STRING); off = NUM2LONG(offset); idx = nargs > 2 ? NUM2LONG(rbIndex) : 0; if (idx < 0) { rb_raise(rb_eRangeError, "index canot be less than zero"); return Qnil; } len = nargs > 3 ? NUM2LONG(rbLength) : (RSTRING_LEN(str) - idx); if ((idx + len) > RSTRING_LEN(str)) { rb_raise(rb_eRangeError, "index+length is greater than size of string"); return Qnil; } checkWrite(ptr); checkBounds(ptr, off, len); if (rb_safe_level() >= 1 && OBJ_TAINTED(str)) { rb_raise(rb_eSecurityError, "Writing unsafe string to memory"); return Qnil; } memcpy(ptr->address + off, RSTRING_PTR(str) + idx, len); return self; } static VALUE memory_type_size(VALUE self) { AbstractMemory* ptr; Data_Get_Struct(self, AbstractMemory, ptr); return INT2NUM(ptr->typeSize); } static VALUE memory_aref(VALUE self, VALUE idx) { AbstractMemory* ptr; VALUE rbOffset = Qnil; Data_Get_Struct(self, AbstractMemory, ptr); rbOffset = ULONG2NUM(NUM2ULONG(idx) * ptr->typeSize); return rb_funcall2(self, id_plus, 1, &rbOffset); } static inline char* memory_address(VALUE obj) { return ((AbstractMemory *) DATA_PTR(obj))->address; } AbstractMemory* rbffi_AbstractMemory_Cast(VALUE obj, VALUE klass) { if (rb_obj_is_kind_of(obj, klass)) { AbstractMemory* memory; Data_Get_Struct(obj, AbstractMemory, memory); return memory; } rb_raise(rb_eArgError, "Invalid Memory object"); return NULL; } void rbffi_AbstractMemory_Error(AbstractMemory *mem, int op) { VALUE rbErrorClass = mem->address == NULL ? NullPointerErrorClass : rb_eRuntimeError; if (op == MEM_RD) { rb_raise(rbErrorClass, "invalid memory read at address=%p", mem->address); } else if (op == MEM_WR) { rb_raise(rbErrorClass, "invalid memory write at address=%p", mem->address); } else { rb_raise(rbErrorClass, "invalid memory access at address=%p", mem->address); } } static VALUE memory_op_get_strptr(AbstractMemory* ptr, long offset) { void* tmp = NULL; if (ptr != NULL && ptr->address != NULL) { checkRead(ptr); checkBounds(ptr, offset, sizeof(tmp)); memcpy(&tmp, ptr->address + offset, sizeof(tmp)); } return tmp != NULL ? rb_tainted_str_new2(tmp) : Qnil; } static void memory_op_put_strptr(AbstractMemory* ptr, long offset, VALUE value) { rb_raise(rb_eArgError, "Cannot set :string fields"); } static MemoryOp memory_op_strptr = { memory_op_get_strptr, memory_op_put_strptr }; //static MemoryOp memory_op_pointer = { memory_op_get_pointer, memory_op_put_pointer }; MemoryOps rbffi_AbstractMemoryOps = { .int8 = &memory_op_int8, .uint8 = &memory_op_uint8, .int16 = &memory_op_int16, .uint16 = &memory_op_uint16, .int32 = &memory_op_int32, .uint32 = &memory_op_uint32, .int64 = &memory_op_int64, .uint64 = &memory_op_uint64, .slong = &memory_op_long, .ulong = &memory_op_ulong, .float32 = &memory_op_float32, .float64 = &memory_op_float64, .pointer = &memory_op_pointer, .strptr = &memory_op_strptr, }; void rbffi_AbstractMemory_Init(VALUE moduleFFI) { VALUE classMemory = rb_define_class_under(moduleFFI, "AbstractMemory", rb_cObject); rbffi_AbstractMemoryClass = classMemory; rb_global_variable(&rbffi_AbstractMemoryClass); rb_define_alloc_func(classMemory, memory_allocate); NullPointerErrorClass = rb_define_class_under(moduleFFI, "NullPointerError", rb_eRuntimeError); rb_global_variable(&NullPointerErrorClass); #undef INT #define INT(type) \ rb_define_method(classMemory, "put_" #type, memory_put_##type, 2); \ rb_define_method(classMemory, "get_" #type, memory_get_##type, 1); \ rb_define_method(classMemory, "put_u" #type, memory_put_u##type, 2); \ rb_define_method(classMemory, "get_u" #type, memory_get_u##type, 1); \ rb_define_method(classMemory, "put_array_of_" #type, memory_put_array_of_##type, 2); \ rb_define_method(classMemory, "get_array_of_" #type, memory_get_array_of_##type, 2); \ rb_define_method(classMemory, "put_array_of_u" #type, memory_put_array_of_u##type, 2); \ rb_define_method(classMemory, "get_array_of_u" #type, memory_get_array_of_u##type, 2); INT(int8); INT(int16); INT(int32); INT(int64); INT(long); #define ALIAS(name, old) \ rb_define_alias(classMemory, "put_" #name, "put_" #old); \ rb_define_alias(classMemory, "get_" #name, "get_" #old); \ rb_define_alias(classMemory, "put_u" #name, "put_u" #old); \ rb_define_alias(classMemory, "get_u" #name, "get_u" #old); \ rb_define_alias(classMemory, "put_array_of_" #name, "put_array_of_" #old); \ rb_define_alias(classMemory, "get_array_of_" #name, "get_array_of_" #old); \ rb_define_alias(classMemory, "put_array_of_u" #name, "put_array_of_u" #old); \ rb_define_alias(classMemory, "get_array_of_u" #name, "get_array_of_u" #old); ALIAS(char, int8); ALIAS(short, int16); ALIAS(int, int32); ALIAS(long_long, int64); rb_define_method(classMemory, "put_float32", memory_put_float32, 2); rb_define_method(classMemory, "get_float32", memory_get_float32, 1); rb_define_alias(classMemory, "put_float", "put_float32"); rb_define_alias(classMemory, "get_float", "get_float32"); rb_define_method(classMemory, "put_array_of_float32", memory_put_array_of_float32, 2); rb_define_method(classMemory, "get_array_of_float32", memory_get_array_of_float32, 2); rb_define_alias(classMemory, "put_array_of_float", "put_array_of_float32"); rb_define_alias(classMemory, "get_array_of_float", "get_array_of_float32"); rb_define_method(classMemory, "put_float64", memory_put_float64, 2); rb_define_method(classMemory, "get_float64", memory_get_float64, 1); rb_define_alias(classMemory, "put_double", "put_float64"); rb_define_alias(classMemory, "get_double", "get_float64"); rb_define_method(classMemory, "put_array_of_float64", memory_put_array_of_float64, 2); rb_define_method(classMemory, "get_array_of_float64", memory_get_array_of_float64, 2); rb_define_alias(classMemory, "put_array_of_double", "put_array_of_float64"); rb_define_alias(classMemory, "get_array_of_double", "get_array_of_float64"); rb_define_method(classMemory, "put_pointer", memory_put_pointer, 2); rb_define_method(classMemory, "get_pointer", memory_get_pointer, 1); rb_define_method(classMemory, "put_array_of_pointer", memory_put_array_of_pointer, 2); rb_define_method(classMemory, "get_array_of_pointer", memory_get_array_of_pointer, 2); rb_define_method(classMemory, "get_string", memory_get_string, -1); rb_define_method(classMemory, "put_string", memory_put_string, 2); rb_define_method(classMemory, "get_bytes", memory_get_bytes, 2); rb_define_method(classMemory, "put_bytes", memory_put_bytes, -1); rb_define_method(classMemory, "get_array_of_string", memory_get_array_of_string, -1); rb_define_method(classMemory, "clear", memory_clear, 0); rb_define_method(classMemory, "total", memory_size, 0); rb_define_alias(classMemory, "size", "total"); rb_define_method(classMemory, "type_size", memory_type_size, 0); rb_define_method(classMemory, "[]", memory_aref, 1); id_to_ptr = rb_intern("to_ptr"); id_call = rb_intern("call"); id_plus = rb_intern("+"); }