-
Notifications
You must be signed in to change notification settings - Fork 609
/
pointer.rb
451 lines (379 loc) · 13.7 KB
/
pointer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
module Rubinius
module FFI
##
# Pointer is Rubinius's "fat" pointer class. It represents an actual
# pointer, in C language terms, to an address in memory. They're called
# fat pointers because the Pointer object is an wrapper around
# the actual pointer, the Rubinius runtime doesn't have direct access
# to the raw address.
#
# This class is used extensively in FFI usage to interface with various
# parts of the underlying system. It provides a number of operations
# for operating on the memory that is pointed to. These operations effectively
# give Rubinius the cast/read capabilities available in C, but using
# high level methods.
#
# MemoryPointer objects can be put in autorelease mode. In this mode,
# when the GC cleans up a MemoryPointer object, the memory it points
# to is passed to free(3), releasing the memory back to the OS.
#
# NOTE: MemoryPointer exposes direct, unmanaged operations on any
# memory. It therefore MUST be used carefully. Reading or writing to
# invalid address will cause bus errors and segmentation faults.
#
class Pointer
include PointerAccessors
def initialize(a1, a2=undefined)
if undefined.equal? a2
self.address = a1
else
@type = a1
self.address = a2
end
end
def inspect
# Don't have this print the data at the location. It can crash everything.
addr = address()
if addr < 0
sign = "-"
addr = -addr
else
sign = ""
end
"#<#{self.class.name} address=#{sign}0x#{addr.to_s(16)}>"
end
# Return the address pointed to as an Integer
def address
Rubinius.primitive :pointer_address
raise PrimitiveFailure, "FFI::Pointer#address primitive failed"
end
alias_method :to_i, :address
# Set the address pointed to from an Integer
def address=(address)
Rubinius.primitive :pointer_set_address
raise PrimitiveFailure, "FFI::Pointer#address= primitive failed"
end
def null?
address == 0x0
end
# Add +value+ to the address pointed to and return a new Pointer
def +(value)
Rubinius.primitive :pointer_add
raise PrimitiveFailure, "FFI::Pointer#+ primitive failed"
end
# Indicates if +self+ and +other+ point to the same address
def ==(other)
return false unless other.kind_of? Pointer
return address == other.address
end
def network_order(start, size)
Rubinius.primitive :pointer_network_order
raise PrimitiveFailure, "FFI::Pointer#network_order primitive failed"
end
# Read +len+ bytes from the memory pointed to and return them as
# a String
def read_string_length(len)
Rubinius.primitive :pointer_read_string
raise PrimitiveFailure, "FFI::Pointer#read_string_length primitive failed"
end
# Read bytes from the memory pointed to until a NULL is seen, return
# the bytes as a String
def read_string_to_null
Rubinius.primitive :pointer_read_string_to_null
raise PrimitiveFailure, "FFI::Pointer#read_string_to_null primitive failed"
end
# Read bytes as a String from the memory pointed to
def read_string(len=nil)
if len
read_string_length(len)
else
read_string_to_null
end
end
# FFI compat methods
def get_bytes(offset, length)
(self + offset).read_string_length(length)
end
# Write String +str+ as bytes into the memory pointed to. Only
# write up to +len+ bytes.
def write_string_length(str, len)
Rubinius.primitive :pointer_write_string
raise PrimitiveFailure, "FFI::Pointer#write_string_length primitive failed"
end
# Write a String +str+ as bytes to the memory pointed to.
def write_string(str, len=nil)
len = str.bytesize unless len
write_string_length(str, len);
end
# Read a sequence of types +type+, length +length+, using method +reader+
def read_array_of_type(type, reader, length, signed=nil)
# If signed is not nil and is actually a boolean,
# then use that as an argument to the reader, which
# is then assumed to support signed reading.
args = []
args = [signed] if !signed.nil?
# Build up the array
ary = []
size = FFI.type_size(FFI.find_type type)
tmp = self
length.times {
ary << tmp.send(reader, *args)
tmp += size
}
ary
end
# Write a sequence of types +type+ using method +reader+ from +ary+
def write_array_of_type(type, writer, ary)
size = FFI.type_size(FFI.find_type type)
tmp = self
ary.each do |i|
tmp.send(writer, i)
tmp += size
end
self
end
# Read bytes from +offset+ from the memory pointed to as type +type+
def get_at_offset(offset, type)
Rubinius.primitive :pointer_get_at_offset
raise PrimitiveFailure, "FFI::Pointer#get_at_offset primitive failed"
end
# Write +val+ as type +type+ to bytes from +offset+
def set_at_offset(offset, type, val)
Rubinius.primitive :pointer_set_at_offset
raise PrimitiveFailure, "FFI::Pointer#set_at_offset primitive failed"
end
# Number of bytes taken up by a pointer.
def self.size
Rubinius::WORDSIZE / 8
end
# Primitive methods
def primitive_read_char(signed)
Rubinius.primitive :pointer_read_char
raise PrimitiveFailure, "FFI::Pointer#primitive_read_char primitive failed"
end
def primitive_write_char(obj)
Rubinius.primitive :pointer_write_char
raise PrimitiveFailure, "FFI::Pointer#primitive_write_char primitive failed"
end
def primitive_read_short(signed)
Rubinius.primitive :pointer_read_short
raise PrimitiveFailure, "FFI::Pointer#primitive_read_short primitive failed"
end
def primitive_write_short(obj)
Rubinius.primitive :pointer_write_short
raise PrimitiveFailure, "FFI::Pointer#primitive_write_short primitive failed"
end
def primitive_read_int(signed)
Rubinius.primitive :pointer_read_int
raise PrimitiveFailure, "FFI::Pointer#primitive_read_int primitive failed"
end
def primitive_write_int(obj)
Rubinius.primitive :pointer_write_int
raise PrimitiveFailure, "FFI::Pointer#primitive_write_int primitive failed"
end
def primitive_read_long(signed)
Rubinius.primitive :pointer_read_long
raise PrimitiveFailure, "FFI::Pointer#primitive_read_long primitive failed"
end
def primitive_write_long(obj)
Rubinius.primitive :pointer_write_long
raise PrimitiveFailure, "FFI::Pointer#primitive_write_long primitive failed"
end
def primitive_read_long_long(signed)
Rubinius.primitive :pointer_read_long_long
raise PrimitiveFailure, "FFI::Pointer#primitive_read_long_long primitive failed"
end
def primitive_write_long_long(obj)
Rubinius.primitive :pointer_write_long_long
raise PrimitiveFailure, "FFI::Pointer#primitive_write_long_long primitive failed"
end
def primitive_read_float
Rubinius.primitive :pointer_read_float
raise PrimitiveFailure, "FFI::Pointer#primitive_read_float primitive failed"
end
def primitive_write_float(obj)
Rubinius.primitive :pointer_write_float
raise PrimitiveFailure, "FFI::Pointer#primitive_write_float primitive failed"
end
def primitive_read_double
Rubinius.primitive :pointer_read_double
raise PrimitiveFailure, "FFI::Pointer#primitive_read_double primitive failed"
end
def primitive_write_double(obj)
Rubinius.primitive :pointer_write_double
raise PrimitiveFailure, "FFI::Pointer#primitive_write_double primitive failed"
end
def primitive_read_pointer
Rubinius.primitive :pointer_read_pointer
raise PrimitiveFailure, "FFI::Pointer#primitive_read_pointer primitive failed"
end
def primitive_write_pointer(obj)
Rubinius.primitive :pointer_write_pointer
raise PrimitiveFailure, "FFI::Pointer#primitive_write_pointer primitive failed"
end
##
# If +val+ is true, this Pointer object will call
# free() on it's address when it is garbage collected.
def autorelease=(val)
Rubinius.primitive :pointer_set_autorelease
raise PrimitiveFailure, "FFI::Pointer#autorelease= primitive failed"
end
##
# Returns true if autorelease is enabled, otherwise false.
def autorelease?
Rubinius.primitive :pointer_autorelease_p
raise PrimitiveFailure, "FFI::Pointer#pointer_autorelease_p primitive failed"
end
NULL = Pointer.new(0x0)
end
class MemoryPointer < Pointer
# call-seq:
# MemoryPointer.new(num) => MemoryPointer instance of <i>num</i> bytes
# MemoryPointer.new(sym) => MemoryPointer instance with number
# of bytes need by FFI type <i>sym</i>
# MemoryPointer.new(obj) => MemoryPointer instance with number
# of <i>obj.size</i> bytes
# MemoryPointer.new(sym, count) => MemoryPointer instance with number
# of bytes need by length-<i>count</i> array
# of FFI type <i>sym</i>
# MemoryPointer.new(obj, count) => MemoryPointer instance with number
# of bytes need by length-<i>count</i> array
# of <i>obj.size</i> bytes
# MemoryPointer.new(arg) { |p| ... }
#
# Both forms create a MemoryPointer instance. The number of bytes to
# allocate is either specified directly or by passing an FFI type, which
# specifies the number of bytes needed for that type.
#
# The form without a block returns the MemoryPointer instance. The form
# with a block yields the MemoryPointer instance and frees the memory
# when the block returns. The value returned is the value of the block.
#
def self.new(type, count=nil, clear=true)
if type.kind_of? Fixnum
size = type
elsif type.kind_of? Symbol
type = FFI.find_type type
size = FFI.type_size(type)
else
size = type.size
end
if count
total = size * count
else
total = size
end
ptr = malloc total
ptr.total = total
ptr.type_size = size
FFI::Platform::POSIX.memset ptr, 0, total if clear
if block_given?
begin
value = yield ptr
ensure
ptr.free
end
return value
else
ptr.autorelease = true
ptr
end
end
def self.malloc(total)
Rubinius.primitive :pointer_malloc
raise PrimitiveFailure, "FFI::MemoryPointer.malloc primitive failed"
end
def self.from_string(str)
ptr = new str.bytesize + 1
ptr.write_string str + "\0"
ptr
end
def copy
other = malloc total
other.total = total
other.type_size = type_size
FFI::Platform::POSIX.memcpy other, self, total
Rubinius.privately do
other.initialize_copy self
end
other
end
# Indicates how many bytes the chunk of memory that is pointed to takes up.
attr_accessor :total
# Indicates how many bytes the type that the pointer is cast as uses.
attr_accessor :type_size
# Access the MemoryPointer like a C array, accessing the +which+ number
# element in memory. The position of the element is calculate from
# +@type_size+ and +which+. A new MemoryPointer object is returned, which
# points to the address of the element.
#
# Example:
# ptr = MemoryPointer.new(:int, 20)
# new_ptr = ptr[9]
#
# c-equiv:
# int *ptr = (int*)malloc(sizeof(int) * 20);
# int *new_ptr;
# new_ptr = &ptr[9];
#
def [](which)
raise ArgumentError, "unknown type size" unless @type_size
self + (which * @type_size)
end
# Release the memory pointed to back to the OS.
def free
Rubinius.primitive :pointer_free
raise PrimitiveFailure, "FFI::MemoryPointer#free primitive failed"
end
end
class DynamicLibrary::Symbol < Pointer
def initialize(library, ptr, name)
@library = library
@name = name
self.address = ptr.address
end
def inspect
"#<FFI::Library::Symbol name=#{@name} address=#{address.to_s(16)}>"
end
end
class Function < Pointer
def initialize(ret_type, arg_types, val=nil, options=nil, &block)
if block
if val or options
raise ArgumentError, "specify a block or a proc/address, not both"
end
val = block
end
args = arg_types.map { |x| FFI.find_type(x) }
ret = FFI.find_type(ret_type)
if val.kind_of? Pointer
@function = FFI.generate_function(val, :func, args, ret)
self.address = val.address
elsif val.respond_to? :call
@function, ptr = FFI.generate_trampoline(val, :func, args, ret)
self.address = ptr.address
else
raise ArgumentError, "value wasn't a FFI::Pointer and didn't respond to call"
end
# Hook the created function into the method_table so that #call goes
# straight to it.
sc = Rubinius::Type.object_singleton_class(self)
sc.method_table.store :call, @function, :public
end
attr_reader :function
# Hook this Function up to be an instance/class method +name+ on +mod+
def attach(mod, name)
unless mod.kind_of?(Module)
raise TypeError, "mod must be a Module"
end
name = name.to_sym
# Make it available as a method callable directly..
sc = Rubinius::Type.object_singleton_class(mod)
sc.method_table.store name, @function, :public
# and expose it as a private method for people who
# want to include this module.
mod.method_table.store name, @function, :public
end
end
end
end