Module: Trepan

Defined in:
app/util.rb,
app/run.rb,
app/frame.rb,
app/default.rb,
app/options.rb,
app/complete.rb,
app/cmd_parse.rb,
lib/trepanning.rb

Overview

Copyright © 2011 Rocky Bernstein <[email protected]>

Defined Under Namespace

Modules: CmdParser, Complete, Util Classes: Frame

Constant Summary collapse

DEFAULT_START_SETTINGS =

Default options to Trepan.start

{
  :init        => true,  # Set $0 and save ARGV?
  :post_mortem => false, # post-mortem debugging on uncaught exception?
  :tracing     => nil    # Debugger.tracing value. true/false resets,
                         # nil keeps the prior value
}
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
    'trepan8.ini'
else
    HOME_DIR = ENV['HOME'].to_s
    '.trepan8rc'
end
CMD_INITFILE =
File.join(HOME_DIR, CMD_INITFILE_BASE)
DEFAULT_SETTINGS =

Default settings for a Trepan class object

{
  :cmdproc_opts    => {},    # Default Trepan::CmdProcessor settings
  :core_opts       => {},    # Default Trepan::Core settings
  :delete_restore  => true,  # Delete restore profile after reading?
  :initial_dir     => nil,   # If --cd option was given, we save it here.
  :nx              => false, # Don't run user startup file (e.g. .trepanxrc)
  :offset          => 0,     # skipping back +offset+ frames. This lets you start
                             # the debugger straight into callers method.

  # Default values used only when 'server' or 'client'
  # (out-of-process debugging)
  :port            => 1955,
  :host            => 'localhost',

  :restart_argv    => [],
  :server          => false  # Out-of-process debugging?
}
VERSION =
'0.1.6'
PROGRAM =
'trepan8'
@@intf =
[Trepan::UserInterface.new(nil, nil, opts)]

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.annotateObject

gdb-style annotation mode. Used in GNU Emacs interface



68
69
70
# File 'lib/trepanning.rb', line 68

def annotate
  @annotate
end

.cmd_portObject (readonly)

Returns the value of attribute cmd_port.



78
79
80
# File 'lib/trepanning.rb', line 78

def cmd_port
  @cmd_port
end

.control_threadObject (readonly)

Returns the value of attribute control_thread.



78
79
80
# File 'lib/trepanning.rb', line 78

def control_thread
  @control_thread
end

.ctrl_portObject (readonly)

Returns the value of attribute ctrl_port.



78
79
80
# File 'lib/trepanning.rb', line 78

def ctrl_port
  @ctrl_port
end

.handlerObject

Returns the value of attribute handler.



63
64
65
# File 'lib/trepanning.rb', line 63

def handler
  @handler
end

.start_sentinalObject

If start_sentinal is set, it is a string to look for in caller() and is used to see if the call stack is truncated. Is also defined in app/default.rb



76
77
78
# File 'lib/trepanning.rb', line 76

def start_sentinal
  @start_sentinal
end

.threadObject (readonly)

Returns the value of attribute thread.



78
79
80
# File 'lib/trepanning.rb', line 78

def thread
  @thread
end

.wait_connectionObject

in remote mode, wait for the remote connection



71
72
73
# File 'lib/trepanning.rb', line 71

def wait_connection
  @wait_connection
end

Class Method Details

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



224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/trepanning.rb', line 224

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_startup_filesObject



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

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

.completion_method(last_token, leading = nil) ⇒ Object

FIXME: see if we can put this in app/complete. The method is called when we want to do debugger command completion such as called from GNU Readline with <TAB>.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/trepanning.rb', line 22

def self.completion_method(last_token, leading=nil)
  if leading.nil?
      if Readline.respond_to?(:line_buffer)
        completion =
          Trepan.handler.cmdproc.complete(Readline.line_buffer,
                                            last_token)
      else
        completion = Trepan.handler.cmdproc.complete(last_token, '')
      end
  else
    completion = Trepan.handler.cmdproc.complete(leading, last_token)
  end
  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

.copy_default_optionsObject



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

def 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

.debug_program(ruby_path, options) ⇒ Object

Given a Ruby interpreter and program we are to debug, debug it. The caller must ensure that ARGV is set up to remove any debugger arguments or things that the debugged program isn’t supposed to see. FIXME: Should we make ARGV an explicit parameter?



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'app/run.rb', line 13

def debug_program(ruby_path, options)
  # Make sure Ruby script syntax checks okay.
  # Otherwise we get a load message that looks like trepan8 has
  # a problem.

  output = ruby_syntax_errors(Trepan::PROG_SCRIPT.inspect)
  if output
    puts output
    exit $?.exitstatus
  end

  cmdproc = Debugger.handler.cmdproc
  %w(highlight basename traceprint).each do |opt|
    opt = opt.to_sym
    cmdproc.settings[opt] = options[opt]
  end
  cmdproc.unconditional_prehooks.insert_if_new(-1, *cmdproc.trace_hook) if
    options[:traceprint]

  # Record where we are we can know if the call stack has been
  # truncated or not.
  Trepan.start_sentinal=caller(0)[1]

  bt = Debugger.debug_load(Trepan::PROG_SCRIPT, options[:stop], false)
  if bt
    if options[:post_mortem]
      Debugger.handle_post_mortem(bt)
    else
      print bt.backtrace.map{|l| "\t#{l}"}.join("\n"), "\n"
      print "Uncaught exception: #{bt}\n"
    end
  end
end

.interface=(value) ⇒ Object

:nodoc:



80
81
82
# File 'lib/trepanning.rb', line 80

def interface=(value) # :nodoc:
  Debugger.handler.interface = value
end

.process_cmdfile_setting(settings) ⇒ Object



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

def process_cmdfile_setting(settings)
  p settings
  puts caller
  exit
  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

.ruby_syntax_errors(prog_script) ⇒ Object



71
72
73
74
75
76
77
# File 'app/run.rb', line 71

def ruby_syntax_errors(prog_script)
  output = `#{RbConfig.ruby} -c #{prog_script} 2>&1`
  if $?.exitstatus != 0 and RUBY_PLATFORM !~ /mswin/
    return output
  end
  return nil
end

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



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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/options.rb', line 33

def 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('--cport PORT', Integer, 'Port used for control commands') do
      |cport|
      options[:cport] = cport
    end
    opts.on('--[no-]highlight',
            'Use [no] syntax highlight output') do |v|
      options[:highlight] = ((v) ? :term : nil)
      if options[:highlight]
        begin
          require 'linecache'
        rescue LoadError
          require 'linecache19'
        end
        unless LineCache.respond_to?(:clear_file_format_cache)
          stderr.puts "Your version of LineCache doesn't support terminal highlight"
          options[:higlight] = false
        end
      end
    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('--keep-frame-binding', 'Keep frame bindings') do
      options[:frame_bind] = true
    end
    opts.on('-m', '--post-mortem', 'Activate post-mortem mode') do
      options[:post_mortem] = true
    end
    opts.on('--nx',
            "Do not run debugger initialization file #{CMD_INITFILE}") do
      options[:nx] = true
    end
    opts.on('--[no-]control', 'Start [not] control thread') do |v|
      options[:control] = v
    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-]quit', 'Do [not] quit when script finishes') do |v|
      options[:quit] = v
    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 "trepan8 is not compatible with Ruby's 'debug' library. This option is ignored."
      else
        require name
      end
    end
    opts.on('--[no-]rewrite-program',
            'Do not set $0 to the program being debugged') do |v|
      options[:rewrite_program] = v
    end
    opts.on('--[no-]stop', 'Do not stop when script is loaded') do |v|
      options[:stop] = v
    end
    opts.on('--script FILE', String, 'Name of the script file to run') do
      |script|
      options[:script] = script
      unless File.exists?(options[:script])
        stderr.puts "Script file '#{options[:script]}' is not found"
        exit 10
      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('-w', '--wait', 'Wait for a client connection; implies -s option') do
      options[:wait] = true
    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



17
18
19
# File 'app/options.rb', line 17

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

.start(options = {}, &block) ⇒ Object

Trepan.start(options) -> bool Trepan.start(options) { … } -> obj

If it’s called without a block it returns true, unless debugger was already started. If a block is given, it starts debugger and yields to block. When the block is finished executing it stops the debugger with Trepan.stop method.

If a block is given, it starts debugger and yields to block. When the block is finished executing it stops the debugger with Trepan.stop method. Inside the block you will probably want to have a call to Trepan.debugger. For example:

Trepan.start{debugger; foo}  # Stop inside of foo

Also, ruby-debug only allows one invocation of debugger at a time; nested Trepan.start’s have no effect and you can’t use this inside the debugger itself.

Note that if you want to stop debugger, you must call Trepan.stop as many time as you called Trepan.start method.

options is a hash used to set various debugging options. Set :init true if you want to save ARGV and some variables which make a debugger restart possible. Only the first time :init is set true will values get set. Since ARGV is saved, you should make sure it hasn’t been changed before the (first) call. Set :post_mortem true if you want to enter post-mortem debugging on an uncaught exception. Once post-mortem debugging is set, it can’t be unset.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/trepanning.rb', line 115

def start(options={}, &block)
  options = Trepan::DEFAULT_START_SETTINGS.merge(options)
  if options[:init]
    Trepan.const_set('ARGV', ARGV.clone) unless
      defined? Trepan::ARGV
    Trepan.const_set('PROG_SCRIPT', $0) unless
      defined? Trepan::PROG_SCRIPT
    Trepan.const_set('INITIAL_DIR', Dir.pwd) unless
      defined? Trepan::INITIAL_DIR
  end
  Trepan.tracing = options[:tracing] unless options[:tracing].nil?
  retval = Debugger.started? ? block && block.call(self) : Debugger.start_(&block)
  if options[:post_mortem]
    post_mortem
  end
  return retval
end

.start_client(host = 'localhost', port = PORT) ⇒ Object

Connects to the remote debugger



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

def start_client(host = 'localhost', port = PORT)
  require "socket"
  interface = Trepan::LocalInterface.new
  socket = TCPSocket.new(host, port)
  puts "Connected."

  catch(:exit) do
    while (line = socket.gets)
      case line
      when /^PROMPT (.*)$/
        input = interface.read_command($1)
        throw :exit unless input
        socket.puts input
      when /^CONFIRM (.*)$/
        input = interface.confirm($1)
        throw :exit unless input
        socket.puts input
      else
        print line
      end
    end
  end
  socket.close
end

.start_control(host = nil, ctrl_port = PORT + 1) ⇒ Object

:nodoc:



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/trepanning.rb', line 181

def start_control(host = nil, ctrl_port = PORT + 1) # :nodoc:
  raise "Debugger is not started" unless started?
  return @ctrl_port if defined?(@control_thread) && @control_thread
  server = TCPServer.new(host, ctrl_port)
  @ctrl_port = server.addr[1]
  @control_thread = Debugger::DebugThread.new do
    while (session = server.accept)
      interface = RemoteInterface.new(session)
      processor = ControlCommandProcessor.new(interface)
      processor.process_commands
    end
  end
  @ctrl_port
end

.start_remote(host = nil, port = PORT, post_mortem = false) ⇒ Object Also known as: start_server

Starts a remote debugger.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/trepanning.rb', line 140

def start_remote(host = nil, port = PORT, post_mortem = false)
  return if @thread
  return if started?

  self.interface = nil
  start
  self.post_mortem if post_mortem

  if port.kind_of?(Array)
    cmd_port, ctrl_port = port
  else
    cmd_port, ctrl_port = port, port + 1
  end

  ctrl_port = start_control(host, ctrl_port)

  yield if block_given?

  mutex = Mutex.new
  proceed = ConditionVariable.new

  server = TCPServer.new(host, cmd_port)
  @cmd_port = cmd_port = server.addr[1]
  @thread = Debugger::DebugThread.new do
    while (session = server.accept)
      self.interface = RemoteInterface.new(session)
      if wait_connection
        mutex.synchronize do
          proceed.signal
        end
      end
    end
  end
  if wait_connection
    mutex.synchronize do
      proceed.wait(mutex)
    end
  end
end

.started?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'lib/trepanning.rb', line 133

def started?
  Debugger.started?
end

.whence_file(prog_script) ⇒ Object

Do a shell-like path lookup for prog_script and return the results. If we can’t find anything return prog_script.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/run.rb', line 49

def whence_file(prog_script)
  if RbConfig::CONFIG['target_os'].start_with?('mingw')
    if (prog_script =~ /^[a-zA-Z][:]/)
      start = prog_script[2..2]
      if [File::ALT_SEPARATOR, File::SEPARATOR].member?(start)
        return prog_script
      end
    end
  end
  if prog_script.start_with?(File::SEPARATOR) || prog_script.start_with?('.')
    # Don't search since this name has path is explicitly absolute or
    # relative.
    return prog_script
  end
  for dirname in ENV['PATH'].split(File::PATH_SEPARATOR) do
    prog_script_try = File.join(dirname, prog_script)
    return prog_script_try if File.readable?(prog_script_try)
  end
  # Failure
  return prog_script
end