memory_io 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 23fd90a4ba0906c5264a4149f8992c1ca22ed251
4
+ data.tar.gz: d6414406dac7f68362f6d1aafb0d31966f8e21b0
5
+ SHA512:
6
+ metadata.gz: cc05ccbf5888b182138ff6112f8979e48875a8f3ab68730bf72e3cb5391e9205c32d7494e2d792196eda735eeac7e851e45fadcd54cdf51b9624d779b85e6492
7
+ data.tar.gz: b7ee99af0626e2486f8fab0da2b3c2bef47d877396a796118fc8ae8b4bfa456df609b0703d4da400b5a147e66a041ae557d69df2212b181f9d073353cf0e4bf6
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ [![Build Status](https://travis-ci.org/david942j/memory_io.svg?branch=master)](https://travis-ci.org/david942j/memory_io)
2
+ [![Gem Version](https://badge.fury.io/rb/memory_io.svg)](https://badge.fury.io/rb/memory_io)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/dc8da34c5a8ab0095530/maintainability)](https://codeclimate.com/github/david942j/memory_io/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/dc8da34c5a8ab0095530/test_coverage)](https://codeclimate.com/github/david942j/memory_io/test_coverage)
5
+ [![Inline docs](https://inch-ci.org/github/david942j/memory_io.svg?branch=master)](https://inch-ci.org/github/david942j/memory_io)
6
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
7
+
8
+ # Memory IO
9
+
10
+ Read/Write complicated structures in memory easily.
11
+
12
+ ## Installation
13
+
14
+ Available on RubyGems.org!
15
+
16
+ ```bash
17
+ $ gem install memory_io
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Read Process's Memory
23
+ ```
24
+ require 'memory_io'
25
+
26
+ process = MemoryIO.attach(`pidof victim`.to_i)
27
+ puts process.read('heap', 4, as: :u64).map { |c| '0x%016x' % c }
28
+ # 0x0000000000000000
29
+ # 0x0000000000000021
30
+ # 0x00000000deadbeef
31
+ # 0x0000000000000000
32
+ #=> nil
33
+
34
+ process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
35
+ #=> ['0xef', '0xbe', '0xad', '0xde']
36
+
37
+ process.read('libc', 4)
38
+ #=> "\x7fELF"
39
+ ```
@@ -0,0 +1,126 @@
1
+ require 'memory_io/types/types'
2
+
3
+ module MemoryIO
4
+ # Main class to use {MemoryIO}.
5
+ class IO
6
+ attr_reader :stream # @return [#pos=, #read, #write]
7
+
8
+ # Instantiate an {IO} object.
9
+ #
10
+ # @param [#pos=, #read, #write] stream
11
+ # The file-like object to be read/written.
12
+ # +file+ can be unwritable if you will not invoke any write-related method.
13
+ #
14
+ # If +stream.read(*)+ returns empty string or +nil+, it would be seen as reaching EOF.
15
+ def initialize(stream)
16
+ @stream = stream
17
+ end
18
+
19
+ # Read and convert result into custom type/stucture.
20
+ #
21
+ # @param [Integer] num_elements
22
+ # Number of elements to be read.
23
+ # This parameter must be positive and larger than zero.
24
+ #
25
+ # This parameter may effect the return type,
26
+ # see documents of return value.
27
+ # @param [Integer?] from
28
+ # Invoke +stream.pos = from+ before starting to read.
29
+ # +nil+ for not changing current position of stream.
30
+ # @param [nil, Symbol, MemoryIO::Types, Proc] as
31
+ # Decide the type/structure when reading.
32
+ # See {MemoryIO::Types} for all supported types.
33
+ #
34
+ # A +Proc+ is allowed, which should accept +stream+ as the first argument.
35
+ # The return value of the proc would be the return objects of this method.
36
+ #
37
+ # If +nil+ is given, this method returns a string and has same behavior as +::IO#read+.
38
+ # @param [Boolean] force_array
39
+ # When +num_elements+ equals to 1, the read +Object+ would be returned.
40
+ # Pass +true+ to this parameter to force this method returning an array.
41
+ #
42
+ # @return [String, Object, Array<Object>]
43
+ # There're multiple possible return types,
44
+ # which dependes on the value of parameter +num_elements+, +as+, and +force_array+.
45
+ #
46
+ # See examples for clear usage. The rule of return type is listed as following:
47
+ #
48
+ # * +as = nil+:
49
+ # A +String+ with length +num_elements+ is returned.
50
+ # * +as != nil+ and +num_elements = 1+ and +force_array = false+:
51
+ # An +Object+ is returned. The type of +Object+ depends on parameter +as+.
52
+ # * +as != nil+ and +num_elements = 1+ and +force_array = true+:
53
+ # An array with one element is returned.
54
+ # * +as != nil+ and +num_elements > 1+:
55
+ # An array with length +num_elements+ is returned.
56
+ #
57
+ # If EOF is occured, object(s) read will be returned.
58
+ #
59
+ # @example
60
+ # stream = StringIO.new('A' * 8 + 'B' * 8)
61
+ # io = MemoryIO.new(stream)
62
+ # io.read(9)
63
+ # #=> "AAAAAAAAB"
64
+ # io.read(100)
65
+ # #=> "BBBBBBB"
66
+ #
67
+ # # read two unsigned 32-bit integers starts from posistion 4
68
+ # io.read(2, from: 4, as: :u32)
69
+ # #=> [1094795585, 1111638594] # [0x41414141, 0x42424242]
70
+ #
71
+ # io.read(1, as: :u16)
72
+ # #=> 16962 # 0x4242
73
+ # io.read(1, as: :u16, force_array: true)
74
+ # #=> [16962]
75
+ # @example
76
+ # stream = StringIO.new("\xef\xbe\xad\xde")
77
+ # io = MemoryIO.new(stream)
78
+ # io.read(1, as: :u32)
79
+ # #=> 3735928559
80
+ # io.rewind
81
+ # io.read(1, as: :s32)
82
+ # #=> -559038737
83
+ # @example
84
+ # stream = StringIO.new("123\x0045678\x00")
85
+ # io = MemoryIO.new(stream)
86
+ # io.read(2, as: :c_str)
87
+ # #=> ["123", "45678"]
88
+ # @example
89
+ # # pass lambda to `as`
90
+ # stream = StringIO.new("\x03123\x044567")
91
+ # io = MemoryIO.new(stream)
92
+ # io.read(2, as: lambda { |stream| stream.read(stream.read(1).ord) })
93
+ # #=> ["123", "4567"]
94
+ #
95
+ # @note
96
+ # This method's arguments and return value are different with +::IO#read+.
97
+ # Check documents and examples.
98
+ def read(num_elements, from: nil, as: nil, force_array: false)
99
+ stream.pos = from if from
100
+ return stream.read(num_elements) if as.nil?
101
+ conv = to_proc(as, :read)
102
+ # TODO: handle eof
103
+ ret = Array.new(num_elements) { conv.call(stream) }
104
+ ret = ret.first if num_elements == 1 && !force_array
105
+ ret
106
+ end
107
+
108
+ # Set +stream+ to the beginning.
109
+ # i.e. invoke +stream.pos = 0+.
110
+ #
111
+ # @return [0]
112
+ def rewind
113
+ stream.pos = 0
114
+ end
115
+
116
+ private
117
+
118
+ def to_proc(as, rw)
119
+ ret = as.respond_to?(:call) ? as : MemoryIO::Types.get_proc(as, rw)
120
+ raise ArgumentError, <<-EOERR.strip unless ret.respond_to?(:call)
121
+ Invalid argument `as`: #{as.inspect}. It should be either a Proc or a supported type of MemoryIO::Types.
122
+ EOERR
123
+ ret
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,113 @@
1
+ module MemoryIO
2
+ # Records information of a process.
3
+ class Process
4
+ # @api private
5
+ # @return [#readable?, #writable?]
6
+ attr_reader :perm
7
+
8
+ # @api private
9
+ #
10
+ # Create process object from pid.
11
+ #
12
+ # @param [Integer] pid
13
+ # Process id.
14
+ #
15
+ # @note
16
+ # This class only supports procfs-based system. i.e. /proc is mounted and readable.
17
+ #
18
+ # @todo
19
+ # Support MacOS
20
+ # @todo
21
+ # Support Windows
22
+ def initialize(pid)
23
+ @pid = pid
24
+ @mem = "/proc/#{pid}/mem"
25
+ # check permission of '/proc/pid/mem'
26
+ @perm = MemoryIO::Util.file_permission(@mem)
27
+ # TODO: raise custom exception
28
+ raise Errno::ENOENT, @mem if perm.nil?
29
+ # FIXME: use logger
30
+ warn(<<-EOS.strip) unless perm.readable? || perm.writable?
31
+ You have no permission to read/write this process.
32
+
33
+ Check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
34
+ again as the root user.
35
+
36
+ To enable attach another process, do:
37
+
38
+ $ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
39
+ EOS
40
+ end
41
+
42
+ # Parse +/proc/[pid]/maps+ to get all bases.
43
+ #
44
+ # @return [{Symbol => Integer}]
45
+ # Hash of bases.
46
+ #
47
+ # @example
48
+ # process = Process.new(`pidof victim`.to_i)
49
+ # puts process.bases.map { |key, val| format('%s: 0x%016x', key, val) }
50
+ # # vsyscall: 0xffffffffff600000
51
+ # # vdso: 0x00007ffd5b565000
52
+ # # vvar: 0x00007ffd5b563000
53
+ # # stack: 0x00007ffd5ad21000
54
+ # # ld: 0x00007f339a69b000
55
+ # # libc: 0x00007f33996f1000
56
+ # # heap: 0x00005571994a1000
57
+ # # victim: 0x0000557198bcb000
58
+ # #=> nil
59
+ def bases
60
+ file = "/proc/#{@pid}/maps"
61
+ stat = MemoryIO::Util.file_permission(file)
62
+ return {} unless stat && stat.readable?
63
+ maps = ::IO.binread(file).split("\n").map do |line|
64
+ # 7f76515cf000-7f76515da000 r-xp 00000000 fd:01 29360257 /lib/x86_64-linux-gnu/libnss_files-2.24.so
65
+ addr, _perm, _offset, _dev, _inode, pathname = line.strip.split(' ', 6)
66
+ next nil if pathname.nil?
67
+ addr = addr.to_i(16)
68
+ pathname = pathname[1..-2] if pathname =~ /^\[.+\]$/
69
+ pathname = ::File.basename(pathname)
70
+ [MemoryIO::Util.trim_libname(pathname).to_sym, addr]
71
+ end
72
+ maps.compact.reverse.to_h
73
+ end
74
+
75
+ # Read from process's memory.
76
+ #
77
+ # This method has *almost* same arguements and return types as {IO#read}.
78
+ # The only difference is this method needs parameter +addr+ (which
79
+ # will be passed to paramter +from+ in {IO#reada}).
80
+ #
81
+ # @param [Integer, String] addr
82
+ # The address start to read.
83
+ # When String is given, it will be safe-evaluated.
84
+ # You can use variables such as +'heap'/'stack'/'libc'+ in this parameter.
85
+ # See examples.
86
+ # @param [Integer] num_elements
87
+ # Number of elements to read. See {IO#read}.
88
+ #
89
+ # @return [String, Object, Array<Object>]
90
+ # See {IO#read}.
91
+ #
92
+ # @example
93
+ # process = MemoryIO.attach(`pidof victim`.to_i)
94
+ # puts process.read('heap', 4, as: :u64).map { |c| '0x%016x' % c }
95
+ # # 0x0000000000000000
96
+ # # 0x0000000000000021
97
+ # # 0x00000000deadbeef
98
+ # # 0x0000000000000000
99
+ # #=> nil
100
+ # process.read('heap+0x10', 4, as: :u8).map { |c| '0x%x' % c }
101
+ # #=> ['0xef', '0xbe', '0xad', '0xde']
102
+ #
103
+ # process.read('libc', 4)
104
+ # #=> "\x7fELF"
105
+ # @see IO#read
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
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,60 @@
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
@@ -0,0 +1,37 @@
1
+ module MemoryIO
2
+ module Types
3
+ # The base class, all descendents of this class would be consider as a valid 'type'.
4
+ class Type
5
+ class << self
6
+ # Find the subclass of {Type} by symbol.
7
+ #
8
+ # @param [Symbol] symbol
9
+ # Symbol to be find.
10
+ #
11
+ # @return [#read, #write]
12
+ # The object that registered in {.register}.
13
+ def find(symbol)
14
+ @map[symbol]
15
+ end
16
+
17
+ # @api private
18
+ #
19
+ # @param [Symbol] symbol
20
+ # Symbol name that used for searching.
21
+ # @param [#read, #write] klass
22
+ # Normally, +klass+ is a descendent of {Type}.
23
+ #
24
+ # @return [void]
25
+ def register(symbol, klass)
26
+ @map ||= {}
27
+ @map[symbol] = klass
28
+ end
29
+
30
+ # TO record descendants.
31
+ def inherited(klass)
32
+ register(MemoryIO::Util.underscore(klass.name).to_sym, klass)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,56 @@
1
+ require 'memory_io/types/type'
2
+ require 'memory_io/util'
3
+
4
+ require 'memory_io/types/c_str'
5
+ require 'memory_io/types/number'
6
+
7
+ module MemoryIO
8
+ # Module that includes multiple types.
9
+ #
10
+ # Supported types are all descendants of {Types::Type}.
11
+ module Types
12
+ module_function
13
+
14
+ # @api private
15
+ #
16
+ # Returns the class whose name matches +name+.
17
+ #
18
+ # This method would search all descendants of {Types::Type}.
19
+ #
20
+ # @return [Symbol] name
21
+ # Class name to be searched.
22
+ #
23
+ # @return [Class?]
24
+ # The class.
25
+ #
26
+ # @example
27
+ # Types.find(:u64)
28
+ # #=> MemoryIO::Types::U64
29
+ #
30
+ # Types.find(:c_str)
31
+ # #=> MemoryIO::Types::CStr
32
+ def find(name)
33
+ Types::Type.find(Util.underscore(name.to_s).to_sym)
34
+ end
35
+
36
+ # Returns a callable object according to +name+.
37
+ #
38
+ # @param [Symbol] name
39
+ # The name of type.
40
+ # @param [:read, :write] rw
41
+ # Read or write?
42
+ #
43
+ # @return [Proc?]
44
+ # The proc that accepts +stream+ as the first parameter.
45
+ #
46
+ # @example
47
+ # Types.get_proc(:c_str, :write)
48
+ # #=> #<Method: MemoryIO::Types::CStr.write>
49
+ # Types.get_proc(:s32, :read)
50
+ # #=> #<Method: MemoryIO::Types::Number#read>
51
+ def get_proc(name, rw)
52
+ klass = find(name)
53
+ klass && klass.method(rw)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
1
+ require 'ostruct'
2
+ require 'dentaku'
3
+
4
+ module MemoryIO
5
+ # Defines utility methods.
6
+ module Util
7
+ module_function
8
+
9
+ # Convert input into snake-case.
10
+ #
11
+ # This method also removes strings before +'::'+ in +str+.
12
+ #
13
+ # @param [String] str
14
+ # String to be converted.
15
+ #
16
+ # @return [String]
17
+ # Converted string.
18
+ #
19
+ # @example
20
+ # Util.underscore('MemoryIO')
21
+ # #=> 'memory_io'
22
+ #
23
+ # Util.underscore('MyModule::MyClass')
24
+ # #=> 'my_class'
25
+ def underscore(str)
26
+ return '' if str.empty?
27
+ str = str.split('::').last
28
+ str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
29
+ str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
30
+ str.downcase!
31
+ str
32
+ end
33
+
34
+ # @api private
35
+ #
36
+ # @param [String] file
37
+ # File name.
38
+ #
39
+ # @return [#readable?, #writable?, nil]
40
+ # Struct with two boolean method.
41
+ # +nil+ for file not exists or is inaccessible.
42
+ def file_permission(file)
43
+ return nil unless File.file?(file)
44
+ stat = File.stat(file)
45
+ # we do a trick here because /proc/[pid]/mem might be marked as readable but fails at sysopen.
46
+ os = OpenStruct.new(readable?: stat.readable_real?, writable?: stat.writable_real?)
47
+ begin
48
+ os.readable? && File.open(file, 'rb').close
49
+ rescue Errno::EACCES
50
+ os[:readable?] = false
51
+ end
52
+ begin
53
+ os.writable? && File.open(file, 'wb').close
54
+ rescue Errno::EACCES
55
+ os[:writable?] = false
56
+ end
57
+ os
58
+ end
59
+
60
+ # Evaluate string safely.
61
+ #
62
+ # @param [String] str
63
+ # String to be evaluated.
64
+ # @param [{Symbol => Integer}] vars
65
+ # Predefined variables
66
+ #
67
+ # @return [Integer]
68
+ # Result.
69
+ #
70
+ # @example
71
+ # Util.safe_eval('heap + 0x10 * pp', heap: 0xde00, pp: 8)
72
+ # #=> 56960 # 0xde80
73
+ def safe_eval(str, **vars)
74
+ return str if str.is_a?(Integer)
75
+ # dentaku 2 doesn't support hex
76
+ str = str.gsub(/0x[0-9a-zA-Z]+/) { |c| c.to_i(16) }
77
+ Dentaku::Calculator.new.store(vars).evaluate(str)
78
+ end
79
+
80
+ # Remove extension name (.so) and version in library name.
81
+ #
82
+ # @param [String] name
83
+ # Original library filename.
84
+ #
85
+ # @return [String]
86
+ # Name without version and '.so'.
87
+ #
88
+ # @example
89
+ # Util.trim_libname('libc-2.24.so')
90
+ # #=> 'libc'
91
+ # Util.trim_libname('libcrypto.so.1.0.0')
92
+ # #=> 'libcrypto'
93
+ # Util.trim_libname('not_a_so')
94
+ # #=> 'not_a_so'
95
+ def trim_libname(name)
96
+ type1 = '(-[\d.]+)?\.so$'
97
+ type2 = '\.so.\d+[\d.]+$'
98
+ name.sub(/#{type1}|#{type2}/, '')
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ module MemoryIO
2
+ # Current gem version.
3
+ VERSION = '0.0.0'.freeze
4
+ end
data/lib/memory_io.rb ADDED
@@ -0,0 +1,21 @@
1
+ # MemoryIO - Read/Write structures in memory.
2
+ #
3
+ # @author david942j
4
+ module MemoryIO
5
+ module_function
6
+
7
+ # Get a process by process id.
8
+ #
9
+ # @param [Integer] pid
10
+ # Process Id in linux.
11
+ #
12
+ # @return [MemoryIO::Process]
13
+ # A process object for further usage.
14
+ def attach(pid)
15
+ MemoryIO::Process.new(pid)
16
+ end
17
+ end
18
+
19
+ require 'memory_io/io'
20
+ require 'memory_io/process'
21
+ require 'memory_io/version'
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memory_io
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - david942j
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dentaku
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.11
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 2.0.11
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: codeclimate-test-reporter
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.6'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.6'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '12.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '12.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.5'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.5'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rubocop
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.52'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.52'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.13.0
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.13.0
103
+ - !ruby/object:Gem::Dependency
104
+ name: yard
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.9'
117
+ description: ''
118
+ email:
119
+ - david942j@gmail.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - README.md
125
+ - lib/memory_io.rb
126
+ - lib/memory_io/io.rb
127
+ - lib/memory_io/process.rb
128
+ - lib/memory_io/types/c_str.rb
129
+ - lib/memory_io/types/number.rb
130
+ - lib/memory_io/types/type.rb
131
+ - lib/memory_io/types/types.rb
132
+ - lib/memory_io/util.rb
133
+ - lib/memory_io/version.rb
134
+ homepage: https://github.com/david942j/memory_io
135
+ licenses:
136
+ - MIT
137
+ metadata: {}
138
+ post_install_message:
139
+ rdoc_options: []
140
+ require_paths:
141
+ - lib
142
+ required_ruby_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: 2.1.0
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 2.6.14
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: memory_io
158
+ test_files: []