blob: 9e66e5a024a8cde335ad9aa98711835564fb63c5 [file] [log] [blame]
###############################################################################
# Copyright (c) 2005, 2007 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
###############################################################################
require 'pathname'
THIS_PATH = File.dirname(__FILE__)
require THIS_PATH + '/properties'
require THIS_PATH + '/breakpoints'
require THIS_PATH + '/stack'
require THIS_PATH + '/logger'
#
# uri -> path
#
def uri_to_path(uri)
# File.expand_path
CGI.unescape(uri).sub!('file:///', '')
end
#
# path -> uri
#
def path_to_uri(path)
'file:///' + CGI.escape(path).gsub('+', '%20')
end
#
# Thread label
#
def get_thread_label(thread)
is_main = thread == Thread.main
sprintf("Thread %s id=%d, priority=%d", is_main ? '(main)' : '', thread.object_id, thread.priority)
end
def normalize_path(path)
Pathname.new(path).expand_path.to_s
end
module XoredDebugger
class CommandHandler
include Logger
def initialize(debugger, thread, io_manager, key, script)
@debugger = debugger
@thread = thread
@io_manager = io_manager
@key = key
@script = script
@stop_sent = false
@command = nil
@context = debugger.thread_context(thread)
@command_handler = debugger.create_debug_thread do
log("New control thread started: " + Thread.current.inspect)
io_manager.send('init', init('app_test_id', @key, get_thread_label(@thread), @script))
catch (:done) do
loop do
begin
command_loop
rescue Exception
log('Exception in command loop:')
log("\tMessage: " + $!.message)
log("\tBacktrace: " + $!.backtrace.join("\n"))
end
end
end
log("Exiting control thread: " + Thread.current.inspect)
#
# unless @context.dead?
# # if thread is still alive and suspended then resume thread
# log("Resuming thread without control")
# if @context.suspended?
# @context.resume
# end
# end
end
end
# Init
def init(app_id, ide_key, thread, file_uri)
{ :app_id => app_id,
:ide_key => ide_key,
:session => 'session',
:thread => thread,
:parent => '',
:file_uri => file_uri }
end
# Status command
def status
{ :status => 'xxx_xxx',
:reason => 'ok' }
end
# Feature commands
def feature_get(name)
log("feature_get: #{name}")
supported = feature_manager.supported?(name)
{ :name => name,
:supported => supported,
:value => supported ? feature_manager.get(name).to_s : nil }
end
def feature_set(name, value)
log("feature_set: #{name} = #{value}")
feature_manager.set(name, value) # Check types!!! (string or int)
{ :name => name,
:success => 1 }
end
# Context commands
LOCAL_CONTEXT_ID = 0
GLOBAL_CONTEXT_ID = 1
CLASS_CONTEXT_ID = 2
def context_names(depth)
{ :contexts => [
{ :name => 'Local', :id => LOCAL_CONTEXT_ID },
{ :name => 'Global', :id => GLOBAL_CONTEXT_ID },
{ :name => 'Class', :id => CLASS_CONTEXT_ID }
] }
end
def context_get(depth, context_id)
contexts = [LOCAL_CONTEXT_ID, GLOBAL_CONTEXT_ID, CLASS_CONTEXT_ID]
unless context_id.nil?
contexts = [context_id]
end
def make_props(exp, d)
vars = stack_manager.eval(exp, d)
props = []
vars.each { |var|
real_var = stack_manager.eval(var, d)
props << make_property(var, real_var)
}
props
end
properties = []
# Local variables
if contexts.include?(LOCAL_CONTEXT_ID)
properties += make_props('local_variables', depth)
# TODO: correct this later
self_var = stack_manager.eval('self', depth)
unless self_var.nil?
properties << make_property('self', self_var)
end
end
# Global variables
if contexts.include?(GLOBAL_CONTEXT_ID)
properties += make_props('global_variables', depth)
end
# Class variables
if contexts.include?(CLASS_CONTEXT_ID)
properties += make_props('instance_variables', depth)
end
{ :properties => properties,
:context_id => context_id }
end
# Property commands
def property_get(name, depth, key)
# TODO: name, depth usage
obj = 'Error :('
begin
obj = ObjectSpace._id2ref(key)
if obj.nil?
obj = 'Object is nil'
end
rescue Exception
end
{ :property => make_property(name, obj) }
end
def property_set(name, depth, value)
success = true
begin
command = "#{name} = #{value.to_s}"
stack_manager.eval(command, depth)
rescue Exception
success = false
end
{ :success => success }
end
def property_value
#TODO:
{}
end
# Breakpoint commands
def breakpoint_set(info)
log('Setting breakpoint, info: ' + info.inspect)
id = breakpoint_manager.add(info)
{ :state => info.state,
:breakpoint_id => id }
end
def breakpoint_get(id)
info = breakpoint_manager[id]
breakpoint = {
:breakpoint_id => info.breakpoint_id, # Specially added by breakpoint manager
:state => info.state,
:hit_count => info.hit_count, # Specially added by breakpoint manager
:hit_value => info.hit_value,
:hit_condition => info.hit_condition,
}
# Type specific inforamtion
type = info.class
if type == LineBreakpointInfo
breakpoint[:type] = 'line'
breakpoint[:filename] = info.file
breakpoint[:lineno] = info.line
end
# Return value
{ :breakpoint => breakpoint }
end
def breakpoint_list
# TODO:
{}
end
def breakpoint_update(id, info)
breakpoint_manager.update(id, info)
{}
end
def breakpoint_remove(id)
breakpoint_manager.remove(id)
{}
end
# Stack commands
def stack_depth
{ :depth => stack_manager.depth }
end
def stack_get(depth = nil)
levels = []
stack_manager.depth.times { |i|
level = stack_manager[i]
where = level.method
if where.nil?
where = source_manager.line_at(level.file, level.line)
end
levels << { :level => i,
:type => 'source',
:filename => path_to_uri(normalize_path(level.file)),
:lineno => level.line,
:cmdbegin => '0:0',
:cmdend => '0:0',
:where => where }
}
unless depth.nil?
n = depth.to_i
{ :levels => [levels[n]] }
else
{ :levels => levels }
end
end
# Context commands
def stdin_data(data)
# TODO:
end
def stdin_configure(value)
# 0 - disable
# 1 - redirect
# TODO:
if value == 0
log("Disabling stdin")
elsif value == 1
log("Redirecting stdin")
end
{ :success => true }
end
def stdout_configure(value)
# 0 - default
# 1 - copy data
# 2 - redirection
# TODO:
log("Configure stdout: #{value}")
{ :success => true }
end
def stderr_configure(value)
# 0 - default
# 1 - copy data
# 2 - redirection
# TODO:
log("Configure stderr: #{value}")
{ :success => true }
end
def eval_handler(expression, depth)
log('Evaluating expression: ' + expression)
success = true
property = nil
begin
property = make_property(expression, stack_manager.eval(expression, depth))
rescue Exception
success = false
end
{ :success => success,
:property => property }
end
def dispatch_command(command)
log('Dispatching command: ' + command.name)
data = case command.name
# Status
when 'status': status
# FeatureCommands
when 'feature_get':
name = command.arg('-n')
feature_get(name)
when 'feature_set':
name = command.arg('-n')
value = command.arg('-v')
feature_set(name, value)
# Continuation commands
when 'run': run
when 'step_into': step_into
when 'step_over': step_over
when 'step_out': step_out
when 'stop': stop
when 'detach': detach
when 'break': break_cmd
# Breakpoint commands
when 'breakpoint_set'
type = command.arg('-t')
state = command.arg_with_default('-s', 'enabled') == 'enabled' ? true : false
temporary = command.arg_with_default('-r', false)
hit_value = command.arg_with_default('-h', 1).to_i
hit_condition = command.arg_with_default('-o', '>=')
expression = command.data
info = case type
when 'line'
file = File.expand_path(uri_to_path(command.arg('-f')))
line = command.arg('-n').to_i
log("\tfile: " + file.to_s)
log("\tline: " + line.to_s)
LineBreakpointInfo.new(file, line, state, temporary, expression, hit_value, hit_condition)
when 'call'
function = command.arg('-m')
# TODO:
nil
when 'return'
function = command.arg('-m')
# TODO:
nil
when 'exception'
exception = command.arg('-x')
log("\texception: " + file.to_s)
ExceptionBreakpointInfo.new(exception, state, temporary, expression, hit_value, hit_condition)
when 'conditional'
# TODO:
nil
when 'whatch'
# TODO:
nil
end
log('Breakpoint info: ' + info.to_s)
unless info.nil?
breakpoint_set(info)
end
when 'breakpoint_get'
id = command.arg('-d').to_i
breakpoint_get(id)
when 'breakpoint_update'
id = command.arg('-d').to_i
state = command.arg_with_default('-s', 'enabled') == 'enabled' ? true : false
temporary = command.arg_with_default('-r', false)
hit_value = command.arg_with_default('-h', 1).to_i
hit_condition = command.arg_with_default('-o', '>=')
expression = command.data
info = BreakpointInfo.new(state, temporary, expression, hit_value, hit_condition)
breakpoint_update(id, info)
when 'breakpoint_remove'
id = command.arg('-d').to_i
breakpoint_remove(id)
when 'breakpoint_list'
breakpoint_list
# Context commands
when 'context_names'
depth = command.arg_with_default('-d', '0').to_i
context_names(depth)
when 'context_get'
depth = command.arg_with_default('-d', '0').to_i
context_id = command.arg_with_default('-c', '0').to_i
context_get(depth, context_id)
# Property commands
when 'property_get'
name = command.arg('-n')
depth = command.arg_with_default('-d', '0').to_i
key = command.arg('-k').to_i
property_get(name, depth, key)
when 'property_set'
name = command.arg('-n')
depth = command.arg_with_default('-d', '0').to_i
value = command.data
property_set(name, depth, value)
when 'property_value'
property_value
# Stack commands
when 'stack_get'
depth = command.arg('-d')
stack_get(depth)
when 'stack_depth'
stack_depth
# Extended commands
when 'stdin'
value = command.arg('-c')
unless value.nil?
stdin_configure(value.to_i)
else
stdin_data(command.data)
end
when 'stdout'
value = command.arg('-c').to_i
stdout_configure(value)
when 'stderr'
value = command.arg('-c').to_i
stderr_configure(value)
when 'eval'
expression = command.data
depth = command.arg_with_default('-d', '0').to_i
eval_handler(expression, depth)
end
unless data.nil?
data[:id] = command.arg('-i')
end
#log('Dispatch completed, data: ' + data.inspect)
data
end
# Debugger ineraction
def send_break
unless @last_continuation_command.nil?
log('Sending responce to continuation command...')
map = { :status => 'break',
:reason => 'ok',
:id => @last_continuation_command.arg('-i') }
io_manager.send(@last_continuation_command.name, map)
@last_continuation_command = nil
end
end
def send_stopped
log(@last_continuation_command.inspect)
unless @last_continuation_command.nil?
map = { :status => 'stopped',
:reason => 'ok',
:id => @last_continuation_command.arg('-i') }
io_manager.send(@last_continuation_command.name, map)
@last_continuation_command = nil
end
end
# Continuation commands
def run
@context.resume
nil
end
def step_into
@context.step(1)
@context.resume
nil
end
def step_over
@context.step_over(1)
@context.resume
nil
end
def stop
@context.interrupt
nil
end
def detach
nil
end
def break_cmd
@context.suspend
send_break
# responce to break command
{ :success => 1 }
end
# Terminate function
def terminate
log('Thread termination: ' + @thread.inspect)
log("\tMain?: " + (@thread == Thread.main).to_s)
send_stopped
Thread.kill(@thread)
end
def terminated
log("Thread 'terminated' event: " + @thread.inspect)
send_stopped
@io_manager.close
end
def command_loop
loop do
log('Waiting command...')
@command = Command.new(io_manager.receive)
if ['run', 'step_into', 'step_over', 'step_out'].include?(@command.name)
@last_continuation_command = @command
end
log('Dispatching command...')
data = dispatch_command(@command)
if data.nil?
break
else
io_manager.send(@command.name, data)
end
end
end
# Managers
def capture_manager
@debugger.capture_manager
end
def breakpoint_manager
@debugger.breakpoint_manager
end
def feature_manager
@debugger.feature_manager
end
def source_manager
@debugger.source_manager
end
def stack_manager
raise NotImplementedError.new('You MUST implement this method in ancessors')
end
def io_manager
@io_manager
end
def at_breakpoint(context, breakpoint)
log(sprintf('=> at_breakpoint: %s:%s', breakpoint.file, breakpoint.line.to_s))
if breakpoint.state
@skipBreakpoint = false
else
@skipBreakpoint = true
end
end
def at_catchpoint(context, excpt)
end
def at_tracing(context, file, line)
end
def at_line(context, file, line)
log(sprintf('=> at_line: %s: %d', file, line))
unless context.stop_reason == :breakpoint && @skipBreakpoint
@context.suspend
send_break
end
end
end # class CommandHandler
end # module XoredDebugger