debug 0.2.0 → 1.0.0.alpha0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/debug/bp.vim ADDED
@@ -0,0 +1,68 @@
1
+ let g:rdb_bps = {}
2
+
3
+ function SET_BP()
4
+ let signed = sign_getplaced(bufname(), {'lnum': line('.')})
5
+ if empty(signed[0]['signs'])
6
+ call sign_place(0, '', 'signBP', bufname(), {'lnum': line('.')})
7
+ else
8
+ "echo signed[0]['signs']
9
+ call sign_unplace('', {'buffer': bufname(), 'id': signed[0]['signs'][0]['id']})
10
+ endif
11
+ endfunction
12
+
13
+ function UPDATE_BPS()
14
+ let signs = sign_getplaced(bufname())
15
+ let key = expand('%:p')
16
+
17
+ if empty(signs[0]['signs'])
18
+ let removed = remove(g:rdb_bps, key)
19
+ else
20
+ let g:rdb_bps[key] = signs[0]['signs']
21
+ endif
22
+ endfunction
23
+
24
+ function APPLY_BPS()
25
+ let key = expand('%:p')
26
+ if has_key(g:rdb_bps, key)
27
+ for b in g:rdb_bps[key]
28
+ call sign_place(0, '', 'signBP', bufname(), {'lnum': b['lnum']})
29
+ endfor
30
+ endif
31
+ endfunction
32
+
33
+ function WRITE_BPS()
34
+ call writefile([json_encode(g:rdb_bps)], '.rdb_breakpoints.json')
35
+ endfunction
36
+
37
+ " load
38
+ try
39
+ let json = readfile('.rdb_breakpoints.json')
40
+ let g:rdb_bps = json_decode(json[0])
41
+ " {"/full/path/to/file1": [{"lnum": 10}, ...], ...}
42
+ catch /Can't open/
43
+ let g:rdb_bps = {}
44
+ catch /Invalid arguments for function json_decode/
45
+ let g:rdb_bps = {}
46
+ endtry
47
+
48
+ sign define signBP text=BR
49
+
50
+ call APPLY_BPS()
51
+
52
+ autocmd BufReadPost * call APPLY_BPS()
53
+ autocmd BufUnload * call UPDATE_BPS()
54
+ autocmd VimLeave * call WRITE_BPS()
55
+
56
+ function! s:ruby_bp_settings() abort
57
+ echomsg "Type <Space> to toggle break points and <q> to quit"
58
+
59
+ if &readonly
60
+ nnoremap <silent> <buffer> <Space> :call SET_BP()<CR>
61
+ nnoremap <silent> <buffer> q :<C-u>quit<CR>
62
+ endif
63
+ endfunction
64
+
65
+ " autocmd FileType ruby call s:ruby_bp_settings()
66
+ autocmd BufEnter *.rb call s:ruby_bp_settings()
67
+
68
+ call s:ruby_bp_settings()
@@ -0,0 +1,97 @@
1
+ module DEBUGGER__
2
+ class LineBreakpoint
3
+ attr_reader :path, :line, :key
4
+
5
+ def initialize type, iseq, line, cond = nil, oneshot: false
6
+ @iseq = iseq
7
+ @path = iseq.path
8
+ @line = line
9
+ @type = type
10
+ @cond = cond
11
+ @oneshot = oneshot
12
+ @key = [@path, @line].freeze
13
+ setup
14
+ enable
15
+ end
16
+
17
+ def safe_eval b, expr
18
+ b.eval(expr)
19
+ rescue Exception => e
20
+ puts "[EVAL ERROR]"
21
+ puts " expr: #{expr}"
22
+ puts " err: #{e} (#{e.class})"
23
+ nil
24
+ end
25
+
26
+ def setup
27
+ if !@cond
28
+ @tp = TracePoint.new(@type) do |tp|
29
+ tp.disable if @oneshot
30
+ ThreadClient.current.on_breakpoint tp
31
+ end
32
+ else
33
+ @tp = TracePoint.new(@type) do |tp|
34
+ next unless safe_eval tp.binding, @cond
35
+ tp.disable if @oneshot
36
+ ThreadClient.current.on_breakpoint tp
37
+ end
38
+ end
39
+ end
40
+
41
+ def enable
42
+ if @type == :line
43
+ @tp.enable(target: @iseq, target_line: @line)
44
+ else
45
+ @tp.enable(target: @iseq)
46
+ end
47
+ rescue ArgumentError
48
+ puts @iseq.disasm # for debug
49
+ raise
50
+ end
51
+
52
+ def disable
53
+ @tp.disable
54
+ end
55
+
56
+ def to_s
57
+ "line bp #{@iseq.absolute_path}:#{@line} (#{@type})" +
58
+ if @cond
59
+ "if #{@cond}"
60
+ else
61
+ ""
62
+ end
63
+ end
64
+
65
+ def inspect
66
+ "<#{self.class.name} #{self.to_s}>"
67
+ end
68
+ end
69
+
70
+ class CatchBreakpoint
71
+ attr_reader :key
72
+
73
+ def initialize pat
74
+ @pat = pat
75
+ @tp = TracePoint.new(:raise){|tp|
76
+ exc = tp.raised_exception
77
+ exc.class.ancestors.each{|cls|
78
+ if pat === cls.name
79
+ puts "catch #{exc.class.inspect} by #{@pat.inspect}"
80
+ ThreadClient.current.on_suspend :catch
81
+ end
82
+ }
83
+ }
84
+ @tp.enable
85
+
86
+ @key = pat.freeze
87
+ end
88
+
89
+ def disable
90
+ @tp.disable
91
+ end
92
+
93
+ def to_s
94
+ "catch bp #{@pat.inspect}"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,135 @@
1
+ require 'socket'
2
+ require_relative 'config'
3
+
4
+ module DEBUGGER__
5
+ class Client
6
+ begin
7
+ require 'readline'
8
+ def readline
9
+ Readline.readline("\n(rdb) ", true)
10
+ end
11
+ rescue LoadError
12
+ def readline
13
+ print "\n(rdb) "
14
+ gets
15
+ end
16
+ end
17
+
18
+ def help
19
+ puts "-run -e attach # connect via UNIX Domain socket"
20
+ puts "-run -e attach name # connect via UNIX Domain socket with specified name"
21
+ puts "-run -e attach port_num # connect via TCP/IP socket with specified port"
22
+ puts "-run -e attach host port_num"
23
+ puts " # connect via TCP/IP socket with specified host and port"
24
+ end
25
+
26
+ def initialize argv
27
+ case argv.size
28
+ when 0
29
+ connect_unix
30
+ when 1
31
+ case arg = argv.shift
32
+ when /-h/, /--help/
33
+ help
34
+ exit
35
+ when /\A\d+\z/
36
+ connect_tcp nil, arg.to_i
37
+ else
38
+ connect_unix arg
39
+ end
40
+ when 2
41
+ connect_tcp argv[0], argv[1]
42
+ else
43
+ help
44
+ exit!
45
+ end
46
+ end
47
+
48
+ def cleanup_unix_domain_sockets
49
+ Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*') do |file|
50
+ if /(\d+)$/ =~ file
51
+ begin
52
+ Process.kill(0, $1.to_i)
53
+ rescue Errno::ESRCH
54
+ File.unlink(file)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def connect_unix name = nil
61
+ if name
62
+ if File.exist? name
63
+ @s = Socket.unix(name)
64
+ else
65
+ @s = Socket.unix(File.join(DEBUGGER__.unix_domain_socket_basedir, name))
66
+ end
67
+ else
68
+ cleanup_unix_domain_sockets
69
+ files = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
70
+ case files.size
71
+ when 0
72
+ $stderr.puts "There is no debug sessions."
73
+ exit
74
+ when 1
75
+ @s = Socket.unix(files.first)
76
+ else
77
+ $stderr.puts "Please select a debug session:"
78
+ files.each{|f|
79
+ $stderr.puts " #{File.basename(f)}"
80
+ }
81
+ exit
82
+ end
83
+ end
84
+ end
85
+
86
+ def connect_tcp host, port
87
+ @s = Socket.tcp(host, port)
88
+ end
89
+
90
+ def connect
91
+ trap(:SIGINT){
92
+ @s.puts "pause"
93
+ }
94
+
95
+ while line = @s.gets
96
+ # p line: line
97
+ case line
98
+ when /^out (.*)/
99
+ puts "#{$1}"
100
+ when /^input/
101
+ prev_trap = trap(:SIGINT, 'DEFAULT')
102
+
103
+ begin
104
+ line = readline
105
+ rescue Interrupt
106
+ retry
107
+ ensure
108
+ trap(:SIGINT, prev_trap)
109
+ end
110
+
111
+ line = (line || 'quit').strip
112
+ @s.puts "command #{line}"
113
+ when /^ask (.*)/
114
+ print $1
115
+ @s.puts "answer #{gets || ''}"
116
+ when /^quit/
117
+ raise 'quit'
118
+ else
119
+ puts "(unknown) #{line.inspect}"
120
+ end
121
+ end
122
+ rescue
123
+ STDERR.puts "disconnected (#{$!})"
124
+ exit
125
+ end
126
+ end
127
+ end
128
+
129
+ def connect argv = ARGV
130
+ DEBUGGER__::Client.new(argv).connect
131
+ end
132
+
133
+ if __FILE__ == $0
134
+ connect
135
+ end
@@ -0,0 +1,26 @@
1
+ module DEBUGGER__
2
+ def self.unix_domain_socket_basedir
3
+ case
4
+ when path = ENV['RUBY_DEBUG_SOCK_DIR']
5
+ when path = ENV['XDG_RUNTIME_DIR']
6
+ when home = ENV['HOME']
7
+ path = File.join(home, '.ruby-debug-sock')
8
+ unless File.exist?(path)
9
+ Dir.mkdir(path, 0700)
10
+ end
11
+ else
12
+ raise 'specify RUBY_DEBUG_SOCK_DIR environment variable for UNIX domain socket directory.'
13
+ end
14
+
15
+ path
16
+ end
17
+
18
+ def self.create_unix_domain_socket_name_prefix
19
+ user = ENV['USER'] || 'ruby-debug'
20
+ File.join(unix_domain_socket_basedir, "ruby-debug-#{user}")
21
+ end
22
+
23
+ def self.create_unix_domain_socket_name
24
+ create_unix_domain_socket_name_prefix + "-#{Process.pid}"
25
+ end
26
+ end
data/lib/debug/repl.rb ADDED
@@ -0,0 +1,67 @@
1
+ require_relative 'session'
2
+
3
+ module DEBUGGER__
4
+ class UI_Repl
5
+ def initialize
6
+ end
7
+
8
+ def quit n
9
+ exit n
10
+ end
11
+
12
+ def ask prompt
13
+ print prompt
14
+ (gets || '').strip
15
+ end
16
+
17
+ def puts str
18
+ case str
19
+ when Array
20
+ str.each{|line|
21
+ $stdout.puts line.chomp
22
+ }
23
+ when String
24
+ str.each_line{|line|
25
+ $stdout.puts line.chomp
26
+ }
27
+ when nil
28
+ $stdout.puts
29
+ end
30
+ end
31
+
32
+ begin
33
+ require 'readline'
34
+ def readline
35
+ setup_interrupt do
36
+ str = Readline.readline("\n(rdb) ", true)
37
+ (str || 'quit').strip
38
+ end
39
+ end
40
+ rescue LoadError
41
+ def readline
42
+ setup_interrupt do
43
+ print "\n(rdb) "
44
+ (gets || 'quit').strip
45
+ end
46
+ end
47
+ end
48
+
49
+ def setup_interrupt
50
+ current_thread = Thread.current # should be session_server thread
51
+
52
+ prev_handler = trap(:INT){
53
+ current_thread.raise Interrupt
54
+ }
55
+
56
+ yield
57
+ ensure
58
+ trap(:INT, prev_handler)
59
+ end
60
+ end
61
+
62
+ SESSION = Session.new(UI_Repl.new)
63
+
64
+ PREV_HANDLER = trap(:SIGINT){
65
+ ThreadClient.current.on_trap
66
+ }
67
+ end
@@ -0,0 +1,119 @@
1
+ require 'socket'
2
+ require_relative 'session'
3
+
4
+ module DEBUGGER__
5
+ class UI_Server
6
+ def initialize
7
+ @sock = nil
8
+ @client_addr = nil
9
+ @q_msg = Queue.new
10
+ @q_ans = Queue.new
11
+
12
+ @reader_thread = Thread.new do
13
+ accept do |server|
14
+ @sock = server
15
+ @q_msg = Queue.new
16
+ @q_ans = Queue.new
17
+
18
+ setup_interrupt do
19
+ pause
20
+
21
+ begin
22
+ while line = @sock.gets
23
+ case line
24
+ when /\Apause/
25
+ pause
26
+ when /\Acommand ?(.+)/
27
+ @q_msg << $1
28
+ when /\Aanswer (.*)/
29
+ @q_ans << $1
30
+ else
31
+ STDERR.puts "unsupported: #{line}"
32
+ exit!
33
+ end
34
+ end
35
+ end
36
+ end
37
+ ensure
38
+ @sock = nil
39
+ @q_msg.close
40
+ @q_ans.close
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ def setup_interrupt
47
+ prev_handler = trap(:SIGINT) do
48
+ # $stderr.puts "trapped SIGINT"
49
+ ThreadClient.current.on_trap
50
+
51
+ case prev_handler
52
+ when Proc
53
+ prev_handler.call
54
+ else
55
+ # ignore
56
+ end
57
+ end
58
+
59
+ yield
60
+ ensure
61
+ trap(:SIGINT, prev_handler)
62
+ end
63
+
64
+ def accept
65
+ raise "NOT IMPLEMENTED ERROR"
66
+ end
67
+
68
+ attr_reader :reader_thread
69
+
70
+ class NoRemoteError < Exception; end
71
+
72
+ def sock
73
+ yield @sock if @sock
74
+ rescue Errno::EPIPE
75
+ # ignore
76
+ end
77
+
78
+ def ask prompt
79
+ sock do |s|
80
+ s.puts "ask #{prompt}"
81
+ @q_ans.pop
82
+ end
83
+ end
84
+
85
+ def puts str
86
+ case str
87
+ when Array
88
+ enum = str.each
89
+ when String
90
+ enum = str.each_line
91
+ end
92
+
93
+ sock do |s|
94
+ enum.each do |line|
95
+ s.puts "out #{line.chomp}"
96
+ end
97
+ end
98
+ end
99
+
100
+ def readline
101
+ (sock do |s|
102
+ s.puts "input"
103
+ @q_msg.pop
104
+ end || 'continue').strip
105
+ end
106
+
107
+ def pause
108
+ $stderr.puts "DEBUG: pause request"
109
+ Process.kill(:SIGINT, Process.pid)
110
+ end
111
+
112
+ def quit n
113
+ # ignore n
114
+ sock do |s|
115
+ s.puts "quit"
116
+ end
117
+ end
118
+ end
119
+ end