gdb 0.1.0 → 0.2.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: 3f32294dfb015b22fbc1fbdfa8a8babb257cd336
4
- data.tar.gz: d8ea3ac19cad4184677fbb55831443b51acccac8
3
+ metadata.gz: f9b16e3cec333997d16a2400dacc343110aec6a4
4
+ data.tar.gz: dc486d72d111cfaeac7928402ee1e39a57552bd1
5
5
  SHA512:
6
- metadata.gz: 582e43a9d160827b8787124ffb48d2e7658ce2bd1f152dfd07227a4cc84eda9ef6cbad2c367ce90669e4b886ab71863b771c902035ceb912c525a3f0f1e9452b
7
- data.tar.gz: '034946046228f22c23838f7ef4b5b69a90dc198cc946335eaf29e1e7ff380d2a0567167947c7dee0cc07e2b9517b687dcb379de309685b2981a737165cb6c0f8'
6
+ metadata.gz: 3e8c780fe48de70df13df78fb9f987afab66d268f6f4b27b85665279f4fed43abdc512806a7b7467e77006f5e8c95f2069532fa717134503568c3f65842b9dbb
7
+ data.tar.gz: 92dda6f5248627108645bc55c9e3d5a3f7d476bfa2cc3295e4edc7a53e2815ca9f488712109422608fca88fdb29c9f0edec7ad8f8b88647fbf85b5007e277770
data/README.md CHANGED
@@ -1,2 +1,161 @@
1
- # gdb-ruby
2
- Use gdb in ruby!
1
+ [![Build Status](https://travis-ci.org/david942j/gdb-ruby.svg?branch=master)](https://travis-ci.org/david942j/gdb-ruby)
2
+ [![Code Climate](https://codeclimate.com/github/david942j/gdb-ruby/badges/gpa.svg)](https://codeclimate.com/github/david942j/gdb-ruby)
3
+ [![Issue Count](https://codeclimate.com/github/david942j/gdb-ruby/badges/issue_count.svg)](https://codeclimate.com/github/david942j/gdb-ruby)
4
+ [![Test Coverage](https://codeclimate.com/github/david942j/gdb-ruby/badges/coverage.svg)](https://codeclimate.com/github/david942j/gdb-ruby/coverage)
5
+ [![Inline docs](https://inch-ci.org/github/david942j/gdb-ruby.svg?branch=master)](https://inch-ci.org/github/david942j/gdb-ruby)
6
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
7
+
8
+ # GDB-Ruby
9
+
10
+ It's time for Ruby lovers to use Ruby in gdb, and... gdb in Ruby!
11
+
12
+ # Use Ruby in gdb
13
+
14
+ We provide a binary `gdb-ruby` with usage exactly same as normal gdb,
15
+ while has two extra commands: `ruby` and `pry`!
16
+
17
+ See examples below:
18
+
19
+ ```yaml
20
+ $ gdb-ruby -q bash
21
+ Reading symbols from bash...(no debugging symbols found)...done.
22
+ (gdb) help ruby
23
+ Evaluate a Ruby command.
24
+ There's an instance 'gdb' for you. See examples.
25
+
26
+ Syntax: ruby <ruby code>
27
+
28
+ Examples:
29
+ ruby p 'abcd'
30
+ # "abcd"
31
+
32
+ Use gdb:
33
+ ruby puts gdb.break('main')
34
+ # Breakpoint 1 at 0x41eed0
35
+
36
+ Method defined will remain in context:
37
+ ruby def a(b); b * b; end
38
+ ruby p a(9)
39
+ # 81
40
+ (gdb) help pry
41
+ Enter Ruby interactive shell.
42
+ Everything works like a charm!
43
+
44
+ Syntax: pry
45
+
46
+ Example:
47
+ pry
48
+ # [1] pry(#<GDB::EvalContext>)>
49
+ ```
50
+
51
+ ## Integrate with other gdb extensions
52
+
53
+ Completely NO effort if you want to use **gdb-ruby** with other gdb extension.
54
+
55
+ For example, I usually use [gef](https://github.com/hugsy/gef) for my gdb.
56
+ And everything works as usual when integrated with **gdb-ruby**:
57
+
58
+ Launching with `$ gdb-ruby -q bash`
59
+
60
+ ![ruby-in-gef](https://i.imgur.com/WMxofSs.png)
61
+
62
+ # Use gdb in Ruby
63
+
64
+ Communicate with gdb in your Ruby script.
65
+
66
+ ## Useful methods
67
+
68
+ Basical usage is use `execute` to do anything you want to execute inside gdb,
69
+ while **gdb-ruby** provides some useful methods listed as following:
70
+
71
+ * `break`: Set break points. Alias: `b`
72
+ * `run`: Run. Alias: `r`
73
+ * `register`: Get value by register's name. Alias: `reg`
74
+ * `text_base`: Get current running program's text base, useful for a PIE binary.
75
+ * `pid`: Get the process id of running process.
76
+ * `read_memory`: Read process's memory, with friendly type casting. Alias: `readm`
77
+ * `write_memory`: Write process's memory, useful for dynamic analysis. Alias: `writem`
78
+ * `interact`: Back to normal gdb interactive mode.
79
+
80
+ All of these methods are fully documented at [online doc](http://www.rubydoc.info/github/david942j/gdb-ruby/master/GDB/GDB), go for it!
81
+
82
+ ## Examples
83
+
84
+ Play with argv using **gdb-ruby**.
85
+
86
+ This script does:
87
+ 1. Set break point at `main`.
88
+ 2. Get argv using `register` and `read_memory`.
89
+ 3. Change argv using `write_memory`.
90
+
91
+ ```ruby
92
+ require 'gdb'
93
+
94
+ # launch a gdb instance
95
+ gdb = GDB::GDB.new('bash')
96
+
97
+ # 1. set breakpoint
98
+ gdb.break('main')
99
+ #=> "Breakpoint 1 at 0x41eed0"
100
+ gdb.run('-c "echo cat"')
101
+
102
+ # 2. get argv pointers
103
+ rdi = gdb.reg(:rdi)
104
+ #=> 3
105
+ rsi = gdb.reg(:rsi)
106
+ argv = gdb.readm(rsi, rdi, as: :uint64)
107
+ argv.map{ |c|'0x%x' % c }
108
+ #=> ['0x7fffffffe61b', '0x7fffffffe625', '0x7fffffffe628']
109
+
110
+ # 3. overwrite argv[2]'s 'cat' to 'FAT'
111
+ gdb.writem(argv[2] + 5, 'FAT') # echo FAT
112
+
113
+ puts gdb.execute('continue')
114
+ # Continuing.
115
+ # FAT
116
+ # [Inferior 1 (process 32217) exited normally]
117
+ ```
118
+
119
+ Set a break point, run it, and back to gdb interactive mode.
120
+
121
+ ```ruby
122
+ require 'gdb'
123
+
124
+ # launch a gdb instance
125
+ gdb = GDB::GDB.new('bash')
126
+ # set breakpoints
127
+ gdb.break('main')
128
+ gdb.run
129
+ # shows process do running
130
+ gdb.execute('info reg rip')
131
+ #=> "rip 0x41eed0\t0x41eed0 <main>"
132
+
133
+ # interaction like normal gdb!
134
+ gdb.interact
135
+ ```
136
+
137
+ # Installation
138
+
139
+ Available on RubyGems.org!
140
+ ```
141
+ $ gem install gdb
142
+ ```
143
+
144
+ # Development
145
+
146
+ ```
147
+ git clone https://github.com/david942j/gdb-ruby
148
+ cd gdb-ruby
149
+ bundle
150
+ bundle exec rake
151
+ ```
152
+
153
+ # Bugs & feedbacks
154
+
155
+ Feel free to file an issue if you find any bugs.
156
+ Any feature requests and suggestions are welcome! :grimacing:
157
+
158
+ # Growing up
159
+
160
+ **gdb-ruby** is under developing, give it a star and [watching](https://github.com/david942j/gdb-ruby/subscription)
161
+ for latest updates!
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gdb'
4
+ require 'shellwords'
5
+
6
+ GDB::GDB.new(Shellwords.join(ARGV)).interact
data/lib/gdb.rb CHANGED
@@ -6,4 +6,5 @@
6
6
  module GDB
7
7
  end
8
8
 
9
+ require 'gdb/gdb'
9
10
  require 'gdb/version'
@@ -0,0 +1,25 @@
1
+ require 'pry'
2
+
3
+ module GDB
4
+ # For evaluation ruby code in gdb.
5
+ class EvalContext
6
+ attr_reader :gdb # @return [GDB::GDB]
7
+
8
+ def initialize(gdb)
9
+ @gdb = gdb
10
+ end
11
+
12
+ private
13
+
14
+ # Invoke pry, wrapper with some settings.
15
+ #
16
+ # @return [void]
17
+ def invoke_pry
18
+ org = Pry.config.history.file
19
+ # this has no effect if gdb is launched by pry
20
+ Pry.config.history.file = '~/.gdb-pry_history'
21
+ $stdin.cooked { pry }
22
+ Pry.config.history.file = org
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,278 @@
1
+ require 'io/console'
2
+ require 'pty'
3
+ require 'readline'
4
+
5
+ require 'gdb/eval_context'
6
+ require 'gdb/gdb_error'
7
+ require 'gdb/tube/tube'
8
+ require 'gdb/type_io'
9
+
10
+ module GDB
11
+ # For launching a gdb process.
12
+ class GDB
13
+ # Absolute path to python scripts.
14
+ SCRIPTS_PATH = File.join(__dir__, 'scripts').freeze
15
+
16
+ def initialize(arguments, gdb: 'gdb')
17
+ arguments = "--command=#{File.join(SCRIPTS_PATH, 'gdbinit.py')}" + ' ' + arguments # XXX
18
+ @tube = spawn(gdb + ' ' + arguments)
19
+ @prompt = '(gdb-ruby) '
20
+ @tube.unget(@tube.readuntil(@prompt))
21
+ end
22
+
23
+ # Execute a command in gdb.
24
+ #
25
+ # @param [String] cmd
26
+ # Command to be executed.
27
+ #
28
+ # @return [String]
29
+ # The execution result returned by gdb.
30
+ #
31
+ # @example
32
+ # gdb = GDB::GDB.new('bash')
33
+ # gdb.execute('b main')
34
+ # #=> "Breakpoint 1 at 0x41eed0"
35
+ # gdb.execute('run')
36
+ # gdb.execute('print $rsi')
37
+ # #=> "$1 = 0x7fffffffdef8"
38
+ def execute(cmd)
39
+ @tube.puts(cmd)
40
+ @tube.readuntil(@prompt).strip
41
+ end
42
+ alias exec execute
43
+
44
+ # Set break point.
45
+ #
46
+ # This method some magic, see parameters or examples.
47
+ #
48
+ # @param [Integer, String] point
49
+ # If +Integer+ is given, will be translated as set break point
50
+ # at address +point+, i.e. equivalent to +break *<integer>+.
51
+ # If +String+ is given, equivalent to invoke +execute("break <point>")+.
52
+ #
53
+ # @return [String]
54
+ # Returns what gdb displayed after set a break point.
55
+ #
56
+ # @example
57
+ # gdb = GDB::GDB.new('bash')
58
+ # gdb.break('main')
59
+ # #=> "Breakpoint 1 at 0x41eed0"
60
+ # gdb.break(0x41eed0)
61
+ # #=> "Note: breakpoint 1 also set at pc 0x41eed0.\r\nBreakpoint 2 at 0x41eed0"
62
+ def break(point)
63
+ case point
64
+ when Integer then execute("break *#{point}")
65
+ when String then execute("break #{point}")
66
+ end
67
+ end
68
+ alias b break
69
+
70
+ # Run process.
71
+ #
72
+ # @param [String] args
73
+ # Arguments to pass to run command.
74
+ #
75
+ # @return [String]
76
+ # Returns what gdb displayed.
77
+ #
78
+ # @example
79
+ # gdb = GDB::GDB.new('bash')
80
+ # puts gdb.run('-c "echo 111"')
81
+ # # Starting program: /bin/bash -c "echo 111"
82
+ # # 111
83
+ # # [Inferior 1 (process 3229) exited normally]
84
+ # #=> nil
85
+ #
86
+ # @note
87
+ # If breakpoints are not set properly and cause gdb hangs,
88
+ # this method will hang, too.
89
+ def run(args = '')
90
+ execute('run ' + args)
91
+ end
92
+ alias r run
93
+
94
+ # Get current value of register
95
+ #
96
+ # @param [String, Symbol] reg_name
97
+ #
98
+ # @return [Integer]
99
+ # Value of desired register.
100
+ #
101
+ # @todo
102
+ # Handle when +reg_name+ is not a general-purpose register.
103
+ def register(reg_name)
104
+ check_alive!
105
+ Integer(python_p("gdb.parse_and_eval('$#{reg_name}')").split.first)
106
+ end
107
+ alias reg register
108
+
109
+ # Get the process's text base.
110
+ #
111
+ # @return [Integer]
112
+ # The base address.
113
+ #
114
+ # @note
115
+ # This will also set a variable +$text+ in gdb.
116
+ def text_base
117
+ check_alive!
118
+ base = Integer(execute('info proc stat').scan(/Start of text: (.*)/).flatten.first)
119
+ execute("set $text = #{base}")
120
+ base
121
+ end
122
+ alias code_base text_base
123
+
124
+ # Is process running?
125
+ #
126
+ # Actually judged by if {#pid} returns zero.
127
+ #
128
+ # @return [Boolean]
129
+ # True for process is running.
130
+ def alive?
131
+ !pid.zero?
132
+ end
133
+ alias running? alive?
134
+
135
+ # Get the process's pid.
136
+ #
137
+ # This method implemented by invoking +python print(gdb.selected_inferior().pid)+.
138
+ #
139
+ # @return [Integer]
140
+ # The pid of process. If process is not running, zero is returned.
141
+ def pid
142
+ @pid = python_p('gdb.selected_inferior().pid').to_i
143
+ end
144
+
145
+ # Read current process's memory.
146
+ #
147
+ # See {TypeIO#read} for details.
148
+ #
149
+ # @param [Mixed] args
150
+ # See {TypeIO#read}.
151
+ #
152
+ # @return [Object]
153
+ # See {TypeIO#read}.
154
+ #
155
+ # @yieldparam [IO] io
156
+ # See {TypeIO#read}.
157
+ #
158
+ # @yieldreturn [Object]
159
+ # See {TypeIO#read}.
160
+ #
161
+ # @example
162
+ # # example of fetching argv
163
+ # gdb = GDB::GDB.new('spec/binaries/amd64.elf')
164
+ # gdb.break('main')
165
+ # gdb.run('pusheen the cat')
166
+ # gdb.read_memory(0x400000, 4)
167
+ # #=> "\x7fELF"
168
+ # argc = gdb.register(:rdi)
169
+ # #=> 4
170
+ # args = gdb.read_memory(gdb.register(:rsi), argc, as: :uint64)
171
+ # Array.new(3) do |i|
172
+ # gdb.read_memory(args[i + 1], 1) do |m|
173
+ # str = ''
174
+ # str << m.read(1) until str.end_with?("\x00")
175
+ # str
176
+ # end
177
+ # end
178
+ # #=> ["pusheen\x00", "the\x00", "cat\x00"]
179
+ #
180
+ # # or, use our build-in types listed in {TypeIO::TYPES}
181
+ # gdb.read_memory(args[1], 3, as: :cstring)
182
+ # #=> ["pusheen\x00", "the\x00", "cat\x00"]
183
+ def read_memory(*args, &block)
184
+ check_alive! # this would set @pid
185
+ File.open("/proc/#{@pid}/mem", 'rb') do |f|
186
+ ::GDB::TypeIO.new(f).read(*args, &block)
187
+ end
188
+ end
189
+ alias readm read_memory
190
+
191
+ # Write a string to process at specific address.
192
+ #
193
+ # @param [Integer] addr
194
+ # Target address.
195
+ # @param [String] str
196
+ # String to be written.
197
+ #
198
+ # @return [Integer]
199
+ # Bytes written.
200
+ def write_memory(addr, str)
201
+ check_alive! # this would set @pid
202
+ File.open("/proc/#{@pid}/mem", 'wb') do |f|
203
+ ::GDB::TypeIO.new(f).write(addr, str)
204
+ end
205
+ end
206
+ alias writem write_memory
207
+
208
+ # To simplify the frequency call of +python print(xxx)+.
209
+ #
210
+ # @param [String] cmd
211
+ # python command.
212
+ # @return [String]
213
+ # Execution result.
214
+ def python_p(cmd)
215
+ execute("python print(#{cmd})")
216
+ end
217
+
218
+ # Enter gdb interactive mode.
219
+ # Gdb will be closed after interaction.
220
+ #
221
+ # @return [void]
222
+ def interact
223
+ $stdin.raw { @tube.interact(method(:output_hook)) }
224
+ close
225
+ end
226
+
227
+ # Terminate the gdb process.
228
+ #
229
+ # @return [void]
230
+ def close
231
+ return if @tube.closed?
232
+ @tube.close
233
+ Process.wait(@gdb_pid)
234
+ nil
235
+ end
236
+ alias quit close
237
+
238
+ private
239
+
240
+ def check_alive!
241
+ raise GDBError, 'Process is not running' unless alive?
242
+ end
243
+
244
+ TIOCSWINSZ = 0x5414
245
+
246
+ def spawn(cmd)
247
+ output, input, @gdb_pid = PTY.spawn(cmd)
248
+ output.ioctl(TIOCSWINSZ, [*IO.console.winsize, 0, 0].pack('S*'))
249
+ ::GDB::Tube::Tube.new(input, output)
250
+ end
251
+
252
+ COMMAND_PREFIX = 'gdb-ruby> '.freeze
253
+
254
+ # @param [String] output
255
+ #
256
+ # @return [String]
257
+ def output_hook(output)
258
+ return output.gsub(@prompt, '') unless output.start_with?(COMMAND_PREFIX)
259
+ cmd, args = output.slice(COMMAND_PREFIX.size..-1).split(' ', 2)
260
+ # only support ruby and pry now.
261
+ return output unless %w[ruby pry].include?(cmd)
262
+ args = 'send(:invoke_pry)' if cmd == 'pry'
263
+ # gdb by default set tty
264
+ # hack it
265
+ `stty opost onlcr`
266
+ begin
267
+ eval_context.instance_eval(args)
268
+ rescue StandardError, ScriptError => e
269
+ $stdout.puts("#{e.class}: #{e}")
270
+ end
271
+ nil
272
+ end
273
+
274
+ def eval_context
275
+ @context ||= ::GDB::EvalContext.new(self)
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,5 @@
1
+ module GDB
2
+ # Raise this error if the request will fail in gdb.
3
+ class GDBError < StandardError
4
+ end
5
+ end
@@ -0,0 +1,88 @@
1
+ import gdb
2
+ import signal
3
+
4
+ class GDBRuby():
5
+ def __init__(self):
6
+ # with this life is prettier.
7
+ gdb.execute("set confirm off")
8
+ gdb.execute("set verbose off")
9
+ gdb.execute("set pagination off")
10
+ gdb.execute("set step-mode on")
11
+ gdb.execute("set print elements 0")
12
+ gdb.execute("set print pretty on")
13
+ self.hook_gdb_prompt()
14
+
15
+ '''
16
+ Hook gdb prompt
17
+ '''
18
+ def hook_gdb_prompt(self):
19
+ # don't hook twice
20
+ if gdb.prompt_hook == self._prompt_hook: return
21
+ self._original_hook = gdb.prompt_hook
22
+ gdb.prompt_hook = self._prompt_hook
23
+
24
+ def resume_prompt(self):
25
+ gdb.prompt_hook = self._original_hook
26
+
27
+ def _prompt_hook(self, current_prompt):
28
+ if self._original_hook == None:
29
+ org = '(gdb) '
30
+ else:
31
+ org = self._original_hook(current_prompt)
32
+ return '(gdb-ruby) ' + org
33
+
34
+ __commands__ = []
35
+ def register_command(cls):
36
+ """Decorator for registering new command to GDB."""
37
+ global __commands__
38
+ __commands__.append(cls)
39
+ return cls
40
+
41
+ class GDBRubyCommand(gdb.Command):
42
+ def __init__(self, klass):
43
+ self.klass = klass
44
+ self.__doc__ = klass._doc_
45
+ super(GDBRubyCommand, self).__init__(klass._cmdline_, gdb.COMMAND_USER)
46
+
47
+ def invoke(self, args, _from_tty):
48
+ print("gdb-ruby> " + self.klass._cmdline_ + ' ' + args)
49
+
50
+ @register_command
51
+ class RubyCommand():
52
+ _doc_ = """Evaluate a Ruby command.
53
+ There's an instance 'gdb' for you. See examples.
54
+
55
+ Syntax: ruby <ruby code>
56
+
57
+ Examples:
58
+ ruby p 'abcd'
59
+ # "abcd"
60
+
61
+ Use gdb:
62
+ ruby puts gdb.break('main')
63
+ # Breakpoint 1 at 0x41eed0
64
+
65
+ Method defined will remain in context:
66
+ ruby def a(b); b * b; end
67
+ ruby p a(9)
68
+ # 81
69
+ """
70
+ _cmdline_ = 'ruby'
71
+
72
+ @register_command
73
+ class PryCommand():
74
+ _doc_ = """Enter Ruby interactive shell.
75
+ Everything works like a charm!
76
+
77
+ Syntax: pry
78
+
79
+ Example:
80
+ pry
81
+ # [1] pry(#<GDB::EvalContext>)>
82
+ """
83
+ _cmdline_ = 'pry'
84
+
85
+
86
+ if not 'gdbruby' in globals():
87
+ [GDBRubyCommand(c) for c in __commands__]
88
+ gdbruby = GDBRuby()
@@ -0,0 +1,74 @@
1
+ module GDB
2
+ module Tube
3
+ # IO buffer.
4
+ class Buffer
5
+ attr_reader :size # @return [Integer] size
6
+
7
+ def initialize
8
+ @data = []
9
+ @size = 0
10
+ end
11
+
12
+ # Push string into buffer.
13
+ #
14
+ # @param [String] str
15
+ # String to push.
16
+ #
17
+ # @return [Buffer]
18
+ # Returns self so this method is chainable.
19
+ def <<(str)
20
+ str = str.to_s.dup
21
+ return self if str.empty?
22
+ @data << str
23
+ @size += str.size
24
+ self
25
+ end
26
+
27
+ def empty?
28
+ @size.zero?
29
+ end
30
+
31
+ # Retrieves at most +n+ bytes from buffer.
32
+ #
33
+ # @param [Integer?] n
34
+ # Maximum number of bytes. +n+ equals +nil+ for unlimited.
35
+ #
36
+ # @return [String]
37
+ # Retrieved string.
38
+ def get(n = nil)
39
+ if n.nil? || n >= @size
40
+ ret = @data.join
41
+ @data.clear
42
+ @size = 0
43
+ else
44
+ now = 0
45
+ idx = @data.find_index do |s|
46
+ if s.size + now >= n
47
+ true
48
+ else
49
+ now += s.size
50
+ false
51
+ end
52
+ end
53
+ ret = @data.slice!(0, idx + 1).join
54
+ back = ret.slice!(n..-1)
55
+ @data.unshift(back) unless back.empty?
56
+ @size -= n
57
+ end
58
+ ret
59
+ end
60
+
61
+ # Push front.
62
+ #
63
+ # @param [String] str
64
+ # String to be push.
65
+ #
66
+ # @return [void]
67
+ def unshift(str)
68
+ return if str.nil? || str.empty?
69
+ @data.unshift(str)
70
+ @size += str.size
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,111 @@
1
+ require 'gdb/tube/buffer'
2
+
3
+ module GDB
4
+ # IO-related classes.
5
+ module Tube
6
+ # For simpler IO manipulation.
7
+ class Tube
8
+ # Batch read size.
9
+ READ_SIZE = 4096
10
+
11
+ # @param [IO] io_in
12
+ # @param [IO] io_out
13
+ def initialize(io_in, io_out)
14
+ @in = io_in
15
+ @out = io_out
16
+ @buffer = ::GDB::Tube::Buffer.new
17
+ end
18
+
19
+ # Read +n+ bytes.
20
+ #
21
+ # @param [Integer] n
22
+ # Number of bytes.
23
+ #
24
+ # @return [String]
25
+ # Returns +n+ bytes from current buffer.
26
+ def readn(n)
27
+ partial while @buffer.size < n
28
+ @buffer.get(n)
29
+ end
30
+
31
+ # Receive from +io+ until string +str+ appears.
32
+ #
33
+ # @param [String] str
34
+ # String to be looking for.
35
+ # @param [Boolean] drop
36
+ # If need keep +str+ in end of returned string.
37
+ #
38
+ # @return [Stirng]
39
+ # Data.
40
+ def readuntil(str, drop: true)
41
+ cur = readn(str.size)
42
+ cur << readn(1) until cur.index(str)
43
+ cur.slice!(-str.size..-1) if drop
44
+ cur
45
+ end
46
+
47
+ # Put to front of buffer.
48
+ #
49
+ # @param [String] str
50
+ #
51
+ # @return [nil]
52
+ def unget(str)
53
+ @buffer.unshift(str)
54
+ nil
55
+ end
56
+
57
+ # @param [#to_s] data
58
+ # Data to be sent.
59
+ #
60
+ # @return [void]
61
+ def puts(data)
62
+ @in.puts(data)
63
+ readuntil(data)
64
+ end
65
+
66
+ # Enter interactive mode.
67
+ #
68
+ # @param [Proc] output_hook
69
+ # Called before flush the output to +$stdout+.
70
+ #
71
+ # @return [void]
72
+ def interact(output_hook = nil)
73
+ @out.ungetc(@buffer.get)
74
+ loop do
75
+ io, = IO.select([$stdin, @out])
76
+ @in.write($stdin.readpartial(READ_SIZE)) if io.include?($stdin)
77
+ next unless io.include?(@out)
78
+ begin
79
+ recv = @out.readpartial(READ_SIZE)
80
+ recv = output_hook.call(recv) if output_hook
81
+ $stdout.write(recv) if recv
82
+ @out.ungetc(@buffer.get) unless @buffer.empty?
83
+ rescue Errno::EIO
84
+ break
85
+ end
86
+ end
87
+ end
88
+
89
+ # Close both side.
90
+ #
91
+ # @return [void]
92
+ def close
93
+ @in.close
94
+ @out.close
95
+ end
96
+
97
+ # Is {#close} invoked?
98
+ #
99
+ # @return [Boolean]
100
+ def closed?
101
+ @in.closed? && @out.closed?
102
+ end
103
+
104
+ private
105
+
106
+ def partial
107
+ @buffer << @out.readpartial(READ_SIZE)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,148 @@
1
+ module GDB
2
+ # Support read / write custom defined tpyes.
3
+ class TypeIO
4
+ class << self
5
+ # Read a little endian integer.
6
+ #
7
+ # @param [#read] io
8
+ # @param [Integer] byte
9
+ # @param [:signed, :unsigned] sign
10
+ # Signed or unsigned integer.
11
+ #
12
+ # @return [Integer]
13
+ # The read integer.
14
+ #
15
+ # @example
16
+ # read_integer(StringIO.new("\x80"), 1, :signed)
17
+ # #=> -128
18
+ def read_integer(io, byte, sign)
19
+ str = io.read(byte).reverse # little endian
20
+ val = str.bytes.reduce(0) { |sum, b| sum * 256 + b }
21
+ if sign == :signed && val >= (1 << (8 * byte - 1))
22
+ val -= 1 << (8 * byte)
23
+ end
24
+ val
25
+ end
26
+
27
+ # Read until +needle+ appears.
28
+ #
29
+ # @param [IO] io
30
+ # @param [String] needle
31
+ #
32
+ # @return [String]
33
+ def read_until(io, needle)
34
+ str = ''
35
+ str << io.read(1) until str.end_with?(needle)
36
+ str
37
+ end
38
+ end
39
+
40
+ # Supported built-in types.
41
+ TYPES = {
42
+ string: nil, # will be special handled
43
+
44
+ cstring: ->(io) { TypeIO.read_until(io, "\x00") }, # read a null-terminated string.
45
+
46
+ int8: ->(io) { TypeIO.read_integer(io, 1, :signed) },
47
+ int16: ->(io) { TypeIO.read_integer(io, 2, :signed) },
48
+ int32: ->(io) { TypeIO.read_integer(io, 4, :signed) },
49
+ int64: ->(io) { TypeIO.read_integer(io, 8, :signed) },
50
+ int128: ->(io) { TypeIO.read_integer(io, 16, :signed) },
51
+
52
+ uint8: ->(io) { TypeIO.read_integer(io, 1, :unsigned) },
53
+ uint16: ->(io) { TypeIO.read_integer(io, 2, :unsigned) },
54
+ uint32: ->(io) { TypeIO.read_integer(io, 4, :unsigned) },
55
+ uint64: ->(io) { TypeIO.read_integer(io, 8, :unsigned) },
56
+ uint128: ->(io) { TypeIO.read_integer(io, 16, :unsigned) },
57
+
58
+ float: ->(io) { io.read(4).unpack('F').first },
59
+ double: ->(io) { io.read(8).unpack('D').first }
60
+ }.freeze
61
+
62
+ # Instantiate a {TypeIO} object.
63
+ #
64
+ # @param [IO, #pos=, #read, #write] io
65
+ # The IO file.
66
+ def initialize(io)
67
+ @io = io.binmode
68
+ end
69
+
70
+ # Read data at specific address and cast into desired type.
71
+ #
72
+ # @param [Integer] addr
73
+ # Address to be read.
74
+ #
75
+ # @param [Integer] size
76
+ # Number of data to be read. See parameter +as+ for details.
77
+ #
78
+ # @param [Symbol] as
79
+ # The needed returned type.
80
+ # Note that the total bytes be read will be +size * sizeof(as)+.
81
+ # For example, if +as+ equals +:int32+, +size * 4+ bytes would be read,
82
+ # and returned type is array of 32 bits signed integers.
83
+ #
84
+ # Supported types are listed in {TypeIO::TYPES}, all integer-like types
85
+ # are seen as little endian. If you need big endian or other fashion things, pass a block
86
+ # instead of using parameter +as+.
87
+ #
88
+ # @yieldparam [IO] io
89
+ # If block is given, the parameter +as+ would be ignored.
90
+ # Block would be invoked +size+ times, and the returned object would be collected into
91
+ # one array and returned.
92
+ #
93
+ # This is convenient for reading non-stable size objects, e.g. c++'s string object.
94
+ # See examples for clearer usage.
95
+ #
96
+ # @yieldreturn [Object]
97
+ # Whatever object you like.
98
+ #
99
+ # @return [String, Object, Array<Object>]
100
+ # If +as+ equals to +:string+, the string with length +size+ would be returned.
101
+ # Otherwise, array of objects would be returned.
102
+ # An exception is when +size+ equals to 1, the read object would be returned
103
+ # instead of create an array with only one element.
104
+ #
105
+ # @example
106
+ # io = TypeIO.new(StringIO.new("AAAA"))
107
+ # io.read(0, 3)
108
+ # #=> "AAA"
109
+ # io.read(0, 1, as: :uint32)
110
+ # #=> 1094795585 # 0x41414141
111
+ #
112
+ # io = TypeIO.new(StringIO.new("\xef\xbe\xad\xde"))
113
+ # io.read(0, 4, as: :int8)
114
+ # #=> [-17, -66, -83, -34]
115
+ #
116
+ # io = TypeIO.new(StringIO.new("\x04ABCD\x03AAA\x00\x04meow"))
117
+ # io.read(0, 4) do |m|
118
+ # len = m.read(1).ord
119
+ # m.read(len)
120
+ # end
121
+ # #=> ['ABCD', 'AAA', '', 'meow']
122
+ def read(addr, size, as: :string)
123
+ @io.pos = addr
124
+ if block_given?
125
+ return yield @io if size == 1
126
+ Array.new(size) { yield @io }
127
+ else
128
+ raise ArgumentError, "Unsupported types #{as.inspect}" unless TYPES.key?(as)
129
+ return @io.read(size) if as == :string
130
+ read(addr, size, &TYPES[as])
131
+ end
132
+ end
133
+
134
+ # Write a string at specific address.
135
+ #
136
+ # @param [Integer] addr
137
+ # Target address.
138
+ # @param [String] str
139
+ # String to be written.
140
+ #
141
+ # @return [Integer]
142
+ # Bytes written.
143
+ def write(addr, str)
144
+ @io.pos = addr
145
+ @io.write(str)
146
+ end
147
+ end
148
+ end
@@ -1,3 +1,4 @@
1
1
  module GDB
2
- VERSION = '0.1.0'.freeze
2
+ # The current version of GDB.
3
+ VERSION = '0.2.0'.freeze
3
4
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - david942j
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-20 00:00:00.000000000 Z
11
+ date: 2017-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: codeclimate-test-reporter
14
+ name: pry
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.6'
20
- type: :development
19
+ version: '0.11'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.6'
26
+ version: '0.11'
27
27
  - !ruby/object:Gem::Dependency
28
- name: pry
28
+ name: codeclimate-test-reporter
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.10'
33
+ version: '0.6'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.10'
40
+ version: '0.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -108,16 +108,26 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.9'
111
- description: " It's time for ruby lovers to use gdb in ruby, and... use ruby in gdb!\n"
111
+ description: 'It''s time for Ruby lovers to use Ruby in gdb, and... gdb in Ruby!
112
+
113
+ '
112
114
  email:
113
115
  - david942j@gmail.com
114
- executables: []
116
+ executables:
117
+ - gdb-ruby
115
118
  extensions: []
116
119
  extra_rdoc_files: []
117
120
  files:
118
121
  - README.md
119
- - Rakefile
122
+ - bin/gdb-ruby
120
123
  - lib/gdb.rb
124
+ - lib/gdb/eval_context.rb
125
+ - lib/gdb/gdb.rb
126
+ - lib/gdb/gdb_error.rb
127
+ - lib/gdb/scripts/gdbinit.py
128
+ - lib/gdb/tube/buffer.rb
129
+ - lib/gdb/tube/tube.rb
130
+ - lib/gdb/type_io.rb
121
131
  - lib/gdb/version.rb
122
132
  homepage: https://github.com/david942j/gdb-ruby
123
133
  licenses:
@@ -142,5 +152,5 @@ rubyforge_project:
142
152
  rubygems_version: 2.6.10
143
153
  signing_key:
144
154
  specification_version: 4
145
- summary: Use gdb in ruby!
155
+ summary: GDB Ruby-binding and Ruby command in GDB
146
156
  test_files: []
data/Rakefile DELETED
@@ -1,19 +0,0 @@
1
- require 'rspec/core/rake_task'
2
- require 'rubocop/rake_task'
3
- require 'yard'
4
-
5
- task default: %i(rubocop spec)
6
-
7
- RuboCop::RakeTask.new(:rubocop) do |task|
8
- task.patterns = %w[lib/**/*.rb spec/**/*.rb']
9
- end
10
-
11
- RSpec::Core::RakeTask.new(:spec) do |task|
12
- task.pattern = './spec/**/*_spec.rb'
13
- task.rspec_opts = ['--color', '--require spec_helper', '--order rand']
14
- end
15
-
16
- YARD::Rake::YardocTask.new(:doc) do |t|
17
- t.files = ['lib/**/*.rb']
18
- t.stats_options = ['--list-undoc']
19
- end