memory_io 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Inline docs](https://inch-ci.org/github/david942j/memory_io.svg?branch=master)](https://inch-ci.org/github/david942j/memory_io)
|
6
6
|
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](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
|