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 +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
|
+
[![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!
|
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
|