gdbruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8f60d770afee00f6dcff8f144788e63210a5714
4
+ data.tar.gz: 08a8449dc823f8d43403bf09ceca37499e37619f
5
+ SHA512:
6
+ metadata.gz: 57c4b47fd4b823cbfcf7461eb5561f9f36b68da821c0c0cad394123514a4cf2ccdf2c18863eea8a3d02e8f1d336646acb78561678bd3d8b7c8762028c32e34bc
7
+ data.tar.gz: 6b62783d5847fa6f787bba87739599ec33456d4bce35f1e8eb8eba617f0d47ddcd7e561ebb2dff374fb9ad4851b3a782e005e7719667c1a4e89f0ff7f9f3c058
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "2.0.0"
5
+
6
+ before_install:
7
+ - sudo apt-get -qq update
8
+ - sudo apt-get -qq install gdb
9
+
10
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,68 @@
1
+ # gdbruby.rb
2
+
3
+ ## Overview
4
+
5
+ gdbruby.rb can output these information with live process or core.
6
+
7
+ - environment variables
8
+ - C stacktrace
9
+ - Ruby backtrace
10
+
11
+ This is Ruby port of gdbperl.pl made by Akira Higuchi.
12
+
13
+ ## Precondition
14
+
15
+ Your Ruby executable must have debug symbol.
16
+
17
+ ## Usage
18
+
19
+ With live process(process id: 24113)
20
+
21
+ ```sh
22
+ $ gdbruby.rb 24113
23
+ ```
24
+
25
+ With core file. You have to specify path of ruby executable.
26
+
27
+ ```sh
28
+ $ gdbruby.rb core.24113 `rbenv which ruby`
29
+ ```
30
+
31
+ You can get core file with gcore script or execute gcore command on gdb like below.
32
+
33
+ ```
34
+ $ gdb
35
+ (gdb) attach 24113
36
+ (gdb) gcore core.24113
37
+ (gdb) detach
38
+ ```
39
+
40
+ ## Options
41
+
42
+ You can specify options. 0 is interprited as false.
43
+
44
+ ```sh
45
+ $ gdbruby.rb 24113 verbose_gdb=1 c_trace=1
46
+ ```
47
+
48
+ - verbose_gdb: Show request and response to/from gdb(default: false)
49
+ - env: Show environment variables(default: true)
50
+ - c_trace: Show C stacktrace(default:true)
51
+
52
+ ## ToDo
53
+
54
+ - Print all Ruby threads
55
+ - Print arguments on Ruby backtrace
56
+ - Speeding up Ruby's type check
57
+ - List Ruby objects
58
+ - Check memory usage
59
+
60
+ ## FAQ
61
+
62
+ ### Why don't you call functions such like rb_vm_get_sourceline()
63
+
64
+ If you use gdbruby.rb with live process, gdb can call these functions. But if you use gdbruby.rb with core file, gdb cannot call c functions. So I re-implement these functions.
65
+
66
+ ### Which version does it support?
67
+
68
+ Ruby 2.0.0 only.
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+ task :default => :spec
10
+
11
+ desc 'Open an irb session preloaded with the gem library'
12
+ task :console do
13
+ sh 'irb -rubygems -I lib'
14
+ end
15
+ task :c => :console
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,533 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set expandtab ts=2 sw=2 nowrap ft=ruby ff=unix : */
3
+
4
+ # gdbruby.rb - shows the call trace of a running ruby process
5
+ #
6
+ # Ruby porting of gdbperl.pl.
7
+ # gdbperl.pl is made by ahiguchi.
8
+ # https://github.com/ahiguti/gdbperl
9
+ #
10
+ # Copyright (c) Tasuku SUENAGA a.k.a. gunyarakun
11
+ # All rights reserved.
12
+ #
13
+ # Redistribution and use in source and binary forms, with or without
14
+ # modification, are permitted provided that the following conditions are met:
15
+ #
16
+ # Redistributions of source code must retain the above copyright notice,
17
+ # this list of conditions and the following disclaimer.
18
+ # Redistributions in binary form must reproduce the above copyright notice,
19
+ # this list of conditions and the following disclaimer in the
20
+ # documentation and/or other materials provided with the distribution.
21
+ # Neither the name of the author nor the names of its contributors
22
+ # may be used to endorse or promote products derived from this software
23
+ # without specific prior written permission.
24
+ #
25
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
29
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35
+ # POSSIBILITY OF SUCH DAMAGE.
36
+ #
37
+ # Usage: gdbruby.rb PROCESS_ID [ruby_EXECUTABLE] [OPTION=VALUE [...]]
38
+ # gdbruby.rb CORE_FILE ruby_EXECUTABLE [OPTION=VALUE [...]]
39
+
40
+ require 'open3'
41
+
42
+ class GDBRubyConfig
43
+ attr_reader :core_or_pid, :exe, :is_pid
44
+
45
+ def initialize(argvs)
46
+ @config_map = {}
47
+ @argv = []
48
+
49
+ argvs.each do |argv|
50
+ if argv =~ /^(\w+)=(.+)$/
51
+ @config_map[$1] = $2
52
+ else
53
+ @argv << argv
54
+ end
55
+ end
56
+
57
+ parse_argv
58
+ end
59
+
60
+ def parse_argv
61
+ @core_or_pid = @argv[0]
62
+
63
+ unless @core_or_pid
64
+ message =
65
+ "Usage: #{$0} PROCESS_ID [ruby_EXECUTABLE] [OPTION=VALUE [...]]\n" +
66
+ "Usage: #{$0} CORE_FILE ruby_EXECUTABLE [OPTION=VALUE [...]]\n"
67
+ puts message
68
+ exit 1
69
+ end
70
+
71
+ exe = @argv[1]
72
+
73
+ @is_pid = (@core_or_pid =~ /^\d+$/)
74
+ if @is_pid
75
+ if exe.nil?
76
+ begin
77
+ if RUBY_PLATFORM =~ /linux/
78
+ exe = File.readlink("/proc/#{@core_or_pid}/exe")
79
+ end
80
+ rescue
81
+ end
82
+ end
83
+
84
+ if exe.nil?
85
+ exe = `rbenv which ruby`
86
+ exe = `which ruby` unless FileTest.exist?(exe)
87
+ exe.chomp!
88
+ end
89
+ end
90
+
91
+ raise "failed to detect ruby executable" unless exe
92
+ @exe = exe
93
+ end
94
+
95
+ def [](key, default_value = nil)
96
+ if @config_map.has_key?(key)
97
+ return case default_value
98
+ when TrueClass, FalseClass
99
+ not (@config_map[key].empty? || @config_map[key] == '0')
100
+ when Numeric
101
+ @config_map[key].to_i
102
+ else
103
+ @config_map[key]
104
+ end
105
+ end
106
+ default_value
107
+ end
108
+ end
109
+
110
+ class GDB
111
+ COMMAND_READ_BUFFER_SIZE = 1024
112
+ attr_reader :exec_options
113
+
114
+ def initialize(config)
115
+ @config = config
116
+ @exec_options = ['gdb', '-silent', '-nw', @config.exe, @config.core_or_pid]
117
+ end
118
+
119
+ def run
120
+ @gdb_stdin, @gdb_stdout, @gdb_stderr = *Open3.popen3(*@exec_options)
121
+ prepare
122
+ begin
123
+ yield
124
+ detach
125
+ ensure
126
+ if @config.is_pid
127
+ Process.kill('CONT', @config.core_or_pid.to_i)
128
+ end
129
+ @gdb_stdin.close
130
+ @gdb_stdout.close
131
+ @gdb_stderr.close
132
+ end
133
+ end
134
+
135
+ def prepare
136
+ cmd_exec('')
137
+ cmd_exec('set pagination off')
138
+ end
139
+
140
+ def detach
141
+ cmd_get_value("detach")
142
+ cmd_get_value("quit")
143
+ end
144
+
145
+ def log_gdb(pre, message)
146
+ return unless @config['verbose_gdb', false]
147
+ message.each_line do |line|
148
+ puts "#{pre}: #{line}"
149
+ end
150
+ end
151
+
152
+ def cmd_get_pointer(cmd, type)
153
+ response = cmd_exec(cmd)
154
+ raise "Invalid pointer #{response}" unless response =~ /\(#{type} \*\) (0x[0-9a-f]+)/
155
+ $1
156
+ end
157
+
158
+ def cmd_exec(cmd)
159
+ log_gdb('C', cmd)
160
+ if cmd
161
+ send_cmd = cmd.empty? ? cmd : "#{cmd}\n"
162
+ r = @gdb_stdin.syswrite(send_cmd)
163
+ if r < send_cmd.length
164
+ raise "failed to send: [#{cmd}]"
165
+ end
166
+ end
167
+
168
+ responses = []
169
+ while true
170
+ # TODO: specify buffer size
171
+ begin
172
+ buf = @gdb_stdout.sysread(COMMAND_READ_BUFFER_SIZE)
173
+ rescue
174
+ break
175
+ end
176
+ responses << buf
177
+ break if buf =~ /\(gdb\) $/
178
+ end
179
+
180
+ response = responses.join('')
181
+ log_gdb('R', response)
182
+ response
183
+ end
184
+
185
+ def cmd_get_value(cmd)
186
+ response = cmd_exec(cmd)
187
+ return '' unless response =~ /\A\$\d+ =\s+(.+)/
188
+
189
+ value = $1
190
+ if value =~ /0x\w+\s+\"(.+)\"/
191
+ $1
192
+ else
193
+ value
194
+ end
195
+ end
196
+ end
197
+
198
+ class RubyInternal
199
+ FL_USHIFT = 12
200
+ FL_USER1 = 1 << (FL_USHIFT + 1)
201
+ RSTRING_NOEMBED = FL_USER1
202
+
203
+ VM_FRAME_MAGIC_CFUNC = 0x61
204
+ VM_FRAME_MAGIC_MASK_BITS = 8
205
+ VM_FRAME_MAGIC_MASK = ~(~0 << VM_FRAME_MAGIC_MASK_BITS)
206
+
207
+ def initialize(gdb)
208
+ @gdb = gdb
209
+ end
210
+
211
+ def ruby_vm_ifunc_p(pointer)
212
+ # pointer is string like 0xaabbccdd
213
+ @gdb.cmd_get_value("p (enum ruby_value_type)(((struct RBasic *)(#{pointer}))->flags & RUBY_T_MASK) == RUBY_T_NODE") != '0'
214
+ end
215
+
216
+ def ruby_vm_normal_iseq_p(pointer)
217
+ @gdb.cmd_get_value("p #{pointer} && #{pointer} != 0") != '0' and not ruby_vm_ifunc_p(pointer)
218
+ end
219
+
220
+ def rb_vm_get_sourceline(cfp, iseq)
221
+ if ruby_vm_normal_iseq_p(iseq)
222
+ # calc_lineno()@vm_backtrace.c
223
+ current_position = @gdb.cmd_get_value("p #{cfp}->pc - #{iseq}->iseq_encoded").to_i
224
+ # rb_iseq_line_no()@iseq.c
225
+ current_position -= 1 unless current_position == 0
226
+ # find_line_no@iseq.c and get_line_info@iseq.c
227
+ line_info_size = @gdb.cmd_get_value("p #{iseq}->line_info_size").to_i
228
+ line_info_table = "#{iseq}->line_info_table"
229
+ case line_info_size
230
+ when 0
231
+ return 0
232
+ when 1
233
+ return @gdb.cmd_get_value("p #{line_info_table}[0].line_no").to_i
234
+ else
235
+ (1..line_info_size).each do |i|
236
+ position = @gdb.cmd_get_value("p #{line_info_table}[#{i}].position").to_i
237
+ if position == current_position
238
+ return @gdb.cmd_get_value("p #{line_info_table}[#{i}].line_no").to_i
239
+ elsif position > current_position
240
+ return @gdb.cmd_get_value("p #{line_info_table}[#{i - 1}].line_no").to_i
241
+ end
242
+ end
243
+ end
244
+ end
245
+ 0
246
+ end
247
+
248
+ # NOTE: This logic is slow because many commands are sent to gdb.
249
+ # Fetch consts with 'ptype enum ruby_value_type' first and
250
+ # check types in Ruby.
251
+ def rb_type(value)
252
+ type_str = nil
253
+ # IMMEDIATE_P
254
+ if @gdb.cmd_get_value("p (int)(#{value}) & RUBY_IMMEDIATE_MASK") != '0'
255
+ # FIXNUM_P
256
+ if @gdb.cmd_get_value("p (int)(#{value}) & RUBY_FIXNUM_FLAG") != '0'
257
+ type_str = 'RUBY_T_FIXNUM'
258
+ # FLONUM_P
259
+ elsif @gdb.cmd_get_value("p ((int)(#{value}) & RUBY_FLONUM_MASK) == RUBY_FLONUM_FLAG") != '0'
260
+ type_str = 'RUBY_T_FLONUM'
261
+ elsif @gdb.cmd_get_value("p (#{value}) == RUBY_Qtrue") != '0'
262
+ type_str = 'RUBY_T_TRUE'
263
+ # SYMBOL_P
264
+ elsif @gdb.cmd_get_value("p (VALUE)(#{value}) & ~(~(VALUE)0 << RUBY_SPECIAL_SHIFT) == RUBY_SYMBOL_FLAG") != '0'
265
+ type_str = 'RUBY_T_SYMBOL'
266
+ elsif @gdb.cmd_get_value("p (#{value}) == RUBY_Qundef") != '0'
267
+ type_str = 'RUBY_T_UNDEF'
268
+ end
269
+ elsif @gdb.cmd_get_value("p (int)(#{value}) & RUBY_FIXNUM_FLAG") != '0'
270
+ # special consts
271
+ const = @gdb.cmd_get_value("p (enum ruby_special_consts)(#{value})")
272
+ # TODO: change to map
273
+ case const
274
+ when 'RUBY_Qnil'
275
+ type_str = 'RUBY_T_NIL'
276
+ when 'RUBY_Qfalse'
277
+ type_str = 'RUBY_T_FALSE'
278
+ end
279
+ else
280
+ # builtin type
281
+ type_str = @gdb.cmd_get_value("p (enum ruby_value_type)(((struct RBasic*)(#{value}))->flags & RUBY_T_MASK)")
282
+ end
283
+ end
284
+
285
+ def rstring_ptr(value_pointer)
286
+ no_embed = @gdb.cmd_get_value("p ((struct RBasic *)(#{value_pointer}))->flags & #{RSTRING_NOEMBED}")
287
+ if no_embed == '0'
288
+ # embedded in struct
289
+ @gdb.cmd_get_value("p (char *)((struct RString *)(#{value_pointer}))->as.ary")
290
+ else
291
+ # heap pointer
292
+ @gdb.cmd_get_value("p (char *)((struct RString *)(#{value_pointer}))->as.heap.ptr")
293
+ end
294
+ end
295
+
296
+ def rubyvm_cfunc_frame_p(cfp)
297
+ @gdb.cmd_get_value("p (#{cfp}->flag & #{VM_FRAME_MAGIC_MASK}) == #{VM_FRAME_MAGIC_CFUNC}") != '0'
298
+ end
299
+
300
+ def do_hash(key, table)
301
+ # NOTE: table->type->hash is always st_numhash
302
+ key
303
+ end
304
+
305
+ def st_lookup(table, key)
306
+ hash_val = do_hash(key, table)
307
+
308
+ raise if @gdb.cmd_get_value("p (#{table})->entries_packed") != '0'
309
+ raise if @gdb.cmd_get_value("p (#{table})->type->hash == st_numhash") == '0'
310
+ raise if @gdb.cmd_get_value("p (#{table})->type->compare == st_numcmp") == '0'
311
+
312
+ # TODO: check table->entries_packed
313
+ bin_pos = @gdb.cmd_get_value("p (#{hash_val}) % (#{table})->num_bins")
314
+
315
+ ptr = find_entry(table, key, hash_val, bin_pos)
316
+
317
+ if ptr.hex == 0
318
+ nil
319
+ else
320
+ value = @gdb.cmd_get_value("p ((struct st_table_entry *)(#{ptr}))->record")
321
+ value
322
+ end
323
+ end
324
+
325
+ def ptr_not_equal(table, ptr, hash_val, key)
326
+ ptr =~ /(0x[0-9a-f]+)\z/
327
+ ptr_num = $1.hex
328
+ t_hash = @gdb.cmd_get_value("p (#{ptr})->hash")
329
+ t_key = @gdb.cmd_get_value("p (#{ptr})->key")
330
+ # NOTE: table->type->compare is always st_numcmp
331
+ ptr_num != 0 and (t_hash != hash_val or t_key != key)
332
+ end
333
+
334
+ def find_entry(table, key, hash_val, bin_pos)
335
+ ptr = @gdb.cmd_get_value("p (#{table})->as.big.bins[#{bin_pos}]")
336
+ if ptr_not_equal(table, ptr, hash_val, key)
337
+ next_ptr = @gdb.cmd_get_value("p (#{ptr})->next")
338
+ while ptr_not_equal(table, next_ptr, hash_val, key)
339
+ ptr = next_ptr
340
+ next_ptr = @gdb.cmd_get_value("p (#{ptr})->next")
341
+ end
342
+ ptr = next_ptr
343
+ end
344
+ ptr =~ /(0x[0-9a-f]+)\z/
345
+ $1
346
+ end
347
+
348
+ def rb_id2str(id)
349
+ rstring_ptr(st_lookup('global_symbols.id_str', id))
350
+ end
351
+ end
352
+
353
+ class GDBRuby
354
+ MAX_FRAMES = 30
355
+
356
+ def initialize(config)
357
+ @config = config
358
+ end
359
+
360
+ def trace
361
+ @gdb = GDB.new(@config)
362
+ @ri = RubyInternal.new(@gdb)
363
+ puts "command:\n#{@gdb.exec_options.join(' ')}"
364
+ puts ''
365
+ @gdb.run do
366
+ show_environ if @config['env', true]
367
+ show_ruby_version
368
+ show_backtrace
369
+ end
370
+ end
371
+
372
+ def show_environ
373
+ i = 0
374
+ puts "environ:"
375
+ while true
376
+ response = @gdb.cmd_get_value("p ((char **)environ)[#{i}]")
377
+ break if response.empty? or response == '0x0'
378
+ puts response
379
+ i += 1
380
+ end
381
+ puts ''
382
+ end
383
+
384
+ def get_map_of_ruby_thread_pointer_to_gdb_thread_id
385
+ # After 'set pagination off' is executed,
386
+ # thread info is one line per thread.
387
+ map = {}
388
+ @gdb.cmd_exec('info threads').each_line do |line|
389
+ if line =~ /\A[\s\*]+(\d+)/
390
+ gdb_thread_id = $1
391
+ @gdb.cmd_exec("thread #{gdb_thread_id}")
392
+ @gdb.cmd_exec("backtrace").each_line do |bt_line|
393
+ if bt_line =~ /\(th=(0x[0-9a-f]+)/
394
+ ruby_thread_pointer = $1
395
+ map[ruby_thread_pointer] = gdb_thread_id
396
+ break
397
+ end
398
+ end
399
+ end
400
+ end
401
+ map
402
+ end
403
+
404
+ def get_ruby_frame(frame_count)
405
+ # Accessor
406
+ cfp = "(ruby_current_thread->cfp + #{frame_count})"
407
+ iseq = "#{cfp}->iseq"
408
+
409
+ # Check iseq
410
+ iseq_ptr = @gdb.cmd_get_pointer("p #{iseq}", 'rb_iseq_t')
411
+ if iseq_ptr.hex != 0
412
+ # TODO: check cfp->pc is null or not
413
+
414
+ iseq_type = @gdb.cmd_get_value("p #{iseq}->type").intern
415
+
416
+ case iseq_type
417
+ when :ISEQ_TYPE_TOP
418
+ return
419
+ end
420
+
421
+ # Ruby function
422
+ @prev_location = {
423
+ :cfp => cfp,
424
+ :iseq => iseq,
425
+ }
426
+ file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.absolute_path"))
427
+ if file_path.empty?
428
+ file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.path"))
429
+ end
430
+ label = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.label"))
431
+ base_label = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.base_label"))
432
+ line_no = @ri.rb_vm_get_sourceline(cfp, iseq)
433
+
434
+ self_value = @gdb.cmd_get_value("p #{cfp}->self")
435
+ self_type = @ri.rb_type(self_value)
436
+ self_name = self_type == 'RUBY_T_CLASS' ? @gdb.cmd_get_value("p rb_class2name(#{cfp}->self)") : ''
437
+
438
+ func_prefix = "#{self_name}#" unless self_name.empty?
439
+
440
+ {
441
+ :callee => label.empty? ? '(unknown)' : "#{func_prefix}#{label}",
442
+ :args => '', # TODO: implement
443
+ :source_path_line => "#{file_path}:#{line_no}",
444
+ }
445
+ elsif @ri.rubyvm_cfunc_frame_p(cfp)
446
+ # C function
447
+
448
+ mid = @gdb.cmd_get_value("p #{cfp}->me->def ? #{cfp}->me->def->original_id : #{cfp}->me->called_id")
449
+ label = @ri.rb_id2str(mid)
450
+ if @prev_location
451
+ cfp = @prev_location[:cfp]
452
+ iseq = @prev_location[:iseq]
453
+
454
+ file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.absolute_path"))
455
+ if file_path.empty?
456
+ file_path = @ri.rstring_ptr(@gdb.cmd_get_value("p #{iseq}->location.path"))
457
+ end
458
+ line_no = @ri.rb_vm_get_sourceline(cfp, iseq)
459
+ end
460
+
461
+ {
462
+ :callee => label,
463
+ :args => '', # TODO: implement
464
+ :source_path_line => "#{file_path}:#{line_no}",
465
+ }
466
+ end
467
+ end
468
+
469
+ def check_ruby_version
470
+ @ruby_version = @gdb.cmd_get_value('p ruby_version')
471
+ case @ruby_version.intern
472
+ when :'2.0.0'
473
+ end
474
+ raise "unknown ruby version" unless @ruby_version
475
+ end
476
+
477
+ def show_ruby_version
478
+ check_ruby_version
479
+ puts 'ruby_version:'
480
+ puts @ruby_version
481
+ puts ''
482
+ end
483
+
484
+ def show_backtrace
485
+ # TODO: List threads with ruby_current_vm->living_threads and dump all threads.
486
+ # Now, we dump only ruby_current_thread which is equivalent to ruby_current_vm->running_thread.
487
+
488
+ thread_map = get_map_of_ruby_thread_pointer_to_gdb_thread_id
489
+
490
+ # Detect ruby running thread and change gdb thread to it
491
+ current_thread_pointer = @gdb.cmd_get_pointer('p ruby_current_thread', 'rb_thread_t')
492
+ gdb_thread_id = thread_map[current_thread_pointer]
493
+ raise 'Cannot find current thread id in gdb' if gdb_thread_id.nil?
494
+ @gdb.cmd_exec("thread #{gdb_thread_id}")
495
+
496
+ # Show C backtrace
497
+ if @config['c_trace', true]
498
+ response = @gdb.cmd_exec('bt')
499
+ puts 'c_backtrace:'
500
+ response.each_line do |line|
501
+ break if line == '(gdb) '
502
+ puts line
503
+ end
504
+ puts ''
505
+ end
506
+
507
+ # Show Ruby backtrace
508
+ puts 'ruby_backtrace:'
509
+ cfp_count = @gdb.cmd_get_value('p (rb_control_frame_t *)(ruby_current_thread->stack + ruby_current_thread->stack_size) - ruby_current_thread->cfp').to_i
510
+
511
+ frame_infos = []
512
+ @prev_location = nil
513
+ # NOTE: @prev_location may not be set properly when limited by MAX_FRAMES
514
+ ([MAX_FRAMES, cfp_count].min - 1).downto(0).each do |count|
515
+ frame_info = get_ruby_frame(count)
516
+ frame_infos << frame_info if frame_info
517
+ end
518
+ frame_infos.reverse.each_with_index do |fi, i|
519
+ puts "[#{frame_infos.length - i}] #{fi[:callee]}(#{fi[:args]}) <- #{fi[:source_path_line]}"
520
+ end
521
+ end
522
+
523
+ end
524
+
525
+ def main
526
+ config = GDBRubyConfig.new(ARGV)
527
+ gdbruby = GDBRuby.new(config)
528
+ gdbruby.trace
529
+ end
530
+
531
+ main
532
+
533
+ # vim: set expandtab ts=2 sw=2 nowrap ft=ruby ff=unix :
@@ -0,0 +1,21 @@
1
+ #! /usr/bin/env gem build
2
+ # encoding: utf-8
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'gdbruby'
6
+ gem.version = File.read(File.expand_path('VERSION', File.dirname(__FILE__))).chomp
7
+ gem.authors = ["Tasuku SUENAGA"]
8
+ gem.email = ["tasuku-s-github@titech.ac"]
9
+ gem.homepage = "https://github.com/gunyarakun/gdbruby"
10
+ gem.summary = "gdbperl for Ruby"
11
+ gem.description = gem.summary
12
+ gem.licenses = ["New BSD"]
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_development_dependency "rake"
20
+ gem.add_development_dependency "rspec"
21
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ EXECUTABLE = File.join(File.dirname(__FILE__), '..', 'bin', 'gdbruby.rb')
5
+ TARGET = File.expand_path(File.join(File.dirname(__FILE__), 'target', 'call.rb'))
6
+
7
+ describe 'gdbruby' do
8
+ describe 'execute' do
9
+ RESULT = <<EOF
10
+ [13] sleep() <- #{TARGET}:42
11
+ [12] Module1::Class1#block in method6() <- #{TARGET}:42
12
+ [11] Module1::Class1#method7() <- #{TARGET}:47
13
+ [10] Module1::Class1#method6() <- #{TARGET}:40
14
+ [9] Module1::Class1#method5() <- #{TARGET}:35
15
+ [8] Module1::Class1#rescue in method4() <- #{TARGET}:26
16
+ [7] Module1::Class1#method4() <- #{TARGET}:23
17
+ [6] Module1::Class1#method3() <- (eval):1
18
+ [5] eval() <- #{TARGET}:17
19
+ [4] Module1::Class1#method3() <- #{TARGET}:17
20
+ [3] method2() <- #{TARGET}:12
21
+ [2] method1() <- #{TARGET}:7
22
+ [1] <main>() <- #{TARGET}:51
23
+ EOF
24
+
25
+ before(:all) do
26
+ @target_pid = Kernel.spawn(TARGET, :pgroup => true, :out => '/dev/null', :err => '/dev/null')
27
+ end
28
+
29
+ context 'With live process' do
30
+ before(:all) do
31
+ @output = `#{EXECUTABLE} #{@target_pid}`
32
+ end
33
+
34
+ it 'should work' do
35
+ @output =~ /ruby_backtrace:\n(.+)\z/m
36
+ ruby_backtrace = $1
37
+ expect(ruby_backtrace).to eq(RESULT)
38
+ end
39
+ end
40
+
41
+ gcore_path = `which gcore`.chomp!
42
+ if File.executable?(gcore_path)
43
+ context 'With core file' do
44
+ before(:all) do
45
+ @core_path = Tempfile.new('core')
46
+ puts "target_pid: #{@target_pid}"
47
+ system('gcore', '-o', @core_path.path, @target_pid.to_s)
48
+ @output = `#{EXECUTABLE} #{@target_pid}`
49
+ end
50
+
51
+ it 'should work' do
52
+ @output =~ /ruby_backtrace:\n(.+)\z/m
53
+ ruby_backtrace = $1
54
+ expect(ruby_backtrace).to eq(RESULT)
55
+ end
56
+
57
+ after(:all) do
58
+ @core_path.close!
59
+ end
60
+ end
61
+ end
62
+
63
+ after(:all) do
64
+ Process.kill('KILL', @target_pid)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+
3
+ RSpec.configure do |config|
4
+ config.expect_with :rspec do |c|
5
+ c.syntax = :expect
6
+ end
7
+ end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set expandtab ts=2 sw=2 nowrap ft=ruby ff=unix : */
3
+
4
+ puts Process.pid
5
+
6
+ def method1(*args)
7
+ Module1::method2(*args)
8
+ end
9
+
10
+ module Module1
11
+ def self.method2(*args)
12
+ Class1::method3(*args)
13
+ end
14
+
15
+ class Class1
16
+ def self.method3(*args)
17
+ eval('method4(*args)')
18
+ end
19
+ end
20
+ end
21
+
22
+ def method4(*args)
23
+ begin
24
+ raise
25
+ rescue
26
+ method5(*args)
27
+ end
28
+ end
29
+
30
+ def method5(*args)
31
+ begin
32
+ raise
33
+ rescue
34
+ ensure
35
+ method6(*args)
36
+ end
37
+ end
38
+
39
+ def method6(*args)
40
+ method7(*args) do |*args|
41
+ p args
42
+ sleep(100)
43
+ end
44
+ end
45
+
46
+ def method7(*args)
47
+ yield *args
48
+ end
49
+
50
+ # TODO: test Struct, Bignum, File, Data, Match, Complex, Rational
51
+ method1(
52
+ nil,
53
+ true,
54
+ false,
55
+ :symbol,
56
+ 1,
57
+ Module1::Class1.new,
58
+ Module1::Class1,
59
+ Module1,
60
+ 1.0,
61
+ 'string',
62
+ /regex/,
63
+ ['array'],
64
+ { :hash => :value },
65
+ )
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gdbruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tasuku SUENAGA
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: gdbperl for Ruby
42
+ email:
43
+ - tasuku-s-github@titech.ac
44
+ executables:
45
+ - gdbruby.rb
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - .travis.yml
51
+ - Gemfile
52
+ - README.md
53
+ - Rakefile
54
+ - VERSION
55
+ - bin/gdbruby.rb
56
+ - gdbruby.gemspec
57
+ - spec/gdbruby_spec.rb
58
+ - spec/spec_helper.rb
59
+ - spec/target/call.rb
60
+ homepage: https://github.com/gunyarakun/gdbruby
61
+ licenses:
62
+ - New BSD
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: gdbperl for Ruby
84
+ test_files:
85
+ - spec/gdbruby_spec.rb
86
+ - spec/spec_helper.rb
87
+ - spec/target/call.rb