| ############################################################################### |
| # 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 'common/Logger' |
| |
| require 'dbgp/Command' |
| require 'dbgp/Response' |
| require 'dbgp/BreakpointElement' |
| require 'dbgp/StackLevelElement' |
| require 'dbgp/PropertyElement' |
| |
| require 'dbgp/SourceManager' |
| require 'dbgp/Utils' |
| |
| require 'debugger/FeatureManager' |
| require 'debugger/BreakpointContracts' |
| require 'debugger/Exceptions' |
| |
| module XoredDebugger |
| class CommandHandler |
| include Logger |
| include XoredDebuggerUtils |
| |
| def initialize(thread_manager, thread) |
| @debugger = thread_manager.debugger |
| @thread = thread |
| @context = @debugger.thread_context(thread) |
| @feature_manager = @debugger.feature_manager |
| @breakpoint_manager = @debugger.breakpoint_manager |
| @capture_manager = thread_manager.capture_manager |
| end |
| |
| # Handles DBGP command. Returns Responce to be sent to client, |
| # or nil if no responce required |
| def handle(command) |
| begin |
| log('Command: ' + command.name) |
| check_command_arguments(command, '-i') |
| self.send('handle_' + command.name, command) |
| |
| rescue SystemExit |
| raise $! |
| |
| rescue InvalidContextError |
| err_invalid_context(command) |
| |
| rescue BreakpointCouldNotBeSetError |
| err_could_not_set_breakpoint(command) |
| |
| rescue NoSuchBreakpointError |
| err_no_such_breakpoint(command) |
| |
| rescue BreakpointTypeNotSupportedError |
| err_breakpoint_type_not_supported(command) |
| |
| rescue OperationNotAvailableError |
| err_command_not_available(command) |
| |
| rescue ArgumentError |
| err_invalid_option(command) |
| |
| rescue NotImplementedError |
| err_unimplemented_command(command) |
| |
| rescue Exception |
| logException($!, 'in command handler:') |
| err_parse_error(command) |
| end |
| end |
| |
| protected |
| # Status |
| def handle_status(command) |
| response = Response.new(command) |
| response.add_attribute('status', @context.status) |
| response.add_attribute('reason', 'ok') |
| return response |
| end |
| |
| # Options and Configuration |
| def handle_feature_get(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| |
| check_command_arguments(command, '-n') |
| name = command.arg('-n') |
| supported = @feature_manager.supported?(name) |
| |
| response = Response.new(command) |
| response.add_attribute('feature_name', name) |
| response.add_attribute('supported', (supported ? 1:0)) |
| if (supported) |
| value = @feature_manager.get(name).to_s |
| response.set_data(value) |
| end |
| return response |
| end |
| |
| def handle_feature_set(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| |
| check_command_arguments(command, '-n', '-v') |
| name = command.arg('-n') |
| value = command.arg('-v') |
| |
| changed = @feature_manager.set(name, value) # Check types!!! (string or int) |
| response = Response.new(command) |
| response.add_attribute('feature_name', name) |
| response.add_attribute('success', (changed ? 1:0)) |
| return response |
| end |
| |
| |
| # Continuation Commands |
| def handle_run(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| @context.run |
| nil |
| end |
| |
| def handle_step_into(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| @context.step_into |
| nil |
| end |
| |
| def handle_step_over(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| @context.step_over |
| nil |
| end |
| |
| def handle_step_out(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| @context.step_out |
| nil |
| end |
| |
| def handle_stop(command) |
| @context.stop |
| response = Response.new(command) |
| response.add_attribute('status', @context.status) |
| response.add_attribute('reason', 'ok') |
| return response |
| end |
| |
| def handle_detach(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| |
| #breakpoints |
| def handle_breakpoint_set(command) |
| check_command_arguments(command, '-t') |
| type = command.arg('-t') |
| state = command.arg_with_default('-s', 'enabled') == 'enabled' ? true : false |
| hit_value = command.arg_with_default('-h', '0').to_i |
| hit_condition = command.arg_with_default('-o', '>=') |
| temporary = command.arg_with_default('-r', '0').to_i > 0 |
| expression = command.data |
| |
| bp = case type |
| when 'line' |
| check_command_arguments(command, '-f', '-n') |
| file = File.expand_path(uri_to_path(command.arg('-f'))) |
| line = command.arg('-n').to_i |
| @breakpoint_manager.add_line_breakpoint(file, line, temporary) |
| |
| when 'exception' |
| check_command_arguments(command, '-x') |
| exception = command.arg('-x') |
| @breakpoint_manager.add_exception_breakpoint(exception, temporary) |
| |
| when 'conditional' |
| #check_command_arguments(command, '-f', '--') |
| # TODO: add support for conditional breakpoints |
| #raise BreakpointTypeNotSupportedError |
| check_command_arguments(command, '-f', '-n') |
| file = File.expand_path(uri_to_path(command.arg('-f'))) |
| line = command.arg('-n').to_i |
| @breakpoint_manager.add_line_breakpoint(file, line, temporary) |
| |
| when 'watch' |
| check_command_arguments(command, '--') |
| # TODO: add support for watch breakpoints |
| raise BreakpointTypeNotSupportedError |
| |
| when 'call' |
| check_command_arguments(command, '-m') |
| # TODO: add support for call breakpoints |
| raise BreakpointTypeNotSupportedError |
| |
| when 'return' |
| check_command_arguments(command, '-m') |
| # TODO: add support for return breakpoints |
| raise BreakpointTypeNotSupportedError |
| |
| else |
| raise BreakpointTypeNotSupportedError |
| end |
| |
| bp.state = state |
| bp.expression = expression unless expression.nil? |
| bp.hit_condition = hit_condition |
| bp.hit_value = hit_value |
| |
| response = Response.new(command) |
| response.add_attribute('state', (state ? 'enabled':'disabled')) |
| response.add_attribute('id', bp.breakpoint_id) |
| return response |
| end |
| |
| def handle_breakpoint_get(command) |
| check_command_arguments(command, '-d') |
| id = command.arg('-d').to_i |
| bp = @breakpoint_manager.breakpoint(id) |
| |
| response = Response.new(command) |
| response.set_data(BreakpointElement.new(bp)) |
| return response |
| end |
| |
| def handle_breakpoint_update(command) |
| check_command_arguments(command, '-d') |
| id = command.arg('-d').to_i |
| bp = @breakpoint_manager.breakpoint(id) |
| |
| state = command.arg('-s') |
| lineno = command.arg('-n') |
| hit_value = command.arg('-h') |
| hit_condition = command.arg('-o') |
| expression = command.data |
| |
| modified = false |
| unless state.nil? |
| bp.state = (state == 'enabled' ? true : false) |
| modified = true |
| end |
| |
| if (!lineno.nil? && (bp.is_a? LineBreakpointContract)) |
| bp.lineno = lineno.to_i |
| modified = true |
| end |
| |
| unless hit_value.nil? |
| bp.hit_value = hit_value |
| modified = true |
| end |
| |
| unless hit_condition.nil? |
| bp.hit_condition = hit_condition |
| modified = true |
| end |
| |
| unless expression.nil? |
| bp.expression = expression |
| modified = true |
| end |
| |
| |
| response = Response.new(command) |
| unless modified |
| response.set_error(3) |
| end |
| return response |
| end |
| |
| def handle_breakpoint_remove(command) |
| check_command_arguments(command, '-d') |
| id = command.arg('-d').to_i |
| @breakpoint_manager.remove_breakpoint(id) |
| return Response.new(command) |
| end |
| |
| def handle_breakpoint_list(command) |
| xml = '' |
| @breakpoint_manager.breakpoints.each do |bp| |
| xml += BreakpointElement.new(bp).to_xml |
| end |
| |
| response = Response.new(command) |
| unless xml.empty? |
| response.set_data(xml) |
| end |
| return response |
| end |
| |
| |
| # Stack commands |
| def handle_stack_depth(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| response = Response.new(command) |
| response.add_attribute('depth', get_stack_depth(@context)) |
| return response |
| end |
| |
| def handle_stack_get(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| response = Response.new(command) |
| |
| depth = command.arg('-d') |
| if (!depth.nil?) |
| level = @context.stack_frame(depth.to_i) |
| response.set_data(StackLevelElement.new(depth.to_i, level)) |
| else |
| xml = '' |
| get_stack_depth(@context).times { |i| |
| level = @context.stack_frame(i) |
| xml += StackLevelElement.new(i, level).to_xml |
| } |
| response.set_data(xml) |
| end |
| return response |
| end |
| |
| # Context commands |
| LOCAL_CONTEXT_ID = 0 |
| GLOBAL_CONTEXT_ID = 1 |
| CLASS_CONTEXT_ID = 2 |
| CONTEXT_NAMES = [['Local', LOCAL_CONTEXT_ID], |
| ['Global', GLOBAL_CONTEXT_ID], |
| ['Class', CLASS_CONTEXT_ID]] |
| |
| def handle_context_names(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| data = CONTEXT_NAMES.collect { |name, id| sprintf('<context name="%s" id="%d"/>', name, id)} |
| |
| response = Response.new(command) |
| response.set_data(data.join("\n")) |
| return response |
| end |
| |
| def handle_context_get(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| depth = command.arg_with_default('-d', '0').to_i |
| context_id = command.arg_with_default('-c', '0').to_i |
| |
| def make_props(exp, d) |
| vars = @context.eval(exp, d) |
| pagesize = @feature_manager.get('max_children').to_i |
| props = [] |
| vars.each { |var| |
| # these variables give deprecation warnings in 1.9+ |
| # take advantage of the fact that they are strings in 1.8 |
| # and only skip them on newer ruby versions |
| next if [:$KCODE,:$-K,:$=].include?(var) |
| real_var = @context.eval(var, d) |
| props << PropertyElement.new(real_var, var, pagesize, 0) |
| } |
| props |
| end |
| |
| properties = [] |
| |
| response = Response.new(command) |
| response.add_attribute('context', context_id) |
| |
| case context_id |
| # Local variables |
| when LOCAL_CONTEXT_ID |
| properties += make_props('local_variables', depth) |
| |
| # TODO: correct this later |
| self_var = @context.eval('self', depth) |
| unless self_var.nil? |
| properties << PropertyElement.new(self_var, 'self') |
| end |
| |
| # Global variables |
| when GLOBAL_CONTEXT_ID |
| properties += make_props('global_variables', depth) |
| |
| # Class variables |
| when CLASS_CONTEXT_ID |
| properties += make_props('instance_variables', depth) |
| properties += make_props('self.class.class_variables', depth) |
| |
| else |
| raise InvalidContextError |
| end |
| |
| xml = '' |
| properties.each { |property| |
| xml += property.to_xml |
| } |
| response.set_data(xml) |
| return response |
| end |
| |
| |
| # Common Data Types |
| def handle_typemap_get(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| response = Response.new(command) |
| response.add_attribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance') |
| response.add_attribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema') |
| # TODO: Add ruby type mappings to response |
| return response |
| end |
| |
| |
| # Properties, variables and values |
| def handle_property_get(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| check_command_arguments(command, '-n') |
| name = command.arg('-n') |
| depth = command.arg_with_default('-d', '0').to_i |
| page = command.arg_with_default('-p', '0').to_i |
| pagesize = @feature_manager.get('max_children').to_i |
| |
| response = Response.new(command) |
| begin |
| cmd = create_property_eval_command(name) |
| property = PropertyElement.new(@context.eval(cmd, depth), name, pagesize, page) |
| response.set_data(property) |
| |
| rescue OperationNotAvailableError |
| raise $! |
| |
| rescue Exception |
| logException($!, 'in property_get:') |
| response.set_error(300) |
| end |
| return response |
| end |
| |
| def handle_property_set(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| check_command_arguments(command, '-n', '--') |
| name = command.arg('-n') |
| depth = command.arg_with_default('-d', '0').to_i |
| value = command.data |
| |
| response = Response.new(command) |
| begin |
| cmd = create_property_set_command(name, value) |
| property = PropertyElement.new(@context.eval(cmd, depth), name) |
| response.set_data(property) |
| response.add_attribute('success', 1) |
| |
| rescue OperationNotAvailableError |
| raise $! |
| |
| rescue Exception |
| response.set_error(300) |
| response.add_attribute('success', 0) |
| end |
| return response |
| end |
| |
| def handle_property_value(command) |
| raise OperationNotAvailableError unless @context.suspended? |
| check_command_arguments(command, '-n') |
| name = command.arg('-n') |
| depth = command.arg_with_default('-d', '0').to_i |
| |
| response = Response.new(command) |
| begin |
| cmd = create_property_eval_command(name) |
| value = @context.eval(cmd, depth).inspect |
| response.set_data(value, true) |
| response.add_attribute('encoding', 'base64') |
| # TODO: What size attribute means? |
| # response.add_attribute('size', ??? ) |
| rescue OperationNotAvailableError |
| raise $! |
| |
| rescue Exception |
| response.set_error(300) |
| end |
| return response |
| end |
| |
| # Source |
| def handle_source(command) |
| check_command_arguments(command, '-f') |
| path = uri_to_path(command.arg('-f')) |
| lines = SourceManager.instance.source_for(path) |
| |
| response = Response.new(command) |
| if (lines.nil?) |
| # error: can not open file |
| response.set_error(100) |
| else |
| response.add_attribute('success', 1) |
| response.set_data(lines.join, true) |
| end |
| return response |
| end |
| |
| |
| # Stdout, stderr redirection |
| def handle_stdout(command) |
| check_command_arguments(command, '-c') |
| state = case command.arg('-c').to_i |
| when 0 |
| state = CaptureManager::DISABLE |
| when 1 |
| state = CaptureManager::COPY |
| when 2 |
| state = CaptureManager::REDIRECT |
| else |
| raise ArgumentError |
| end |
| |
| @capture_manager.stdout_capturer.state = state |
| response = Response.new(command) |
| response.add_attribute('success', 1) |
| return response |
| end |
| |
| def handle_stderr(command) |
| check_command_arguments(command, '-c') |
| state = case command.arg('-c').to_i |
| when 0 |
| state = CaptureManager::DISABLE |
| when 1 |
| state = CaptureManager::COPY |
| when 2 |
| state = CaptureManager::REDIRECT |
| else |
| raise ArgumentError |
| end |
| |
| @capture_manager.stderr_capturer.state = state |
| response = Response.new(command) |
| response.add_attribute('success', 1) |
| return response |
| end |
| |
| |
| ###################################### |
| # Extended Commands |
| ###################################### |
| |
| def handle_stdin(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| |
| def handle_break(command) |
| raise OperationNotAvailableError if @context.suspended? |
| @context.suspend |
| response = Response.new(command) |
| response.add_attribute('status', @context.status) |
| response.add_attribute('reason', 'ok') |
| return response |
| end |
| |
| def handle_eval(command) |
| check_command_arguments(command, '--') |
| response = Response.new(command) |
| begin |
| expression = create_property_eval_command(command.data) |
| log('Evaluating: ' + expression) |
| result = calculate_in_another_thread(expression) |
| response.add_attribute('success', 1) |
| response.set_data(PropertyElement.new(result, expression)) |
| |
| rescue OperationNotAvailableError |
| raise $! |
| |
| rescue Exception |
| logException($!, 'in handle_eval') |
| response.add_attribute('success', 0) |
| response.set_error(206) |
| end |
| return response |
| end |
| |
| def handle_expr(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| def handle_exec(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| # Spawnpoint commands |
| def handle_spawnpoint_set(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| def handle_spawnpoint_get(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| def handle_spawnpoint_update(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| def handle_spawnpoint_remove(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| def handle_spawnpoint_list(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| # interact - Interactive Shell |
| def handle_interact(command) |
| raise NotImplementedError, 'Feature not implemented' |
| end |
| |
| # Error handling |
| def err_parse_error(command) |
| response = Response.new(command) |
| response.set_error(1) |
| return response |
| end |
| |
| def err_invalid_option(command) |
| response = Response.new(command) |
| response.set_error(3) |
| return response |
| end |
| |
| def err_unimplemented_command(command) |
| response = Response.new(command) |
| response.set_error(4) |
| return response |
| end |
| |
| def err_command_not_available(command) |
| response = Response.new(command) |
| response.set_error(5) |
| return response |
| end |
| |
| def err_could_not_set_breakpoint(command) |
| response = Response.new(command) |
| response.set_error(200) |
| return response |
| end |
| |
| def err_breakpoint_type_not_supported(command) |
| response = Response.new(command) |
| response.set_error(201) |
| return response |
| end |
| |
| def err_no_such_breakpoint(command) |
| response = Response.new(command) |
| response.set_error(205) |
| return response |
| end |
| |
| def err_invalid_context(command) |
| response = Response.new(command) |
| response.set_error(302) |
| return response |
| end |
| |
| def check_command_arguments(command, *args) |
| unless (command.is_a? Command) |
| raise ArgumentError, 'Invalid command was supplied' |
| end |
| args.each do |
| |name| |
| unless (!command.arg(name).nil? || (name == '--' && !command.data.nil?)) |
| raise ArgumentError, 'Required argument missed' |
| end |
| end |
| end |
| |
| def calculate_in_another_thread(expression) |
| @result = nil |
| thread = @debugger.create_debug_thread do |
| @result = @context.eval(expression, 0) |
| end |
| exited = thread.join(5) |
| if (thread.alive?) |
| thread.kill |
| raise Exception, 'Thread not exited' |
| end |
| @result |
| end |
| |
| def create_property_eval_command(name) |
| pos = name.index('::::') |
| if (pos.nil?) |
| return "#{name}" |
| end |
| |
| parent = name[0..pos-1] |
| var = name[pos+4..name.length] |
| |
| space_pos = var.index(' ') |
| tab_pos = var.index('\t') |
| |
| space_pos = var.length if space_pos.nil? |
| tab_pos = var.length if tab_pos.nil? |
| ws_pos = (space_pos < tab_pos) ? space_pos : tab_pos |
| |
| rest = var[ws_pos..var.length] |
| var = var[0..ws_pos - 1] |
| return parent + '.instance_eval(\'' + create_property_eval_command(var) + '\')' |
| + create_property_eval_command(rest) |
| end |
| |
| def create_property_set_command(name, value) |
| pos = name.index('::::') |
| if (pos.nil?) |
| return "#{name} = #{value}" |
| end |
| |
| parent = name[0..pos-1] |
| var = name[pos+4..name.length] |
| return parent + '.instance_eval(\'' + create_property_set_command(var, value) + '\')' |
| end |
| end # class CommandHandler |
| end # module XoredDebugger |