module Byebug
Summary¶ ↑
This is a singleton class allows controlling byebug. Use it to start/stop byebug, set/remove breakpoints, etc.
Main Container for all of Byebug's code
Main debugger's container module. Everything is defined under this module
Namespace for all of byebug's code
frozen_string_literal: true
Remote debugging functionality.
TODO: Refactor & add tests
frozen_string_literal: true
Reopen main module to define the library version
Constants
- PORT
Port number used for remote debugging
- VERSION
Attributes
The actual port that the server is started at
If in remote mode, wait for the remote connection
Debugger's display expressions
Configuration file used for startup commands. Default value is .byebugrc
Running mode of the debugger. Can be either:
-
:attached => Attached to a running program through the `byebug` method.
-
:standalone => Started through `bin/byebug` script.
Public Class Methods
Adds a new exception to the catchpoints array.
static VALUE Add_catchpoint(VALUE self, VALUE value) { UNUSED(self); if (TYPE(value) != T_STRING) rb_raise(rb_eTypeError, "value of a catchpoint must be String"); rb_hash_aset(catchpoints, rb_str_dup(value), INT2FIX(0)); return value; }
Enters byebug right before (or right after if before is false) return events occur. Before entering byebug the init script is read.
# File lib/byebug/attacher.rb, line 9 def self.attach require 'byebug/core' unless started? self.mode = :attached start run_init_script end current_context.step_out(3, true) end
Returns an array of breakpoints.
static VALUE Breakpoints(VALUE self) { UNUSED(self); if (NIL_P(breakpoints)) breakpoints = rb_ary_new(); return breakpoints; }
Returns an array of catchpoints.
static VALUE Catchpoints(VALUE self) { UNUSED(self); return catchpoints; }
Returns an array of all contexts.
static VALUE Contexts(VALUE self) { volatile VALUE list; volatile VALUE new_list; VALUE context; threads_table_t *t_tbl; debug_context_t *dc; int i; UNUSED(self); check_started(); new_list = rb_ary_new(); list = rb_funcall(rb_cThread, rb_intern("list"), 0); for (i = 0; i < RARRAY_LENINT(list); i++) { VALUE thread = rb_ary_entry(list, i); thread_context_lookup(thread, &context); rb_ary_push(new_list, context); } Data_Get_Struct(threads, threads_table_t, t_tbl); st_clear(t_tbl->tbl); for (i = 0; i < RARRAY_LENINT(new_list); i++) { context = rb_ary_entry(new_list, i); Data_Get_Struct(context, debug_context_t, dc); st_insert(t_tbl->tbl, dc->thread, context); } return new_list; }
Returns the current context.
<i>Note:</i> Byebug.current_context.thread == Thread.current
static VALUE Current_context(VALUE self) { VALUE context; UNUSED(self); thread_context_lookup(rb_thread_current(), &context); return context; }
Same as Kernel#load but resets current context's frames.
stop
parameter forces byebug to stop at the first line of code
in file
static VALUE Debug_load(int argc, VALUE * argv, VALUE self) { VALUE file, stop, context; debug_context_t *dc; VALUE status = Qnil; int state = 0; UNUSED(self); if (rb_scan_args(argc, argv, "11", &file, &stop) == 1) stop = Qfalse; Start(self); context = Current_context(self); Data_Get_Struct(context, debug_context_t, dc); dc->calced_stack_size = 1; if (RTEST(stop)) dc->steps = 1; rb_load_protect(file, 0, &state); if (0 != state) { status = rb_errinfo(); reset_stepping_stop_points(dc); } return status; }
Saves information about the unhandled exception and gives a byebug prompt back to the user before program termination.
# File lib/byebug/core.rb, line 70 def self.handle_post_mortem return unless raised_exception context = raised_exception.__bb_context PostMortemProcessor.new(context).at_line end
Interrupts the current thread
# File lib/byebug/remote.rb, line 23 def interrupt current_context.interrupt end
# File lib/byebug/core.rb, line 55 def self.load_settings Dir.glob(File.expand_path('../settings/*.rb', __FILE__)).each do |file| require file end constants.grep(/[a-z]Setting/).map do |name| setting = const_get(name).new Byebug::Setting.settings[setting.to_sym] = setting end end
# File lib/byebug/remote.rb, line 100 def parse_host_and_port(host_port_spec) location = host_port_spec.split(':') location[1] ? [location[0], location[1].to_i] : ['localhost', location[0]] end
Sets post-moterm flag.
static VALUE Set_post_mortem(VALUE self, VALUE value) { UNUSED(self); post_mortem = RTEST(value) ? Qtrue : Qfalse; return value; }
Returns true
if post-mortem debugging is enabled.
static VALUE Post_mortem(VALUE self) { UNUSED(self); return post_mortem; }
Returns raised exception when in post_mortem mode.
static VALUE Raised_exception(VALUE self) { UNUSED(self); return raised_exception; }
The return value is the value of !Byebug.started? before issuing
the start
; That is, true
is returned, unless
byebug was previously started.
static VALUE Start(VALUE self) { if (IS_STARTED) return Qfalse; catchpoints = rb_hash_new(); threads = create_threads_table(); register_tracepoints(self); return Qtrue; }
Connects to the remote byebug
# File lib/byebug/remote.rb, line 75 def start_client(host = 'localhost', port = PORT) Context.interface = LocalInterface.new puts 'Connecting to byebug server...' socket = TCPSocket.new(host, port) puts 'Connected.' catch(:exit) do while (line = socket.gets) case line when /^PROMPT (.*)$/ input = Context.interface.read_command(Regexp.last_match[1]) throw :exit unless input socket.puts input when /^CONFIRM (.*)$/ input = Context.interface.confirm(Regexp.last_match[1]) throw :exit unless input socket.puts input else puts line end end end socket.close end
# File lib/byebug/remote.rb, line 56 def start_control(host = nil, ctrl_port = PORT + 1) return @actual_control_port if @control_thread server = TCPServer.new(host, ctrl_port) @actual_control_port = server.addr[1] @control_thread = DebugThread.new do while (session = server.accept) Context.interface = RemoteInterface.new(session) ControlProcessor.new(Byebug.current_context).process_commands end end @actual_control_port end
Starts a remote byebug
# File lib/byebug/remote.rb, line 30 def start_server(host = nil, port = PORT) return if @thread Context.interface = nil start start_control(host, port == 0 ? 0 : port + 1) yield if block_given? mutex = Mutex.new proceed = ConditionVariable.new server = TCPServer.new(host, port) self.actual_port = server.addr[1] @thread = DebugThread.new do while (session = server.accept) Context.interface = RemoteInterface.new(session) mutex.synchronize { proceed.signal } if wait_connection end end mutex.synchronize { proceed.wait(mutex) } if wait_connection end
Returns true
byebug is started.
static VALUE Started(VALUE self) { UNUSED(self); return IS_STARTED ? Qtrue : Qfalse; }
This method disables byebug. It returns true
if byebug was
already disabled, otherwise it returns false
.
static VALUE Stop(VALUE self) { UNUSED(self); if (IS_STARTED) { clear_tracepoints(self); breakpoints = Qnil; catchpoints = Qnil; return Qfalse; } return Qtrue; }
static VALUE Stoppable(VALUE self) { VALUE context; debug_context_t *dc; if (!IS_STARTED) return Qfalse; if (!NIL_P(breakpoints) && rb_funcall(breakpoints, idEmpty, 0) == Qfalse) return Qfalse; if (!NIL_P(catchpoints) && rb_funcall(catchpoints, idEmpty, 0) == Qfalse) return Qfalse; if (post_mortem == Qtrue) return Qfalse; context = Current_context(self); if (!NIL_P(context)) { Data_Get_Struct(context, debug_context_t, dc); if (dc->steps > 0) return Qfalse; } return Qtrue; }
Returns context of the thread passed as an argument.
static VALUE Thread_context(VALUE self, VALUE thread) { VALUE context; UNUSED(self); check_started(); thread_context_lookup(thread, &context); return context; }
Sets the global tracing flag.
static VALUE Set_tracing(VALUE self, VALUE value) { UNUSED(self); tracing = RTEST(value) ? Qtrue : Qfalse; return value; }
Returns true
if global tracing is enabled.
static VALUE Tracing(VALUE self) { UNUSED(self); return tracing; }
Sets the global verbose flag for TracePoint API events is enabled.
static VALUE Set_verbose(VALUE self, VALUE value) { UNUSED(self); verbose = RTEST(value) ? Qtrue : Qfalse; return value; }
Returns true
if global verbose flag for TracePoint API events
is enabled.
static VALUE Verbose(VALUE self) { UNUSED(self); return verbose; }
Public Instance Methods
Runs normal byebug initialization scripts.
Reads and executes the commands from init file (if any) in the current working directory. This is only done if the current directory is different from your home directory. Thus, you can have more than one init file, one generic in your home directory, and another, specific to the program you are debugging, in the directory where you invoke byebug.
# File lib/byebug/core.rb, line 49 def run_init_script run_rc_file(ENV['HOME']) run_rc_file(Dir.pwd) unless Dir.pwd == ENV['HOME'] end
Private Instance Methods
Runs a initialization script file
# File lib/byebug/core.rb, line 85 def run_rc_file(base_path) old_interface = Context.interface rc_file = File.expand_path(File.join(base_path, init_file)) return unless File.exist?(rc_file) Context.interface = ScriptInterface.new(rc_file) ScriptProcessor.new(nil).process_commands ensure Context.interface = old_interface end