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 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