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 +4 -4
- data/README.md +161 -2
- data/bin/gdb-ruby +6 -0
- data/lib/gdb.rb +1 -0
- data/lib/gdb/eval_context.rb +25 -0
- data/lib/gdb/gdb.rb +278 -0
- data/lib/gdb/gdb_error.rb +5 -0
- data/lib/gdb/scripts/gdbinit.py +88 -0
- data/lib/gdb/tube/buffer.rb +74 -0
- data/lib/gdb/tube/tube.rb +111 -0
- data/lib/gdb/type_io.rb +148 -0
- data/lib/gdb/version.rb +2 -1
- metadata +23 -13
- data/Rakefile +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9b16e3cec333997d16a2400dacc343110aec6a4
|
4
|
+
data.tar.gz: dc486d72d111cfaeac7928402ee1e39a57552bd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e8c780fe48de70df13df78fb9f987afab66d268f6f4b27b85665279f4fed43abdc512806a7b7467e77006f5e8c95f2069532fa717134503568c3f65842b9dbb
|
7
|
+
data.tar.gz: 92dda6f5248627108645bc55c9e3d5a3f7d476bfa2cc3295e4edc7a53e2815ca9f488712109422608fca88fdb29c9f0edec7ad8f8b88647fbf85b5007e277770
|
data/README.md
CHANGED
@@ -1,2 +1,161 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
[](https://travis-ci.org/david942j/gdb-ruby)
|
2
|
+
[](https://codeclimate.com/github/david942j/gdb-ruby)
|
3
|
+
[](https://codeclimate.com/github/david942j/gdb-ruby)
|
4
|
+
[](https://codeclimate.com/github/david942j/gdb-ruby/coverage)
|
5
|
+
[](https://inch-ci.org/github/david942j/gdb-ruby)
|
6
|
+
[](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
|
+

|
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!
|
data/bin/gdb-ruby
ADDED
data/lib/gdb.rb
CHANGED
@@ -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
|
data/lib/gdb/gdb.rb
ADDED
@@ -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,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
|
data/lib/gdb/type_io.rb
ADDED
@@ -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
|
data/lib/gdb/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2017-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: pry
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
20
|
-
type: :
|
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.
|
26
|
+
version: '0.11'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
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.
|
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.
|
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:
|
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
|
-
-
|
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:
|
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
|