gdb 0.1.0 → 0.2.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: 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