memory_io 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +137 -2
- data/lib/memory_io/io.rb +72 -8
- data/lib/memory_io/process.rb +33 -5
- data/lib/memory_io/types/basic/number.rb +72 -0
- data/lib/memory_io/types/clang/c_str.rb +39 -0
- data/lib/memory_io/types/cpp/string.rb +120 -0
- data/lib/memory_io/types/record.rb +68 -0
- data/lib/memory_io/types/type.rb +144 -13
- data/lib/memory_io/types/types.rb +12 -9
- data/lib/memory_io/util.rb +41 -3
- data/lib/memory_io/version.rb +1 -1
- metadata +6 -4
- data/lib/memory_io/types/c_str.rb +0 -34
- data/lib/memory_io/types/number.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03e970969f587b0c3baa79df39eb2599c63ab845
|
4
|
+
data.tar.gz: cd31136f30b5a11859bd84f8b1218cab749cabb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 598e6d01cbe4e1bb779785ccdd677bd8cff45253cf1ea46e55b546e566d4c81eb9fe0771ebb8207cb6f4d257784e9585cd9d99b7f85108bd9ab1eed9f937c68e
|
7
|
+
data.tar.gz: 9b1adc88d6f34e155af0b7cafd7aa2c02a8ca1afa13de5f861d6e0bd073b61f313b1de630a15cd869c6b8974d8e882915667df867b1427dd0ea02c72f37063c5
|
data/README.md
CHANGED
@@ -5,10 +5,74 @@
|
|
5
5
|
[](https://inch-ci.org/github/david942j/memory_io)
|
6
6
|
[](http://choosealicense.com/licenses/mit/)
|
7
7
|
|
8
|
-
#
|
8
|
+
# MemoryIO
|
9
9
|
|
10
10
|
Read/Write complicated structures in memory easily.
|
11
11
|
|
12
|
+
## Motivation
|
13
|
+
|
14
|
+
I usually need to dump a structure, say `string` in C++, from memory for debugging.
|
15
|
+
This is not hard if using gdb.
|
16
|
+
However, gdb doesn't support writing Ruby scripts
|
17
|
+
(unless you use [gdb-ruby](https://github.com/david942j/gdb-ruby), which has dependency of **MemoryIO**).
|
18
|
+
So I create this repo and want to make the debug procedure much easier.
|
19
|
+
|
20
|
+
This repo has two main targets:
|
21
|
+
|
22
|
+
1. To communiate with memory easily.
|
23
|
+
2. To collect all common structures for debugging/learning.
|
24
|
+
|
25
|
+
## Why
|
26
|
+
|
27
|
+
It's not hard to read/write a process's memory (simply open the file `/proc/$PID/mem`),
|
28
|
+
but it still worth to wrap it.
|
29
|
+
|
30
|
+
This repo also targets to collect all common structures, such as how to parse a C++/Rust/Python object from memory.
|
31
|
+
Therefore, **Pull Requests of adding new structures** are welcome :D
|
32
|
+
|
33
|
+
## Supported Platform
|
34
|
+
|
35
|
+
- Linux
|
36
|
+
- (TODO) Windows
|
37
|
+
- (TODO) MacOS
|
38
|
+
|
39
|
+
## Implemented Structures
|
40
|
+
|
41
|
+
Following is the list of supported structures.
|
42
|
+
Each type has a full-name and an alias. For example,
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require 'memory_io'
|
46
|
+
|
47
|
+
process = MemoryIO.attach(`pidof victim`.to_i)
|
48
|
+
# read a 64-bit unsigned integer
|
49
|
+
process.read(0x601000, 1, as: 'basic/u64')
|
50
|
+
# is equivalent to
|
51
|
+
process.read(0x601000, 1, as: :u64)
|
52
|
+
```
|
53
|
+
|
54
|
+
Goto [the online document](http://www.rubydoc.info/github/david942j/memory_io/master/MemoryIO/Types) for more details
|
55
|
+
of each type.
|
56
|
+
|
57
|
+
### BASIC
|
58
|
+
- `basic/u8`: An unsigned 8-bit integer. Also known as: `:u8`
|
59
|
+
- `basic/u16`: An unsigned 16-bit integer. Also known as: `:u16`
|
60
|
+
- `basic/u32`: An unsigned 32-bit integer. Also known as: `:u32`
|
61
|
+
- `basic/u64`: An unsigned 64-bit integer. Also known as: `:u64`
|
62
|
+
- `basic/s8`: A signed 8-bit integer. Also known as: `:s8`
|
63
|
+
- `basic/s16`: A signed 16-bit integer. Also known as: `:s16`
|
64
|
+
- `basic/s32`: A signed 32-bit integer. Also known as: `:s32`
|
65
|
+
- `basic/s64`: A signed 64-bit integer. Also known as: `:s64`
|
66
|
+
- `basic/float`: IEEE-754 32-bit floating number. Also known as: `:float`
|
67
|
+
- `basic/double`: IEEE-754 64-bit floating number. Also known as: `:double`
|
68
|
+
|
69
|
+
### CLANG
|
70
|
+
- `clang/c_str`: A null-terminated string. Also known as: `:c_str`
|
71
|
+
|
72
|
+
### CPP
|
73
|
+
- `cpp/string`: The `std::string` class in C++11. Also known as: `:string`
|
74
|
+
|
75
|
+
|
12
76
|
## Installation
|
13
77
|
|
14
78
|
Available on RubyGems.org!
|
@@ -20,7 +84,7 @@ $ gem install memory_io
|
|
20
84
|
## Usage
|
21
85
|
|
22
86
|
### Read Process's Memory
|
23
|
-
```
|
87
|
+
```ruby
|
24
88
|
require 'memory_io'
|
25
89
|
|
26
90
|
process = MemoryIO.attach(`pidof victim`.to_i)
|
@@ -37,3 +101,74 @@ process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
|
|
37
101
|
process.read('libc', 4)
|
38
102
|
#=> "\x7fELF"
|
39
103
|
```
|
104
|
+
|
105
|
+
### Write Process's Memory
|
106
|
+
```ruby
|
107
|
+
require 'memory_io'
|
108
|
+
|
109
|
+
process = MemoryIO.attach('self') # Hack! Write memory of this process directly!
|
110
|
+
string = 'A' * 16
|
111
|
+
pos = string.object_id * 2 + 16
|
112
|
+
process.read(pos, 16)
|
113
|
+
#=> 'AAAAAAAAAAAAAAAA'
|
114
|
+
|
115
|
+
process.write(pos, 'memory_changed!!')
|
116
|
+
string
|
117
|
+
#=> 'memory_changed!!'
|
118
|
+
```
|
119
|
+
|
120
|
+
### Customize Read
|
121
|
+
```ruby
|
122
|
+
require 'memory_io'
|
123
|
+
process = MemoryIO.attach(`pidof victim`.to_i)
|
124
|
+
|
125
|
+
# An example that read a chunk of pt-malloc.
|
126
|
+
read_chunk = lambda do |stream|
|
127
|
+
_prev_size = stream.read(8)
|
128
|
+
size = (stream.read(8).unpack('Q').first & -16) - 8
|
129
|
+
[size, stream.read(size)]
|
130
|
+
end
|
131
|
+
process.read('heap', 1, as: read_chunk)
|
132
|
+
#=> [24, "\xef\xbe\xad\xde\x00\x00...\x00"]
|
133
|
+
```
|
134
|
+
|
135
|
+
### Define Own Structure
|
136
|
+
```ruby
|
137
|
+
require 'memory_io'
|
138
|
+
process = MemoryIO.attach(`pidof victim`.to_i)
|
139
|
+
|
140
|
+
class MyType < MemoryIO::Types::Type
|
141
|
+
def self.read(stream)
|
142
|
+
self.new(stream.read(1))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Define this if you need to 'write' to memory
|
146
|
+
def self.write(stream, my_type)
|
147
|
+
stream.write(my_type.val)
|
148
|
+
end
|
149
|
+
|
150
|
+
attr_accessor :val
|
151
|
+
def initialize(val)
|
152
|
+
@val = val
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Use snake-case symbol.
|
157
|
+
process.read('libc', 4, as: :my_type)
|
158
|
+
#=> [#<MyType @val="\x7F">,
|
159
|
+
# #<MyType @val="E">,
|
160
|
+
# #<MyType @val="L">,
|
161
|
+
# #<MyType @val="F">]
|
162
|
+
|
163
|
+
process.write('libc', MyType.new('MEOW'), as: :my_type)
|
164
|
+
|
165
|
+
# See if memory changed
|
166
|
+
process.read('libc', 4)
|
167
|
+
#=> 'MEOW'
|
168
|
+
```
|
169
|
+
|
170
|
+
## Developing
|
171
|
+
|
172
|
+
### To Add a New Structure
|
173
|
+
|
174
|
+
TBA
|
data/lib/memory_io/io.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
require 'memory_io/types/types'
|
2
4
|
|
3
5
|
module MemoryIO
|
4
6
|
# Main class to use {MemoryIO}.
|
5
7
|
class IO
|
6
|
-
attr_reader :stream # @return [#pos=, #read, #write]
|
8
|
+
attr_reader :stream # @return [#pos, #pos=, #read, #write]
|
7
9
|
|
8
10
|
# Instantiate an {IO} object.
|
9
11
|
#
|
10
|
-
# @param [#pos=, #read, #write] stream
|
12
|
+
# @param [#pos, #pos=, #read, #write] stream
|
11
13
|
# The file-like object to be read/written.
|
12
14
|
# +file+ can be unwritable if you will not invoke any write-related method.
|
13
15
|
#
|
@@ -27,7 +29,7 @@ module MemoryIO
|
|
27
29
|
# @param [Integer?] from
|
28
30
|
# Invoke +stream.pos = from+ before starting to read.
|
29
31
|
# +nil+ for not changing current position of stream.
|
30
|
-
# @param [nil, Symbol,
|
32
|
+
# @param [nil, Symbol, Proc] as
|
31
33
|
# Decide the type/structure when reading.
|
32
34
|
# See {MemoryIO::Types} for all supported types.
|
33
35
|
#
|
@@ -58,7 +60,7 @@ module MemoryIO
|
|
58
60
|
#
|
59
61
|
# @example
|
60
62
|
# stream = StringIO.new('A' * 8 + 'B' * 8)
|
61
|
-
# io = MemoryIO.new(stream)
|
63
|
+
# io = MemoryIO::IO.new(stream)
|
62
64
|
# io.read(9)
|
63
65
|
# #=> "AAAAAAAAB"
|
64
66
|
# io.read(100)
|
@@ -74,7 +76,7 @@ module MemoryIO
|
|
74
76
|
# #=> [16962]
|
75
77
|
# @example
|
76
78
|
# stream = StringIO.new("\xef\xbe\xad\xde")
|
77
|
-
# io = MemoryIO.new(stream)
|
79
|
+
# io = MemoryIO::IO.new(stream)
|
78
80
|
# io.read(1, as: :u32)
|
79
81
|
# #=> 3735928559
|
80
82
|
# io.rewind
|
@@ -82,19 +84,21 @@ module MemoryIO
|
|
82
84
|
# #=> -559038737
|
83
85
|
# @example
|
84
86
|
# stream = StringIO.new("123\x0045678\x00")
|
85
|
-
# io = MemoryIO.new(stream)
|
87
|
+
# io = MemoryIO::IO.new(stream)
|
86
88
|
# io.read(2, as: :c_str)
|
87
89
|
# #=> ["123", "45678"]
|
88
90
|
# @example
|
89
91
|
# # pass lambda to `as`
|
90
92
|
# stream = StringIO.new("\x03123\x044567")
|
91
|
-
# io = MemoryIO.new(stream)
|
93
|
+
# io = MemoryIO::IO.new(stream)
|
92
94
|
# io.read(2, as: lambda { |stream| stream.read(stream.read(1).ord) })
|
93
95
|
# #=> ["123", "4567"]
|
94
96
|
#
|
95
97
|
# @note
|
96
98
|
# This method's arguments and return value are different with +::IO#read+.
|
97
99
|
# Check documents and examples.
|
100
|
+
#
|
101
|
+
# @see Types
|
98
102
|
def read(num_elements, from: nil, as: nil, force_array: false)
|
99
103
|
stream.pos = from if from
|
100
104
|
return stream.read(num_elements) if as.nil?
|
@@ -105,6 +109,64 @@ module MemoryIO
|
|
105
109
|
ret
|
106
110
|
end
|
107
111
|
|
112
|
+
# Write to stream.
|
113
|
+
#
|
114
|
+
# @param [Object, Array<Object>] objects
|
115
|
+
# Objects to be written.
|
116
|
+
#
|
117
|
+
# @param [Integer] from
|
118
|
+
# The position to start to write.
|
119
|
+
#
|
120
|
+
# @param [nil, Symbol, Proc] as
|
121
|
+
# Decide the method to process writing procedure.
|
122
|
+
# See {MemoryIO::Types} for all supported types.
|
123
|
+
#
|
124
|
+
# A +Proc+ is allowed, which should accept +stream+ and one object as arguments.
|
125
|
+
#
|
126
|
+
# If +objects+ is a descendent instance of {Types::Type} and +as+ is +nil,
|
127
|
+
# +objects.class+ will be used for +as+.
|
128
|
+
# Otherwise, when +as = nil+, this method will simply call +stream.write(objects)+.
|
129
|
+
#
|
130
|
+
# @return [void]
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# stream = StringIO.new
|
134
|
+
# io = MemoryIO::IO.new(stream)
|
135
|
+
# io.write('abcd')
|
136
|
+
# stream.string
|
137
|
+
# #=> "abcd"
|
138
|
+
#
|
139
|
+
# io.write([1, 2, 3, 4], from: 2, as: :u16)
|
140
|
+
# stream.string
|
141
|
+
# #=> "ab\x01\x00\x02\x00\x03\x00\x04\x00"
|
142
|
+
#
|
143
|
+
# io.write(['A', 'BB', 'CCC'], from: 0, as: :c_str)
|
144
|
+
# stream.string
|
145
|
+
# #=> "A\x00BB\x00CCC\x00\x00"
|
146
|
+
# @example
|
147
|
+
# stream = StringIO.new
|
148
|
+
# io = MemoryIO::IO.new(stream)
|
149
|
+
# io.write(%w[123 4567], as: ->(s, str) { s.write(str.size.chr + str) })
|
150
|
+
# stream.string
|
151
|
+
# #=> "\x03123\x044567"
|
152
|
+
#
|
153
|
+
# @example
|
154
|
+
# stream = StringIO.new
|
155
|
+
# io = MemoryIO::IO.new(stream)
|
156
|
+
# cpp_string = CPP::String.new('A' * 4, 15, 16)
|
157
|
+
# # equivalent to io.write(cpp_string, as: :'cpp/string')
|
158
|
+
# io.write(cpp_string)
|
159
|
+
# stream.string
|
160
|
+
# #=> "\x10\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00AAAA\x00"
|
161
|
+
# @see Types
|
162
|
+
def write(objects, from: nil, as: nil)
|
163
|
+
stream.pos = from if from
|
164
|
+
as ||= objects.class if objects.class.ancestors.include?(MemoryIO::Types::Type)
|
165
|
+
return stream.write(objects) if as.nil?
|
166
|
+
conv = to_proc(as, :write)
|
167
|
+
Array(objects).map { |o| conv.call(stream, o) }
|
168
|
+
end
|
169
|
+
|
108
170
|
# Set +stream+ to the beginning.
|
109
171
|
# i.e. invoke +stream.pos = 0+.
|
110
172
|
#
|
@@ -115,8 +177,10 @@ module MemoryIO
|
|
115
177
|
|
116
178
|
private
|
117
179
|
|
180
|
+
# @api private
|
118
181
|
def to_proc(as, rw)
|
119
|
-
ret = as.respond_to?(
|
182
|
+
ret = as.respond_to?(rw) ? as.method(rw) : as
|
183
|
+
ret = ret.respond_to?(:call) ? ret : MemoryIO::Types.get_proc(ret, rw)
|
120
184
|
raise ArgumentError, <<-EOERR.strip unless ret.respond_to?(:call)
|
121
185
|
Invalid argument `as`: #{as.inspect}. It should be either a Proc or a supported type of MemoryIO::Types.
|
122
186
|
EOERR
|
data/lib/memory_io/process.rb
CHANGED
@@ -76,7 +76,7 @@ $ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
|
|
76
76
|
#
|
77
77
|
# This method has *almost* same arguements and return types as {IO#read}.
|
78
78
|
# The only difference is this method needs parameter +addr+ (which
|
79
|
-
# will be passed to paramter +from+ in {IO#
|
79
|
+
# will be passed to paramter +from+ in {IO#read}).
|
80
80
|
#
|
81
81
|
# @param [Integer, String] addr
|
82
82
|
# The address start to read.
|
@@ -104,10 +104,38 @@ $ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
|
|
104
104
|
# #=> "\x7fELF"
|
105
105
|
# @see IO#read
|
106
106
|
def read(addr, num_elements, **options)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
mem_io(:read) { |io| io.read(num_elements, from: MemoryIO::Util.safe_eval(addr, bases), **options) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Write objects at +addr+.
|
111
|
+
#
|
112
|
+
# This method has *almost* same arguments as {IO#write}.
|
113
|
+
#
|
114
|
+
# @param [Integer, String] addr
|
115
|
+
# The address to start to write.
|
116
|
+
# See examples.
|
117
|
+
# @param [Object, Array<Object>] objects
|
118
|
+
# Objects to write.
|
119
|
+
# If +objects+ is an array, the write procedure will be invoked +objects.size+ times.
|
120
|
+
#
|
121
|
+
# @return [void]
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# process = MemoryIO.attach('self')
|
125
|
+
# s = 'A' * 16
|
126
|
+
# process.write(s.object_id * 2 + 16, 'BBBBCCCC')
|
127
|
+
# s
|
128
|
+
# #=> 'BBBBCCCCAAAAAAAA'
|
129
|
+
# @see IO#write
|
130
|
+
def write(addr, objects, **options)
|
131
|
+
mem_io(:write) { |io| io.write(objects, from: MemoryIO::Util.safe_eval(addr, bases), **options) }
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def mem_io(perm)
|
137
|
+
flags = perm == :write ? 'wb' : 'rb'
|
138
|
+
File.open(@mem, flags) { |f| yield MemoryIO::IO.new(f) }
|
111
139
|
end
|
112
140
|
end
|
113
141
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'memory_io/types/type'
|
2
|
+
|
3
|
+
module MemoryIO
|
4
|
+
module Types
|
5
|
+
# Define native types such as integers and floating numbers.
|
6
|
+
module Basic
|
7
|
+
# Register numbers to {Types}.
|
8
|
+
#
|
9
|
+
# All types registerd by this class are assumed as *little endian*.
|
10
|
+
#
|
11
|
+
# This class registered (un)signed {8, 16, 32, 64)-bit integers and IEEE-754 floating numbers.
|
12
|
+
class Number
|
13
|
+
# @param [Integer] bytes
|
14
|
+
# Bytes.
|
15
|
+
# @param [Boolean] signed
|
16
|
+
# Signed or unsigned.
|
17
|
+
# @param [String] pack_str
|
18
|
+
# The indicator to be passed to +Array#pack+ and +String#unpack+.
|
19
|
+
def initialize(bytes, signed, pack_str)
|
20
|
+
@bytes = bytes
|
21
|
+
@signed = signed
|
22
|
+
@pack_str = pack_str
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Integer]
|
26
|
+
def read(stream)
|
27
|
+
unpack(stream.read(@bytes))
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Integer] val
|
31
|
+
def write(stream, val)
|
32
|
+
stream.write(pack(val))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def unpack(str)
|
38
|
+
val = str.unpack(@pack_str).first
|
39
|
+
val -= (2**(@bytes * 8)) if @signed && val >= (2**(@bytes * 8 - 1))
|
40
|
+
val
|
41
|
+
end
|
42
|
+
|
43
|
+
def pack(val)
|
44
|
+
[val].pack(@pack_str)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Register (un)signed n-bits integers.
|
48
|
+
{
|
49
|
+
8 => 'C',
|
50
|
+
16 => 'S',
|
51
|
+
32 => 'I',
|
52
|
+
64 => 'Q'
|
53
|
+
}.each do |t, c|
|
54
|
+
Type.register(Number.new(t / 8, true, c),
|
55
|
+
alias: [:"basic/s#{t}", :"s#{t}"],
|
56
|
+
doc: "A signed #{t}-bit integer.")
|
57
|
+
Type.register(Number.new(t / 8, false, c),
|
58
|
+
alias: [:"basic/u#{t}", :"u#{t}"],
|
59
|
+
doc: "An unsigned #{t}-bit integer.")
|
60
|
+
end
|
61
|
+
|
62
|
+
# Register floating numbers.
|
63
|
+
Type.register(Number.new(4, false, 'F'),
|
64
|
+
alias: %i[basic/float float],
|
65
|
+
doc: 'IEEE-754 32-bit floating number.')
|
66
|
+
Type.register(Number.new(8, false, 'D'),
|
67
|
+
alias: %i[basic/double double],
|
68
|
+
doc: 'IEEE-754 64-bit floating number.')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
require 'memory_io/types/type'
|
4
|
+
|
5
|
+
module MemoryIO
|
6
|
+
module Types
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# Define structures used in C language.
|
10
|
+
module Clang
|
11
|
+
# A null-terminated string.
|
12
|
+
class CStr < Types::Type
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
# String without null byte.
|
17
|
+
def self.read(stream)
|
18
|
+
ret = ''
|
19
|
+
loop do
|
20
|
+
c = stream.read(1)
|
21
|
+
break if c.nil? || c == '' || c == "\x00"
|
22
|
+
ret << c
|
23
|
+
end
|
24
|
+
ret
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
#
|
29
|
+
# @param [String] val
|
30
|
+
# A null byte would be appended if +val+ not ends with null byte.
|
31
|
+
def self.write(stream, val)
|
32
|
+
val = val.to_s
|
33
|
+
val << "\x00" unless val.end_with?("\x00")
|
34
|
+
stream.write(val)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'memory_io/types/type'
|
2
|
+
|
3
|
+
module MemoryIO
|
4
|
+
module Types
|
5
|
+
# Structures in C++.
|
6
|
+
module CPP
|
7
|
+
# The `std::string` class in C++11.
|
8
|
+
#
|
9
|
+
# The std::string class can be seen as:
|
10
|
+
# class string {
|
11
|
+
# void* _M_dataplus;
|
12
|
+
# size_t string_length;
|
13
|
+
# union {
|
14
|
+
# char local_buf[15 + 1];
|
15
|
+
# size_t allocated_capacity;
|
16
|
+
# }
|
17
|
+
# };
|
18
|
+
class String < MemoryIO::Types::Type
|
19
|
+
# std::string uses inlined-buffer if string length isn't larger than {LOCAL_CAPACITY}.
|
20
|
+
LOCAL_CAPACITY = 15
|
21
|
+
|
22
|
+
attr_reader :data # @return [::String]
|
23
|
+
attr_reader :capacity # @return [Integer]
|
24
|
+
attr_reader :dataplus # @return [Integer]
|
25
|
+
|
26
|
+
# Instantiate a {CPP::String} object.
|
27
|
+
#
|
28
|
+
# @param [::String] data
|
29
|
+
# @param [Integer] capacity
|
30
|
+
# @param [Integer] dataplus
|
31
|
+
# A pointer.
|
32
|
+
def initialize(data, capacity, dataplus)
|
33
|
+
@data = data
|
34
|
+
@capacity = capacity
|
35
|
+
@dataplus = dataplus
|
36
|
+
end
|
37
|
+
|
38
|
+
# String length.
|
39
|
+
#
|
40
|
+
# @return [Integer]
|
41
|
+
def length
|
42
|
+
@data.size
|
43
|
+
end
|
44
|
+
alias size length
|
45
|
+
|
46
|
+
# Set data content.
|
47
|
+
#
|
48
|
+
# @param [String] str
|
49
|
+
def data=(str)
|
50
|
+
@data = str
|
51
|
+
warn("Length of str (#{str.size}) is larger than capacity (#{capacity})") if str.size > capacity
|
52
|
+
end
|
53
|
+
|
54
|
+
# Custom inspect view.
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
#
|
58
|
+
# @todo
|
59
|
+
# Let it be colorful in pry.
|
60
|
+
def inspect
|
61
|
+
format("#<%s @data=%s, @capacity=%d, @dataplus=0x%0#{SIZE_T * 2}x>",
|
62
|
+
self.class.name,
|
63
|
+
data.inspect,
|
64
|
+
capacity,
|
65
|
+
dataplus)
|
66
|
+
end
|
67
|
+
|
68
|
+
class << self
|
69
|
+
# @param [#pos, #pos=, #read] stream
|
70
|
+
#
|
71
|
+
# @return [CPP::String]
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# # echo '#include <string>\n#include <cstdio>\nint main() {' > a.cpp && \
|
75
|
+
# # echo 'std::string a="abcd"; printf("%p\\n", &a);' >> a.cpp && \
|
76
|
+
# # echo 'scanf("%*c"); return 0;}' >> a.cpp && \
|
77
|
+
# # g++ -std=c++11 a.cpp -o a
|
78
|
+
# Open3.popen2('stdbuf -o0 ./a') do |_i, o, t|
|
79
|
+
# process = MemoryIO.attach(t.pid)
|
80
|
+
# addr = o.gets.to_i(16)
|
81
|
+
# process.read(addr, 1, as: :string) # or `as: :'cpp/string'`
|
82
|
+
# #=> #<MemoryIO::Types::CPP::String @data="abcd", @capacity=15, @dataplus=0x00007ffe539ca250>
|
83
|
+
# end
|
84
|
+
def read(stream)
|
85
|
+
dataplus = read_size_t(stream)
|
86
|
+
length = read_size_t(stream)
|
87
|
+
union = stream.read(LOCAL_CAPACITY + 1)
|
88
|
+
if length > LOCAL_CAPACITY
|
89
|
+
capacity = MemoryIO::Util.unpack(union[0, Type::SIZE_T])
|
90
|
+
data = keep_pos(stream, pos: dataplus) { |s| s.read(length) }
|
91
|
+
else
|
92
|
+
capacity = LOCAL_CAPACITY
|
93
|
+
data = union[0, length]
|
94
|
+
end
|
95
|
+
new(data, capacity, dataplus)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Write a {CPP::String} object to stream.
|
99
|
+
#
|
100
|
+
# @param [#pos, #pos=, #write] stream
|
101
|
+
# @param [CPP::String] obj
|
102
|
+
#
|
103
|
+
# @return [void]
|
104
|
+
def write(stream, obj)
|
105
|
+
write_size_t(stream, obj.dataplus)
|
106
|
+
write_size_t(stream, obj.length)
|
107
|
+
pos = stream.pos
|
108
|
+
if obj.length > LOCAL_CAPACITY
|
109
|
+
keep_pos(stream, pos: obj.dataplus) { |s| s.write(obj.data + "\x00") }
|
110
|
+
write_size_t(stream, obj.capacity)
|
111
|
+
else
|
112
|
+
stream.write(obj.data + "\x00")
|
113
|
+
end
|
114
|
+
stream.pos = pos + LOCAL_CAPACITY + 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MemoryIO
|
2
|
+
module Types
|
3
|
+
# @api private
|
4
|
+
#
|
5
|
+
# Class that handles a registered object in {Types::Type.find}.
|
6
|
+
# For example, this class will parse inline-docs to generate README.md.
|
7
|
+
class Record
|
8
|
+
# @return [Object]
|
9
|
+
# Whatever.
|
10
|
+
attr_reader :obj
|
11
|
+
|
12
|
+
# @return [Array<Symbol>]
|
13
|
+
# All symbols that can find this record in {Type.find}.
|
14
|
+
attr_reader :keys
|
15
|
+
|
16
|
+
# Instantiate a {Record} object.
|
17
|
+
#
|
18
|
+
# @param [Object] object
|
19
|
+
# @param [Array<Symbol>] keys
|
20
|
+
#
|
21
|
+
# @option [Thread::Backtrace::Location] caller
|
22
|
+
# This option should present if and only if +object+ is a subclass of {Types::Type}.
|
23
|
+
# @option [String] doc
|
24
|
+
# Docstring.
|
25
|
+
# Automatically parse from caller location if this parameter isn't present.
|
26
|
+
def initialize(object, keys, option = {})
|
27
|
+
@obj = object
|
28
|
+
@keys = keys
|
29
|
+
@force_doc = option[:doc]
|
30
|
+
@caller = option[:caller]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the doc string.
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
# If option +doc+ had been passed in {#initialize}, this method simply returns it.
|
37
|
+
# Otherwise, parse the file for inline-docs.
|
38
|
+
# If neither +doc+ nor +caller+ had been passed to {#initialize}, an empty string is returned.
|
39
|
+
def doc
|
40
|
+
return @force_doc if @force_doc
|
41
|
+
return '' unless @caller
|
42
|
+
parse_file_doc(@caller.absolute_path, @caller.lineno)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
def parse_file_doc(file, lineno)
|
49
|
+
return '' unless ::File.file?(file)
|
50
|
+
strings = []
|
51
|
+
lines = ::IO.binread(file).split("\n")
|
52
|
+
(lineno - 1).downto(1) do |no|
|
53
|
+
str = lines[no - 1]
|
54
|
+
break if str.nil?
|
55
|
+
str.strip!
|
56
|
+
break unless str.start_with?('#')
|
57
|
+
strings.unshift(str[2..-1] || '')
|
58
|
+
end
|
59
|
+
trim_docstring(strings)
|
60
|
+
end
|
61
|
+
|
62
|
+
def trim_docstring(strings)
|
63
|
+
strings = strings.drop_while { |s| s.start_with?('@') }.take_while { |s| !s.start_with?('@') }
|
64
|
+
strings.drop_while(&:empty?).reverse.drop_while(&:empty?).reverse.join("\n") + "\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/memory_io/types/type.rb
CHANGED
@@ -1,35 +1,166 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
require 'memory_io/types/record'
|
4
|
+
require 'memory_io/util'
|
5
|
+
|
1
6
|
module MemoryIO
|
2
7
|
module Types
|
3
8
|
# The base class, all descendents of this class would be consider as a valid 'type'.
|
4
9
|
class Type
|
10
|
+
# The size of +size_t+. i.e. +sizeof(size_t)+.
|
11
|
+
SIZE_T = 8
|
12
|
+
|
5
13
|
class << self
|
14
|
+
# Read {Type::SIZE_T} bytes and cast to a little endian unsigned integer.
|
15
|
+
#
|
16
|
+
# @param [#read] stream
|
17
|
+
# Stream to read.
|
18
|
+
#
|
19
|
+
# @return [Integer]
|
20
|
+
# Result.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# s = StringIO.new("\xEF\xBE\xAD\xDExV4\x00")
|
24
|
+
# Type.read_size_t(s).to_s(16)
|
25
|
+
# #=> '345678deadbeef'
|
26
|
+
def read_size_t(stream)
|
27
|
+
MemoryIO::Util.unpack(stream.read(SIZE_T))
|
28
|
+
end
|
29
|
+
|
30
|
+
# Pack +val+ into {Type::SIZE_T} bytes and write to +stream+.
|
31
|
+
#
|
32
|
+
# @param [#write] stream
|
33
|
+
# Stream to write.
|
34
|
+
# @param [Integer] val
|
35
|
+
# Value to be written.
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# s = StringIO.new
|
41
|
+
# Type.write_size_t(s, 0x123)
|
42
|
+
# s.string
|
43
|
+
# #=> "\x23\x01\x00\x00\x00\x00\x00\x00"
|
44
|
+
def write_size_t(stream, val)
|
45
|
+
stream.write(MemoryIO::Util.pack(val, SIZE_T))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Yield a block and resume the position of stream.
|
49
|
+
#
|
50
|
+
# @param [#pos, #pos=] stream
|
51
|
+
# Stream.
|
52
|
+
# @param [Integer] pos
|
53
|
+
# Move +stream+'s position to +pos+ before invoke the block.
|
54
|
+
#
|
55
|
+
# @yieldparam [#pos, #pos=] stream
|
56
|
+
# Same as parameter +stream+.
|
57
|
+
# @yieldreturn [Object]
|
58
|
+
# The returned object will be returned by this method.
|
59
|
+
#
|
60
|
+
# @return [Object]
|
61
|
+
# Returns the object returned by block.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# s = StringIO.new('1234')
|
65
|
+
# Type.keep_pos(s, pos: 2) { |s| s.read(2) }
|
66
|
+
# #=> '34'
|
67
|
+
# s.pos
|
68
|
+
# #=> 0
|
69
|
+
def keep_pos(stream, pos: nil)
|
70
|
+
org = stream.pos
|
71
|
+
stream.pos = pos if pos
|
72
|
+
ret = yield stream
|
73
|
+
stream.pos = org
|
74
|
+
ret
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api private
|
78
|
+
#
|
6
79
|
# Find the subclass of {Type} by symbol.
|
7
80
|
#
|
8
81
|
# @param [Symbol] symbol
|
9
|
-
# Symbol
|
82
|
+
# Symbol that has been registered in {.register}.
|
10
83
|
#
|
11
|
-
# @return [
|
84
|
+
# @return [{Symbol => Object}]
|
12
85
|
# The object that registered in {.register}.
|
86
|
+
#
|
87
|
+
# @see .register
|
13
88
|
def find(symbol)
|
14
89
|
@map[symbol]
|
15
90
|
end
|
16
91
|
|
17
|
-
#
|
92
|
+
# Register a new type.
|
18
93
|
#
|
19
|
-
# @param [
|
20
|
-
#
|
21
|
-
# @param [#read, #write] klass
|
22
|
-
# Normally, +klass+ is a descendent of {Type}.
|
94
|
+
# @param [#read, #write] object
|
95
|
+
# Normally, +object+ is a descendent class of {Type}.
|
23
96
|
#
|
24
|
-
# @
|
25
|
-
|
26
|
-
|
27
|
-
|
97
|
+
# @option [Symbol, Array<Symbol>] alias
|
98
|
+
# Custom symbol name(s) that can be used in {.find}.
|
99
|
+
# @option [String] doc
|
100
|
+
# Doc string that will be shown in README.md.
|
101
|
+
#
|
102
|
+
# @return [Array<Symbol>]
|
103
|
+
# Array of symbols that can be used for finding the registered object.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# Type.register(MemoryIO::Types::Clang::CStr, alias: :meow)
|
107
|
+
# #=> [:'clang/c_str', :c_str, :meow]
|
108
|
+
#
|
109
|
+
# Type.register(ModuleOne::CStr, alias: :my_class)
|
110
|
+
# #=> [:'module_one/c_str', :my_class]
|
111
|
+
#
|
112
|
+
# Type.register(AnotherClass, alias: :my_class)
|
113
|
+
# # An error will be raised because the 'alias' has been registered.
|
114
|
+
#
|
115
|
+
# Type.register(AnotherClass, alias: [:my_class, my_class2])
|
116
|
+
# #=> [:another_class, :my_class2]
|
117
|
+
#
|
118
|
+
# @note
|
119
|
+
# If all symbols in +alias+ have been registered, an ArgumentError will be raised.
|
120
|
+
# However, if at least one of aliases hasn't been used, registration will success.
|
121
|
+
#
|
122
|
+
# @see .find
|
123
|
+
def register(object, option = {})
|
124
|
+
@map ||= OpenStruct.new
|
125
|
+
aliases = Array(option[:alias])
|
126
|
+
reg_fail = ArgumentError.new(<<-EOS.strip)
|
127
|
+
Register '#{object.inspect}' fails because another object with same name has been registered.
|
128
|
+
Specify an alias such as `register(MyClass, alias: :custom_alias_name)`.
|
129
|
+
EOS
|
130
|
+
raise reg_fail if aliases.any? && aliases.all? { |ali| @map[ali] }
|
131
|
+
keys = get_keys(object).concat(aliases).uniq.reject { |k| @map[k] }
|
132
|
+
raise reg_fail if keys.empty?
|
133
|
+
rec = MemoryIO::Types::Record.new(object, keys, option)
|
134
|
+
keys.each { |k| @map[k] = rec }
|
28
135
|
end
|
29
136
|
|
30
|
-
#
|
137
|
+
# @abstract
|
138
|
+
def read(_stream) raise NotImplementedError
|
139
|
+
end
|
140
|
+
|
141
|
+
# @abstract
|
142
|
+
def write(_stream, _obj) raise NotImplementedError
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# @api private
|
148
|
+
#
|
149
|
+
# To record descendants.
|
31
150
|
def inherited(klass)
|
32
|
-
register(
|
151
|
+
register(klass, caller: caller_locations(1, 1).first)
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param [Class] klass
|
155
|
+
#
|
156
|
+
# @return [Array<Symbol>]
|
157
|
+
def get_keys(klass)
|
158
|
+
return [] unless klass.instance_of?(Class)
|
159
|
+
snake = MemoryIO::Util.underscore(klass.name)
|
160
|
+
snake.gsub!(%r[^memory_io/types/], '')
|
161
|
+
ret = [snake]
|
162
|
+
ret << ::File.basename(snake)
|
163
|
+
ret.map(&:to_sym).uniq
|
33
164
|
end
|
34
165
|
end
|
35
166
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'memory_io/types/type'
|
2
2
|
require 'memory_io/util'
|
3
3
|
|
4
|
-
|
5
|
-
require 'memory_io/types/number'
|
4
|
+
Dir[File.join(__dir__, '**', '*.rb')].each { |f| require f }
|
6
5
|
|
7
6
|
module MemoryIO
|
8
7
|
# Module that includes multiple types.
|
@@ -15,24 +14,28 @@ module MemoryIO
|
|
15
14
|
#
|
16
15
|
# Returns the class whose name matches +name+.
|
17
16
|
#
|
18
|
-
# This method
|
17
|
+
# This method will search all descendants of {Types::Type}.
|
19
18
|
#
|
20
19
|
# @return [Symbol] name
|
21
20
|
# Class name to be searched.
|
22
21
|
#
|
23
|
-
# @return [
|
24
|
-
#
|
22
|
+
# @return [#read, #write]
|
23
|
+
# Any object that implemented method +read+ and +write+.
|
24
|
+
# Usually returns a class inherit {Types::Type}.
|
25
25
|
#
|
26
26
|
# @example
|
27
|
-
# Types.find(:u64)
|
28
|
-
# #=> MemoryIO::Types::U64
|
29
|
-
#
|
30
27
|
# Types.find(:c_str)
|
31
28
|
# #=> MemoryIO::Types::CStr
|
29
|
+
#
|
30
|
+
# Types.find(:u64)
|
31
|
+
# #=> #<MemoryIO::Types::Number:0x000055ecc017a310 @bytes=8, @pack_str="Q", @signed=false>
|
32
32
|
def find(name)
|
33
|
-
Types::Type.find(
|
33
|
+
obj = Types::Type.find(name)
|
34
|
+
return obj.obj if obj
|
34
35
|
end
|
35
36
|
|
37
|
+
# @api private
|
38
|
+
#
|
36
39
|
# Returns a callable object according to +name+.
|
37
40
|
#
|
38
41
|
# @param [Symbol] name
|
data/lib/memory_io/util.rb
CHANGED
@@ -8,7 +8,7 @@ module MemoryIO
|
|
8
8
|
|
9
9
|
# Convert input into snake-case.
|
10
10
|
#
|
11
|
-
# This method also
|
11
|
+
# This method also converts +'::'+ to +'/'+.
|
12
12
|
#
|
13
13
|
# @param [String] str
|
14
14
|
# String to be converted.
|
@@ -21,10 +21,10 @@ module MemoryIO
|
|
21
21
|
# #=> 'memory_io'
|
22
22
|
#
|
23
23
|
# Util.underscore('MyModule::MyClass')
|
24
|
-
# #=> 'my_class'
|
24
|
+
# #=> 'my_module/my_class'
|
25
25
|
def underscore(str)
|
26
26
|
return '' if str.empty?
|
27
|
-
str = str.
|
27
|
+
str = str.gsub('::', '/')
|
28
28
|
str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
29
29
|
str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
30
30
|
str.downcase!
|
@@ -77,6 +77,44 @@ module MemoryIO
|
|
77
77
|
Dentaku::Calculator.new.store(vars).evaluate(str)
|
78
78
|
end
|
79
79
|
|
80
|
+
# Unpack a string into an integer.
|
81
|
+
# Little endian is used.
|
82
|
+
#
|
83
|
+
# @param [String] str
|
84
|
+
# String.
|
85
|
+
#
|
86
|
+
# @return [Integer]
|
87
|
+
# Result.
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# Util.unpack("\xff")
|
91
|
+
# #=> 255
|
92
|
+
# Util.unpack("@\xE2\x01\x00")
|
93
|
+
# #=> 123456
|
94
|
+
def unpack(str)
|
95
|
+
str.bytes.reverse.reduce(0) { |s, c| s * 256 + c }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Pack an integer into +b+ bytes.
|
99
|
+
# Little endian is used.
|
100
|
+
#
|
101
|
+
# @param [Integer] val
|
102
|
+
# The integer to pack.
|
103
|
+
# If +val+ contains more than +b+ bytes,
|
104
|
+
# only lower +b+ bytes in +val+ will be packed.
|
105
|
+
#
|
106
|
+
# @param [Integer] b
|
107
|
+
#
|
108
|
+
# @return [String]
|
109
|
+
# Packing result with length +b+.
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# Util.pack(0x123, 4)
|
113
|
+
# #=> "\x23\x01\x00\x00"
|
114
|
+
def pack(val, b)
|
115
|
+
Array.new(b) { |i| (val >> (i * 8)) & 0xff }.pack('C*')
|
116
|
+
end
|
117
|
+
|
80
118
|
# Remove extension name (.so) and version in library name.
|
81
119
|
#
|
82
120
|
# @param [String] name
|
data/lib/memory_io/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memory_io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- david942j
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dentaku
|
@@ -125,8 +125,10 @@ files:
|
|
125
125
|
- lib/memory_io.rb
|
126
126
|
- lib/memory_io/io.rb
|
127
127
|
- lib/memory_io/process.rb
|
128
|
-
- lib/memory_io/types/
|
129
|
-
- lib/memory_io/types/
|
128
|
+
- lib/memory_io/types/basic/number.rb
|
129
|
+
- lib/memory_io/types/clang/c_str.rb
|
130
|
+
- lib/memory_io/types/cpp/string.rb
|
131
|
+
- lib/memory_io/types/record.rb
|
130
132
|
- lib/memory_io/types/type.rb
|
131
133
|
- lib/memory_io/types/types.rb
|
132
134
|
- lib/memory_io/util.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
require 'memory_io/types/type'
|
4
|
-
|
5
|
-
module MemoryIO
|
6
|
-
module Types
|
7
|
-
# Read a null-terminated string.
|
8
|
-
class CStr < Types::Type
|
9
|
-
# @api private
|
10
|
-
#
|
11
|
-
# @return [String]
|
12
|
-
# String without null byte.
|
13
|
-
def self.read(stream)
|
14
|
-
ret = ''
|
15
|
-
loop do
|
16
|
-
c = stream.read(1)
|
17
|
-
break if c.nil? || c == '' || c == "\x00"
|
18
|
-
ret << c
|
19
|
-
end
|
20
|
-
ret
|
21
|
-
end
|
22
|
-
|
23
|
-
# @api private
|
24
|
-
#
|
25
|
-
# @param [String] val
|
26
|
-
# A null byte would be appended if +val+ not ends with null byte.
|
27
|
-
def self.write(stream, val)
|
28
|
-
val = val.to_s
|
29
|
-
val << "\x00" unless val.end_with?("\x00")
|
30
|
-
stream.write(val)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'memory_io/types/type'
|
2
|
-
|
3
|
-
module MemoryIO
|
4
|
-
module Types
|
5
|
-
# @api private
|
6
|
-
# Register numbers to {Types}.
|
7
|
-
#
|
8
|
-
# All types registerd by this class are assumed as *little endian*.
|
9
|
-
class Number
|
10
|
-
# @param [Integer] bytes
|
11
|
-
# Bytes.
|
12
|
-
# @param [Boolean] signed
|
13
|
-
# Signed or unsigned.
|
14
|
-
# @param [String] pack_str
|
15
|
-
# The indicator to be passed to +Array#pack+ and +String#unpack+.
|
16
|
-
def initialize(bytes, signed, pack_str)
|
17
|
-
@bytes = bytes
|
18
|
-
@signed = signed
|
19
|
-
@pack_str = pack_str
|
20
|
-
end
|
21
|
-
|
22
|
-
# @return [Integer]
|
23
|
-
def read(stream)
|
24
|
-
unpack(stream.read(@bytes))
|
25
|
-
end
|
26
|
-
|
27
|
-
# @param [Integer] val
|
28
|
-
def write(stream, val)
|
29
|
-
stream.write(pack(val))
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def unpack(str)
|
35
|
-
val = str.unpack(@pack_str).first
|
36
|
-
val -= (2**@bytes) if @signed && val >= (2**(@bytes - 1))
|
37
|
-
val
|
38
|
-
end
|
39
|
-
|
40
|
-
def pack(val)
|
41
|
-
[val].pack(@pack_str)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Register (un)signed n-bites integers.
|
45
|
-
{
|
46
|
-
8 => 'C',
|
47
|
-
16 => 'S',
|
48
|
-
32 => 'I',
|
49
|
-
64 => 'Q'
|
50
|
-
}.each do |t, c|
|
51
|
-
Type.register("s#{t}".to_sym, Number.new(t, true, c))
|
52
|
-
Type.register("u#{t}".to_sym, Number.new(t, false, c))
|
53
|
-
end
|
54
|
-
|
55
|
-
# Register floating numbers.
|
56
|
-
Type.register(:float, Number.new(4, false, 'F'))
|
57
|
-
Type.register(:double, Number.new(8, false, 'D'))
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|