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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 23fd90a4ba0906c5264a4149f8992c1ca22ed251
4
- data.tar.gz: d6414406dac7f68362f6d1aafb0d31966f8e21b0
3
+ metadata.gz: 03e970969f587b0c3baa79df39eb2599c63ab845
4
+ data.tar.gz: cd31136f30b5a11859bd84f8b1218cab749cabb1
5
5
  SHA512:
6
- metadata.gz: cc05ccbf5888b182138ff6112f8979e48875a8f3ab68730bf72e3cb5391e9205c32d7494e2d792196eda735eeac7e851e45fadcd54cdf51b9624d779b85e6492
7
- data.tar.gz: b7ee99af0626e2486f8fab0da2b3c2bef47d877396a796118fc8ae8b4bfa456df609b0703d4da400b5a147e66a041ae557d69df2212b181f9d073353cf0e4bf6
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
- # Memory IO
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, MemoryIO::Types, Proc] as
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?(:call) ? as : MemoryIO::Types.get_proc(as, rw)
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
@@ -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#reada}).
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
- addr = MemoryIO::Util.safe_eval(addr, bases)
108
- File.open(@mem, 'rb') do |f|
109
- MemoryIO::IO.new(f).read(num_elements, from: addr, **options)
110
- end
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
@@ -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 to be find.
82
+ # Symbol that has been registered in {.register}.
10
83
  #
11
- # @return [#read, #write]
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
- # @api private
92
+ # Register a new type.
18
93
  #
19
- # @param [Symbol] symbol
20
- # Symbol name that used for searching.
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
- # @return [void]
25
- def register(symbol, klass)
26
- @map ||= {}
27
- @map[symbol] = klass
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
- # TO record descendants.
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(MemoryIO::Util.underscore(klass.name).to_sym, klass)
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
- require 'memory_io/types/c_str'
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 would search all descendants of {Types::Type}.
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 [Class?]
24
- # The class.
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(Util.underscore(name.to_s).to_sym)
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
@@ -8,7 +8,7 @@ module MemoryIO
8
8
 
9
9
  # Convert input into snake-case.
10
10
  #
11
- # This method also removes strings before +'::'+ in +str+.
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.split('::').last
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
@@ -1,4 +1,4 @@
1
1
  module MemoryIO
2
2
  # Current gem version.
3
- VERSION = '0.0.0'.freeze
3
+ VERSION = '0.1.0'.freeze
4
4
  end
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.0.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-02 00:00:00.000000000 Z
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/c_str.rb
129
- - lib/memory_io/types/number.rb
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