Class: Trepan

Inherits:
Object
  • Object
show all
Defined in:
app/iseq.rb,
app/mock.rb,
app/util.rb,
app/frame.rb,
app/default.rb,
app/options.rb,
app/brkptmgr.rb,
app/complete.rb,
app/validate.rb,
app/cmd_parse.rb,
app/condition.rb,
app/breakpoint.rb,
lib/trepanning.rb

Overview

The Rubinius Trepan debugger.

This debugger is wired into the debugging APIs provided by Rubinius.

Defined Under Namespace

Modules: CmdParser, Complete, Condition, ISeq, Util, Validate Classes: Breakpoint, BreakpointMgr, DeferredBreakpoint, Frame, MockCore

Constant Summary collapse

CMD_INITFILE_BASE =
if RUBY_PLATFORM =~ /mswin/
  # Of course MS Windows has to be different
  HOME_DIR     =  (ENV['HOME'] ||
                   ENV['HOMEDRIVE'].to_s + ENV['HOMEPATH'].to_s).to_s
  'trepanx.ini'
else
  HOME_DIR = ENV['HOME'].to_s
  '.trepanxrc'
end
CMD_INITFILE =
File.join(HOME_DIR, CMD_INITFILE_BASE)
VERSION =
'0.2.1'
PROGRAM =
'trepanx'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(settings = {}) ⇒ Trepan

Create a new debugger object. The debugger starts up a thread which is where the command line interface executes from. Other threads that you wish to debug are told that their debugging thread is the debugger thread. This is how the debugger is handed control of execution.



47
48
49
# File 'lib/trepanning.rb', line 47

def initialize(opts={})
  @trace_filter = []
end

Instance Attribute Details

#breakpointObject

Returns the value of attribute breakpoint.



28
29
30
# File 'lib/trepanning.rb', line 28

def breakpoint
  @breakpoint
end

#breakpointsObject (readonly)

Returns the value of attribute breakpoints.



176
177
178
# File 'lib/trepanning.rb', line 176

def breakpoints
  @breakpoints
end

#completion_procObject (readonly)

GNU Readline completion proc



175
176
177
# File 'lib/trepanning.rb', line 175

def completion_proc
  @completion_proc
end

#current_frameObject (readonly)

Returns the value of attribute current_frame.



176
177
178
# File 'lib/trepanning.rb', line 176

def current_frame
  @current_frame
end

#debugee_threadObject (readonly)

Returns the value of attribute debugee_thread.



177
178
179
# File 'lib/trepanning.rb', line 177

def debugee_thread
  @debugee_thread
end

#deferred_breakpointsObject (readonly)

Returns the value of attribute deferred_breakpoints.



38
39
40
# File 'lib/trepanning.rb', line 38

def deferred_breakpoints
  @deferred_breakpoints
end

#intfObject

Breakpoint. The current breakpoint we are stopped at or nil if none.



30
31
32
# File 'lib/trepanning.rb', line 30

def intf
  @intf
end

#processorObject (readonly)

Returns the value of attribute processor.



39
40
41
# File 'lib/trepanning.rb', line 39

def processor
  @processor
end

#restart_argvObject

Array. The way the outside world interfaces with us. An array, so that interfaces can be stacked.



33
34
35
# File 'lib/trepanning.rb', line 33

def restart_argv
  @restart_argv
end

#settingsObject (readonly)

How to restart us, empty or nil. Note: restart_argv is typically C’s **argv, not Ruby’s ARGV. So restart_argv is $0.



37
38
39
# File 'lib/trepanning.rb', line 37

def settings
  @settings
end

#trace_filterObject

Procs/Methods we ignore.



3
4
5
# File 'app/mock.rb', line 3

def trace_filter
  @trace_filter
end

#variablesObject (readonly)

Returns the value of attribute variables.



176
177
178
# File 'lib/trepanning.rb', line 176

def variables
  @variables
end

#vm_locationsObject (readonly)

Returns the value of attribute vm_locations.



177
178
179
# File 'lib/trepanning.rb', line 177

def vm_locations
  @vm_locations
end

Class Method Details

.copy_default_optionsObject



19
20
21
22
23
24
25
26
27
28
29
# File 'app/options.rb', line 19

def self.copy_default_options
  options = {}
  DEFAULT_CMDLINE_SETTINGS.each do |key, value|
    begin
      options[key] = value.clone
    rescue TypeError
      options[key] = value
    end
  end
  options
end

.global(settings = {}) ⇒ Object



179
180
181
# File 'lib/trepanning.rb', line 179

def self.global(settings={})
  @global ||= new(settings)
end

.here(settings = {}) ⇒ Object

This is simplest API point. This starts up the debugger in the caller of this method to begin debugging.



191
192
193
# File 'lib/trepanning.rb', line 191

def self.here(settings={})
  global(settings).start(:offset => 1)
end

.setup_options(options, stdout = $stdout, stderr = $stderr) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/options.rb', line 31

def self.setup_options(options, stdout=$stdout, stderr=$stderr)
  OptionParser.new do |opts|
    opts.banner = <<EOB
#{show_version}
Usage: #{PROGRAM} [options] [[--] <script.rb> <script.rb parameters>]
EOB
    opts.separator ''
    opts.separator 'Options:'
    opts.on('--client',
            'Connect to out-of-process program') do
      if options[:server]
        stderr.puts '--server option previously given. --client option ignored.'
      else
        options[:client] = true
      end
    end
    opts.on('-c', '--command FILE', String,
            'Execute debugger commands from FILE') do |cmdfile|
      if File.readable?(cmdfile)
        options[:cmdfiles] << cmdfile
      elsif File.exists?(cmdfile)
          stderr.puts "Command file '#{cmdfile}' is not readable. Option ignored."
      else
        stderr.puts "Command file '#{cmdfile}' does not exist."
      end
    end
    opts.on('--cd DIR', String, 'Change current directory to DIR') do |dir|
      if File.directory?(dir)
        if File.executable?(dir)
          options[:chdir] = dir
        else
          stderr.puts "Can't cd to #{dir}. Option --cd ignored."
        end
      else
        stderr.puts "\"#{dir}\" is not a directory. Option --cd ignored."
      end
    end
    opts.on('--basename',
            'Show only file basename in file locations') do
      options[:basename] = true
    end
    opts.on('-d', '--debug', 'Set $DEBUG=true') do
      $DEBUG = true
    end
   opts.on('--[no-]highlight',
            'Use [no] syntax highlight output') do |v|
      options[:highlight] = ((v) ? :term : nil)
    end
    opts.on('-h', '--host NAME', String,
            'Host or IP used in TCP connections for --server or --client. ' +
            "Default is #{DEFAULT_SETTINGS[:host].inspect}.") do
      |name_or_ip|
      options[:host] = name_or_ip
    end
    opts.on('-I', '--include PATH', String, 'Add PATH to $LOAD_PATH') do
      |path|
      $LOAD_PATH.unshift(path)
    end
    opts.on('--nx',
            "Do not run debugger initialization file #{CMD_INITFILE}") do
      options[:nx] = true
    end
    opts.on('-p', '--port NUMBER', Integer,
            'Port number used in TCP connections for --server or --client. ' +
            "Default is #{DEFAULT_SETTINGS[:port]}.") do
      |num|
      options[:port] = num
    end
    opts.on('--[no-]readline',
            'Try [not] GNU Readline') do |v|
      options[:readline] = v
    end
    opts.on('-r', '--require SCRIPT', String,
            'Require the library, before executing your script') do |name|
      if name == 'debug'
        stderr.puts "ruby-debug is not compatible with Ruby's 'debug' library. This option is ignored."
      else
        require name
      end
    end
    opts.on('-s', '--server',
            'Set up for out-of-process debugging') do
      if options[:client]
        stderr.puts '--client option previously given. --server option ignored.'
      else
        options[:server] = true
      end
    end
    opts.on('-x', '--trace', 'Turn on line tracing') do
      options[:traceprint] = true
      options[:nx] = true
    end
    opts.separator ''
    opts.on_tail('-?', '--help', 'Show this message') do
      options[:help] = true
      stdout.puts opts
      exit
    end
    opts.on_tail('-v', '--version',
                 'print the version') do
      options[:version] = true
      stdout.puts show_version
    end
  end
end

.show_versionObject



15
16
17
# File 'app/options.rb', line 15

def self.show_version
  "#{PROGRAM}, version #{VERSION}"
end

.start(settings = {}) ⇒ Object



183
184
185
186
# File 'lib/trepanning.rb', line 183

def self.start(settings={})
  settings = {:immediate => false, :offset => 1}.merge(settings)
  global(settings).start(settings)
end

Instance Method Details

#add_command_file(cmdfile, opts = {}, stderr = $stderr) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/trepanning.rb', line 236

def add_command_file(cmdfile, opts={}, stderr=$stderr)
  unless File.readable?(cmdfile)
    if File.exists?(cmdfile)
      stderr.puts "Command file '#{cmdfile}' is not readable."
      return
    else
      stderr.puts "Command file '#{cmdfile}' does not exist."
      return
    end
  end
  @intf << Trepan::ScriptInterface.new(cmdfile, @output, opts)
end

#add_deferred_breakpoint(klass_name, which, name, line) ⇒ Object



343
344
345
346
347
348
349
# File 'lib/trepanning.rb', line 343

def add_deferred_breakpoint(klass_name, which, name, line)
  dbp = Trepan::DeferredBreakpoint.new(self, @current_frame, klass_name,
                                       which, name, line,
                                       @deferred_breakpoints)
  @deferred_breakpoints << dbp
  # @processor.brkpts << dbp
end

#add_startup_filesObject



249
250
251
252
253
254
255
256
257
258
# File 'lib/trepanning.rb', line 249

def add_startup_files()
  seen = {}
  cwd_initfile = File.join('.', Trepan::CMD_INITFILE_BASE)
  [cwd_initfile, Trepan::CMD_INITFILE].each do |initfile|
    full_initfile_path = File.expand_path(initfile)
    next if seen[full_initfile_path]
    add_command_file(full_initfile_path) if File.readable?(full_initfile_path)
    seen[full_initfile_path] = true
  end
end

#check_deferred_breakpointsObject



351
352
353
354
355
356
357
358
359
# File 'lib/trepanning.rb', line 351

def check_deferred_breakpoints
  unless @in_deferred_checking
    @in_deferred_checking = true
    @deferred_breakpoints.delete_if do |bp|
      bp.resolve!
    end
    @in_deferred_checking = false
  end
end

#completion_method(last_token, leading = Readline.line_buffer) ⇒ Object

The method is called when we want to do debugger command completion such as called from GNU Readline with <TAB>.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/trepanning.rb', line 132

def completion_method(last_token, leading=Readline.line_buffer)
  completion = @processor.complete(leading, last_token)
  if 1 == completion.size
    completion_token = completion[0]
    if last_token.end_with?(' ')
      if last_token.rstrip == completion_token
        # There is nothing more to complete
        []
      else
        []
      end
    else
      [completion_token]
    end
  else
    # We have multiple completions. Get the last token so that will
    # be presented as a list of completions.
    completion
  end
end

#debugger(settings = {:immediate => false}) ⇒ Object Also known as: start

Startup the debugger, skipping back offset frames. This lets you start the debugger straight into callers method.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/trepanning.rb', line 198

def debugger(settings = {:immediate => false})
  @settings = @settings.merge(settings)
  skip_loader if @settings[:skip_loader]
  spinup_thread
  @debugee_thread = @thread
  if @settings[:hide_level]
    @processor.hidelevels[@thread] = @settings[:hide_level]
  end

  process_cmdfile_setting(settings)

  # Feed info (breakpoint, debugged program thread, channel and backtrace
  # info) to the debugger thread
  locs = Rubinius::VM.backtrace(@settings[:offset] + 1, true)

  method = Rubinius::CompiledMethod.of_sender

  event = settings[:immediate] ? 'debugger-call' : 'start'
  bp = Breakpoint.new('<start>', method, 0, 0, 0, {:event => event} )
  channel = Rubinius::Channel.new

  @local_channel.send Rubinius::Tuple[bp, Thread.current, channel, locs]

  # wait for the debugger to release us
  channel.receive

  # Now that there is a debugger on the other end, set the debugged
  # program thread to call us when it hits a breakpoint.
  Thread.current.set_debugger_thread @thread
  self
end

#decode_oneObject



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/trepanning.rb', line 403

def decode_one
  ip = @current_frame.next_ip

  meth = @current_frame.method
  decoder = Rubinius::InstructionDecoder.new(meth.iseq)
  partial = decoder.decode_between(ip, ip+1)

  partial.each do |ins|
    op = ins.shift

    ins.each_index do |i|
      case op.args[i]
      when :literal
        ins[i] = meth.literals[ins[i]].inspect
      when :local
        if meth.local_names
          ins[i] = meth.local_names[ins[i]]
        end
      end
    end

    puts "=> ip #{ip} = #{op.opcode} #{ins.join(', ')}"
  end
end

#each_frame(start = 0) ⇒ Object



335
336
337
338
339
340
341
# File 'lib/trepanning.rb', line 335

def each_frame(start=0)
  start = start.number if start.kind_of?(Frame)

  start.upto(@vm_locations.size-1) do |idx|
    yield frame(idx)
  end
end

#frame(num) ⇒ Object



327
328
329
# File 'lib/trepanning.rb', line 327

def frame(num)
  @frames[num] ||= Frame.new(self, num, @vm_locations[num])
end

#listen(step_into = false) ⇒ Object

Stop and wait for a debuggee thread to send us info about stopping at a breakpoint.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/trepanning.rb', line 275

def listen(step_into=false)
  @breakpoint = nil
  while true
    if @channel
      if step_into
        @channel << :step
      else
        @channel << true
      end
    end

    # Wait for someone to stop
    @breakpoint, @debugee_thread, @channel, @vm_locations =
      @local_channel.receive

    # Uncache all frames since we stopped at a new place
    @frames = []

    set_frame(0)

    if @breakpoint
      # Some breakpoints are frame specific. Check for this.  hit!
      # also removes the breakpoint if it was temporary and hit.
      status = @breakpoint.hit!(@vm_locations.first.variables)
      if status
        break
      elsif @breakpoint.enabled? && status.nil?
        # A permanent breakpoint. Check the condition.
        break if @breakpoint.condition?(@current_frame.binding)
      end
    else
      @processor.remove_step_brkpt
      break
    end
  end

  event =
    if @breakpoint
      @breakpoint.event || 'brkpt'
    else
      # Evan assures me that the only way the breakpoint can be nil
      # is if we are stepping and enter a function.
      'step-call'
    end
  @processor.instance_variable_set('@event', event)

  if @variables[:show_bytecode]
    decode_one
  end

end

#process_cmdfile_setting(settings) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
# File 'lib/trepanning.rb', line 260

def process_cmdfile_setting(settings)
  settings[:cmdfiles].each do |item|
    cmdfile, opts =
      if item.kind_of?(Array)
        item
      else
        [item, {}]
      end
    add_command_file(cmdfile, opts)
  end if settings.member?(:cmdfiles)
end

#send_between(exec, start, fin) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/trepanning.rb', line 361

def send_between(exec, start, fin)
  ss   = Rubinius::InstructionSet.opcodes_map[:send_stack]
  sm   = Rubinius::InstructionSet.opcodes_map[:send_method]
  sb   = Rubinius::InstructionSet.opcodes_map[:send_stack_with_block]

  iseq = exec.iseq

  fin = iseq.size if fin < 0

  i = start
  while i < fin
    op = iseq[i]
    case op
    when ss, sm, sb
      return exec.literals[iseq[i + 1]]
    else
      op = Rubinius::InstructionSet[op]
      i += (op.arg_count + 1)
    end
  end

  return nil
end

#set_frame(num) ⇒ Object



331
332
333
# File 'lib/trepanning.rb', line 331

def set_frame(num)
  @current_frame = frame(num)
end

#show_code(line = @current_frame.line) ⇒ Object



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/trepanning.rb', line 385

def show_code(line=@current_frame.line)
  path = @current_frame.method.active_path
  str = @processor.line_at(path, line)
  unless str.nil?
    # if @variables[:highlight]
    #   fin = @current_frame.method.first_ip_on_line(line + 1)
    #   name = send_between(@current_frame.method, @current_frame.ip, fin)

    #   if name
    #     str = str.gsub name.to_s, "\033[0;4m#{name}\033[0m"
    #   end
    # end
    # info "#{line}: #{str}"
  else
    show_bytecode(line)
  end
end

#skip_loaderObject

HACK to skip over loader code. Until I find something better…



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/trepanning.rb', line 154

def skip_loader
  cmds =
    if @settings[:skip_loader] == :Xdebug
      ['continue Rubinius::CodeLoader.load_script',
       'next 6',
       # 'set kernelstep off',   # eventually would like 'on'
       'step',
      ]
    else
      ['next', 'next', 'next',
       # 'set kernelstep off',  # eventually would like 'on'
       'step', ]
    end

  input = Trepan::StringArrayInput.open(cmds)
  startup = Trepan::ScriptInterface.new('startup',
                                        Trepan::OutputNull.new(nil),
                                        :input => input)
  @intf << startup
end

#stop(settings = {}) ⇒ Object



231
232
233
234
# File 'lib/trepanning.rb', line 231

def stop(settings = {})
  @processor.finalize
  # Rubinius::VM.debug_channel = nil
end