emonti-jdi_hook 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.rdoc +85 -0
- data/Rakefile +31 -0
- data/jdi_hook.gemspec +34 -0
- data/lib/jdi_hook.rb +166 -0
- data/lib/jdi_hook/base_debugger.rb +119 -0
- data/lib/jdi_hook/event_thread.rb +68 -0
- data/lib/jdi_hook/method_tracer.rb +95 -0
- data/lib/jdi_hook/stream_redirect_thread.rb +39 -0
- data/samples/base_test.rb +7 -0
- data/samples/meth_test.rb +17 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +87 -0
data/History.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
jdi_hook
|
2
|
+
by Eric Monti
|
3
|
+
http://github.com/emonti/jdi_hook
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
JdiHook is a ruby-scriptable Java debugger based on and around Sun's Java
|
8
|
+
Debugging Interface (JDI) API.
|
9
|
+
|
10
|
+
== FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
* JdiHook aims to provide a scriptable engine around Sun's Java Debugging
|
13
|
+
Interface (JDI) using JRuby. The uses of such an engine are intentionally
|
14
|
+
open-ended, but the initial rationale was the need for custom instrumentation
|
15
|
+
and dynamic analysis tools for Java applications with a reverse engineering
|
16
|
+
and vulnerability testing mindset.
|
17
|
+
|
18
|
+
* You already use JAD or JODE for class decompiling, but you need a quick and
|
19
|
+
painless way to observe call trees, hit traces, and other target behaviors
|
20
|
+
during runtime. Create bespoke Java runtime debugging scripts just like you
|
21
|
+
would using PyDbg or Ragweed on a native target.
|
22
|
+
|
23
|
+
== SYNOPSIS:
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'jdi_hook'
|
27
|
+
|
28
|
+
# Start a java target class. Kind of equivalent to running 'jdb HelloWorld'
|
29
|
+
vm = JdiHook.command_line_launch("HelloWorld")
|
30
|
+
|
31
|
+
# Instantiate and attach debugging event handler. MethodTracer is geared
|
32
|
+
# for attaching hooks to method entry and exit events.
|
33
|
+
dbg = JdiHook::MethodTracer.new vm, :redirect_stdio => true
|
34
|
+
|
35
|
+
# Define some handlers for Java method entry and exit events to dump
|
36
|
+
# some information about the method invocation. Event handlers for
|
37
|
+
# MethodTracer are supplied as Ruby Proc objects (or blocks if you will)
|
38
|
+
en_proc = lambda {|this, evt| puts " [*] " << this.notify_entry(evt.method) }
|
39
|
+
ex_proc = lambda {|this, evt| puts " [*] " << this.notify_exit(evt.method) }
|
40
|
+
|
41
|
+
# Configure some event hooks to fire on regex pattern matches by method name
|
42
|
+
dbg.meth_hooks = {
|
43
|
+
/\.main$/ => { :on_entry => en_proc, :on_exit => ex_proc },
|
44
|
+
/.*/ => { :on_entry => en_proc },
|
45
|
+
}
|
46
|
+
|
47
|
+
# "Continue" the target from the debugger
|
48
|
+
dbg.go
|
49
|
+
|
50
|
+
|
51
|
+
== REQUIREMENTS:
|
52
|
+
|
53
|
+
* A JRE (recommend Sun JDK version 1.6+) - http://java.sun.com/javase/downloads/
|
54
|
+
* jruby - http://jruby.org
|
55
|
+
|
56
|
+
== INSTALL:
|
57
|
+
|
58
|
+
* jruby -S gem sources -a http://gems.github.com # only have to do this once
|
59
|
+
* jruby -S gem install emonti-rbkb
|
60
|
+
|
61
|
+
|
62
|
+
== LICENSE:
|
63
|
+
|
64
|
+
(The MIT License)
|
65
|
+
|
66
|
+
Copyright (c) 2008 Eric Monti - Matasano Security
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
'Software'), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
82
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
83
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
84
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
85
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
begin
|
10
|
+
load 'tasks/setup.rb'
|
11
|
+
rescue LoadError
|
12
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_in_path 'lib'
|
17
|
+
require 'jdi_hook'
|
18
|
+
|
19
|
+
task :default => 'spec:run'
|
20
|
+
|
21
|
+
PROJ.name = 'jdi_hook'
|
22
|
+
PROJ.authors = 'Eric Monti'
|
23
|
+
PROJ.email = 'emonti@matasano.com'
|
24
|
+
PROJ.url = 'http://github.com/emonti/jdi_hook'
|
25
|
+
PROJ.version = JdiHook::VERSION
|
26
|
+
PROJ.rubyforge.name = 'jdi_hook'
|
27
|
+
PROJ.readme_file = 'README.rdoc'
|
28
|
+
|
29
|
+
PROJ.spec.opts << '--color'
|
30
|
+
|
31
|
+
# EOF
|
data/jdi_hook.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{jdi_hook}
|
5
|
+
s.version = "1.0.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Eric Monti"]
|
9
|
+
s.date = %q{2009-05-15}
|
10
|
+
s.description = %q{JdiHook is a ruby-scriptable Java debugger based on and around Sun's Java Debugging Interface (JDI) API.}
|
11
|
+
s.email = %q{emonti@matasano.com}
|
12
|
+
s.extra_rdoc_files = ["History.txt", "README.rdoc"]
|
13
|
+
s.files = ["History.txt", "README.rdoc", "Rakefile", "jdi_hook.gemspec", "lib/jdi_hook.rb", "lib/jdi_hook/base_debugger.rb", "lib/jdi_hook/event_thread.rb", "lib/jdi_hook/method_tracer.rb", "lib/jdi_hook/stream_redirect_thread.rb", "samples/base_test.rb", "samples/meth_test.rb", "tasks/ann.rake", "tasks/bones.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/notes.rake", "tasks/post_load.rake", "tasks/rdoc.rake", "tasks/rubyforge.rake", "tasks/setup.rb", "tasks/spec.rake", "tasks/svn.rake", "tasks/test.rake", "tasks/zentest.rake"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/emonti/jdi_hook}
|
16
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{jdi_hook}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{JdiHook is a ruby-scriptable Java debugger based on and around Sun's Java Debugging Interface (JDI) API}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_development_dependency(%q<bones>, [">= 2.5.0"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<bones>, [">= 2.5.0"])
|
30
|
+
end
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<bones>, [">= 2.5.0"])
|
33
|
+
end
|
34
|
+
end
|
data/lib/jdi_hook.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
|
2
|
+
module JdiHook
|
3
|
+
VERSION = '1.0.0'
|
4
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
5
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
6
|
+
|
7
|
+
|
8
|
+
include Java
|
9
|
+
|
10
|
+
include_class [
|
11
|
+
"com.sun.jdi.IncompatibleThreadStateException",
|
12
|
+
"com.sun.jdi.VMDisconnectedException",
|
13
|
+
"com.sun.jdi.InternalException",
|
14
|
+
"com.sun.jdi.event.ClassPrepareEvent",
|
15
|
+
"com.sun.jdi.event.MethodEntryEvent",
|
16
|
+
"com.sun.jdi.event.MethodExitEvent",
|
17
|
+
"com.sun.jdi.event.ModificationWatchpointEvent",
|
18
|
+
"com.sun.jdi.event.StepEvent",
|
19
|
+
"com.sun.jdi.event.ThreadDeathEvent",
|
20
|
+
"com.sun.jdi.event.VMDeathEvent",
|
21
|
+
"com.sun.jdi.event.VMDisconnectEvent",
|
22
|
+
"com.sun.jdi.event.VMStartEvent",
|
23
|
+
"com.sun.jdi.request.EventRequest",
|
24
|
+
"com.sun.jdi.request.StepRequest",
|
25
|
+
"java.io.InputStreamReader",
|
26
|
+
"java.lang.InterruptedException",
|
27
|
+
"java.util.List",
|
28
|
+
"java.util.Map",
|
29
|
+
]
|
30
|
+
|
31
|
+
## Class sugar methods
|
32
|
+
|
33
|
+
# Shorthand for com.sun.jdi.Bootstrap.virtualMachineManager()
|
34
|
+
def self.vm_mgr
|
35
|
+
com.sun.jdi.Bootstrap.virtualMachineManager()
|
36
|
+
end
|
37
|
+
|
38
|
+
# Shorthand for com.sun.jdi.Bootstrap.virtualMachineManager.allConnectors()
|
39
|
+
def self.vm_connectors
|
40
|
+
vm_mgr.allConnectors()
|
41
|
+
end
|
42
|
+
|
43
|
+
# Launches a target VM by running it with commandline arguments and
|
44
|
+
# attaches to it.
|
45
|
+
#
|
46
|
+
# Returns: instance of VirtualMachineImpl
|
47
|
+
#
|
48
|
+
# Arguments:
|
49
|
+
# main = String or Array command line for class target
|
50
|
+
# o = an optional Hash of parameters:
|
51
|
+
# :options = Additional options such as '-classic' (optional)
|
52
|
+
# :home = JAVA_HOME path (default: probably your JAVA_HOME)
|
53
|
+
# :suspend = whether to suspend the target on start (default: true)
|
54
|
+
# :vmexec = what to run through exec (default: java)
|
55
|
+
# :quote = the quote char for tokenizing args? (default: ")
|
56
|
+
def self.command_line_launch(main, o=nil)
|
57
|
+
o ||= {}
|
58
|
+
o[:main] = [*main].join(' ')
|
59
|
+
o[:options] = [*o[:options]].join(' ') if o[:options]
|
60
|
+
con, args = get_connector("com.sun.jdi.CommandLineLaunch", o)
|
61
|
+
return con.launch(args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Attaches to a running VM by process ID on the system.
|
65
|
+
#
|
66
|
+
# Note: Process attaching by pid only works if both the debugger and target
|
67
|
+
# VM are Java 1.6 or higher.
|
68
|
+
#
|
69
|
+
# Returns: instance of VirtualMachineImpl
|
70
|
+
#
|
71
|
+
# Arguments:
|
72
|
+
# pid = process ID to attach to
|
73
|
+
# o = an optional Hash of parameters:
|
74
|
+
# :timeout = connection timeout? (optional)
|
75
|
+
def self.process_attach(pid, o=nil)
|
76
|
+
o ||= {}
|
77
|
+
o[:pid] = pid.to_s
|
78
|
+
con, args = get_connector("com.sun.jdi.ProcessAttach", o)
|
79
|
+
return con.attach(args)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Attaches to a running VM by opening a TCP socket to the target
|
83
|
+
#
|
84
|
+
# Returns: instance of VirtualMachineImpl
|
85
|
+
#
|
86
|
+
# Arguments:
|
87
|
+
# port = port to connect to
|
88
|
+
# o = an optional Hash of parameters:
|
89
|
+
# :hostname = host to connect to (default = localhost)
|
90
|
+
# :timeout = connection timeout? (optional)
|
91
|
+
def self.socket_attach(port, o=nil)
|
92
|
+
o ||= {}
|
93
|
+
o[:port] = port.to_i
|
94
|
+
con, args = get_connector("com.sun.jdi.SocketAttach", o)
|
95
|
+
return con.attach(args)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Connects to a running VM by awaiting a connection via TCP socket
|
99
|
+
# the target initiates the connection to this listener.
|
100
|
+
#
|
101
|
+
# Returns: instance of VirtualMachineImpl
|
102
|
+
#
|
103
|
+
# Arguments:
|
104
|
+
# port = port to listen on
|
105
|
+
# o = an optional Hash of parameters:
|
106
|
+
# :localAddress = address to listen on (default = 0.0.0.0)
|
107
|
+
# :timeout = connection timeout? (optional)
|
108
|
+
def self.socket_listen(port, o=nil)
|
109
|
+
o ||= {}
|
110
|
+
o[:port] = port.to_i
|
111
|
+
con, args = get_connector("com.sun.jdi.SocketListen", o)
|
112
|
+
return con.accept(args)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Finds a connector of the given name from the virtual machine manager
|
116
|
+
# and prepares arguments based on its defaultArguments()
|
117
|
+
#
|
118
|
+
# Returns: an array containing the connector and prepared arguments
|
119
|
+
def self.get_connector(name, opts)
|
120
|
+
unless con=vm_connectors.find {|c| c.name == name}
|
121
|
+
raise "Can't get connector named #{name.inspect}'"
|
122
|
+
end
|
123
|
+
args = con.defaultArguments()
|
124
|
+
opts.each {|k,v| args.get(k.to_s).setValue(v) }
|
125
|
+
return [con, args]
|
126
|
+
end
|
127
|
+
|
128
|
+
##### Cruft added by mr bones:
|
129
|
+
|
130
|
+
# Returns the version string for the library.
|
131
|
+
#
|
132
|
+
def self.version
|
133
|
+
VERSION
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the library path for the module. If any arguments are given,
|
137
|
+
# they will be joined to the end of the libray path using
|
138
|
+
# <tt>File.join</tt>.
|
139
|
+
#
|
140
|
+
def self.libpath( *args )
|
141
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the lpath for the module. If any arguments are given,
|
145
|
+
# they will be joined to the end of the path using
|
146
|
+
# <tt>File.join</tt>.
|
147
|
+
#
|
148
|
+
def self.path( *args )
|
149
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Utility method used to require all files ending in .rb that lie in the
|
153
|
+
# directory below this file that has the same name as the filename passed
|
154
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
155
|
+
# the _filename_ does not have to be equivalent to the directory.
|
156
|
+
#
|
157
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
158
|
+
dir ||= ::File.basename(fname, '.*')
|
159
|
+
search_me = ::File.expand_path(
|
160
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
161
|
+
|
162
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
JdiHook.require_all_libs_relative_to(__FILE__)
|
166
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module JdiHook
|
2
|
+
# This is a base wrapper class for setting up an event handler for event
|
3
|
+
# requests in a debugee target VM.
|
4
|
+
#
|
5
|
+
# Implementations should override the following callbacks:
|
6
|
+
# create_event_requests, receive_event, and cleanup
|
7
|
+
class BaseDebugger
|
8
|
+
include_class [
|
9
|
+
"java.lang.InterruptedException",
|
10
|
+
"com.sun.jdi.request.EventRequest",
|
11
|
+
]
|
12
|
+
|
13
|
+
attr_accessor :class_filters_exc, :class_filters_inc
|
14
|
+
attr_reader :vm
|
15
|
+
|
16
|
+
DEFAULT_EXCLUDES = [
|
17
|
+
# Base java/sun stuff to exclude
|
18
|
+
"java.*", "javax.*", "sun.*", "com.sun.*",
|
19
|
+
# several exclusions for jruby/jirb targets
|
20
|
+
"org.jruby.*", "jline.*", "ruby.*", "org.jcodings.*", "jruby.*",
|
21
|
+
"org.joni.*"
|
22
|
+
]
|
23
|
+
|
24
|
+
def initialize(vm, opts={})
|
25
|
+
@vm = vm
|
26
|
+
@class_filters_exc = opts[:class_filters_exc] || DEFAULT_EXCLUDES
|
27
|
+
@class_filters_inc = opts[:class_filters_inc] || Array.new
|
28
|
+
@debug_mode = opts[:debug_mode] || 0
|
29
|
+
@redirect_stdio = opts[:redirect_stdio]
|
30
|
+
end
|
31
|
+
|
32
|
+
# This method begins the debugging session setting up the event
|
33
|
+
# handler and
|
34
|
+
def go
|
35
|
+
@vm.setDebugTraceMode(@debug_mode)
|
36
|
+
create_event_requests(@vm.eventRequestManager() )
|
37
|
+
@evt_thread = EventThread.new(self)
|
38
|
+
@evt_thread.start()
|
39
|
+
if @redirect_stdio
|
40
|
+
redirect_target_output($stdout, $stderr)
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
@vm.resume()
|
45
|
+
@evt_thread.join()
|
46
|
+
rescue InterruptedException => e
|
47
|
+
STDERR.puts "** Got InterruptedException: #{exc}"
|
48
|
+
ensure
|
49
|
+
cleanup()
|
50
|
+
@evt_thread = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method adds class exclusion and inclusion filters to an
|
55
|
+
# event request. It should be called from overridden create_event_requests
|
56
|
+
# implementations while setting up new event requests for the target VM.
|
57
|
+
def filter_classes(req)
|
58
|
+
if exc=@class_filters_exc
|
59
|
+
exc.each {|e| req.addClassExclusionFilter(e) }
|
60
|
+
end
|
61
|
+
if inc=@class_filters_inc
|
62
|
+
inc.each {|i| req.addClassFilter(i) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# This is a callback to set up event requests.
|
67
|
+
# It is called with one argument 'mgr' which is the event request
|
68
|
+
# manager for the target VM.
|
69
|
+
def create_event_requests(mgr)
|
70
|
+
# stub
|
71
|
+
end
|
72
|
+
|
73
|
+
# This is a callback to dispatch incoming events
|
74
|
+
# override it to perform whatever specific actions you want based
|
75
|
+
# on the event type
|
76
|
+
def receive_event(event)
|
77
|
+
# stub
|
78
|
+
end
|
79
|
+
|
80
|
+
# This is a callback to handle the end of the debugging session
|
81
|
+
# override it to perform any cleanup tasks or wrap up.
|
82
|
+
def cleanup()
|
83
|
+
# stub
|
84
|
+
end
|
85
|
+
|
86
|
+
# This method starts and joins threads to redirect stderr and stdout from
|
87
|
+
# the target process. Capturing IO this way from the target is generally
|
88
|
+
# only possible if the target is connected through a command line
|
89
|
+
# launch connector.
|
90
|
+
#
|
91
|
+
# This method should only be called from the 'go' method after the
|
92
|
+
# primary event thread has been started but before it has been
|
93
|
+
# joined.
|
94
|
+
def redirect_target_output(out=$stdout, err=$stderr)
|
95
|
+
if process = @vm.process()
|
96
|
+
unless @evt_thread and @evt_thread.connected
|
97
|
+
raise "the event thread has not yet been started"
|
98
|
+
end
|
99
|
+
out_thread = StreamRedirectThread.new("target stdout reader",
|
100
|
+
process.getInputStream(),
|
101
|
+
"Process STDOUT",
|
102
|
+
out)
|
103
|
+
|
104
|
+
err_thread = StreamRedirectThread.new("target stderr reader",
|
105
|
+
process.getErrorStream(),
|
106
|
+
"Process STDERR",
|
107
|
+
err)
|
108
|
+
|
109
|
+
out_thread.start()
|
110
|
+
err_thread.start()
|
111
|
+
out_thread.join()
|
112
|
+
err_thread.join()
|
113
|
+
return [out_thread, err_thread]
|
114
|
+
else
|
115
|
+
STDERR.puts "WARNING: can't redirect output on this target'"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module JdiHook
|
2
|
+
class EventThread < java.lang.Thread
|
3
|
+
include_class [
|
4
|
+
"java.lang.InterruptedException",
|
5
|
+
"com.sun.jdi.VMDisconnectedException",
|
6
|
+
"com.sun.jdi.event.VMStartEvent",
|
7
|
+
"com.sun.jdi.event.VMDeathEvent",
|
8
|
+
"com.sun.jdi.event.VMDisconnectEvent",
|
9
|
+
]
|
10
|
+
|
11
|
+
attr_reader :connected
|
12
|
+
|
13
|
+
def initialize( handler )
|
14
|
+
@handler = handler
|
15
|
+
@vm = handler.vm
|
16
|
+
end
|
17
|
+
|
18
|
+
def start(*args)
|
19
|
+
@connected = true
|
20
|
+
super(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def run()
|
24
|
+
queue = @vm.eventQueue()
|
25
|
+
while @connected
|
26
|
+
begin
|
27
|
+
events = queue.remove
|
28
|
+
events.each do |evt|
|
29
|
+
@connected=false if evt.is_a? VMDisconnectEvent
|
30
|
+
@handler.receive_event(evt) if @handler.respond_to?(:receive_event)
|
31
|
+
end
|
32
|
+
events.resume()
|
33
|
+
rescue InterruptedException
|
34
|
+
# ignore
|
35
|
+
rescue VMDisconnectedException
|
36
|
+
# A VMDisconnectedException has happened while dealing with
|
37
|
+
# another event. We need to bail so that we terminate correctly.
|
38
|
+
# XXX do we really need to do this?
|
39
|
+
@connected=false
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A VMDisconnectedException has happened while dealing with
|
46
|
+
# another event. We need to flush the event queue, dealing only
|
47
|
+
# with exit events (VMDeath, VMDisconnect) so that we terminate
|
48
|
+
# correctly.
|
49
|
+
def handleDisconnectedException
|
50
|
+
queue = @vm.eventQueue()
|
51
|
+
while @connected
|
52
|
+
begin
|
53
|
+
eventSet = queue.remove()
|
54
|
+
eventSet.each do |event|
|
55
|
+
if VMDeathEvent === event
|
56
|
+
vmDeathEvent(event)
|
57
|
+
elsif VMDisconnectEvent === event
|
58
|
+
vmDisconnectEvent(event)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
rescue InterruptedException
|
62
|
+
# ignore
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|