readapt 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Rakefile +0 -11
- data/ext/readapt/breakpoints.c +0 -5
- data/ext/readapt/breakpoints.h +0 -1
- data/ext/readapt/frame.c +137 -0
- data/ext/readapt/frame.h +17 -0
- data/ext/readapt/hash_table.c +0 -1
- data/ext/readapt/hash_table.h +0 -2
- data/ext/readapt/inspector.c +51 -0
- data/ext/readapt/inspector.h +8 -0
- data/ext/readapt/monitor.c +39 -26
- data/ext/readapt/normalize.c +14 -8
- data/ext/readapt/normalize.h +1 -1
- data/ext/readapt/readapt.c +2 -0
- data/ext/readapt/stack.c +86 -0
- data/ext/readapt/stack.h +20 -0
- data/ext/readapt/threads.c +105 -15
- data/ext/readapt/threads.h +10 -3
- data/lib/readapt.rb +1 -2
- data/lib/readapt/adapter.rb +3 -3
- data/lib/readapt/breakpoint.rb +8 -1
- data/lib/readapt/debugger.rb +50 -30
- data/lib/readapt/frame.rb +16 -18
- data/lib/readapt/message.rb +10 -7
- data/lib/readapt/message/scopes.rb +2 -1
- data/lib/readapt/message/set_breakpoints.rb +1 -1
- data/lib/readapt/message/stack_trace.rb +16 -4
- data/lib/readapt/message/variables.rb +23 -19
- data/lib/readapt/shell.rb +2 -0
- data/lib/readapt/snapshot.rb +1 -13
- data/lib/readapt/thread.rb +11 -22
- data/lib/readapt/version.rb +1 -1
- metadata +8 -3
- data/lib/readapt/location.rb +0 -25
data/ext/readapt/normalize.h
CHANGED
data/ext/readapt/readapt.c
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
#include "monitor.h"
|
4
4
|
#include "normalize.h"
|
5
5
|
#include "breakpoints.h"
|
6
|
+
#include "frame.h"
|
6
7
|
|
7
8
|
static VALUE m_Readapt;
|
8
9
|
|
@@ -12,5 +13,6 @@ void Init_readapt()
|
|
12
13
|
|
13
14
|
initialize_normalize(m_Readapt);
|
14
15
|
initialize_breakpoints(m_Readapt);
|
16
|
+
initialize_frame(m_Readapt);
|
15
17
|
initialize_monitor(m_Readapt);
|
16
18
|
}
|
data/ext/readapt/stack.c
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#include <stdlib.h>
|
2
|
+
#include "stack.h"
|
3
|
+
|
4
|
+
#define STACK_CAPACITY 20
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Allocate a stack. The `elem_size` is the expected size of each element,
|
8
|
+
* e.g., `sizeof(some_struct)`. The optional `free_func` argument is a pointer
|
9
|
+
* to a function that will be called when an element is popped off the stack.
|
10
|
+
*/
|
11
|
+
stack_t *stack_alloc(size_t elem_size, void (*free_func)(void *))
|
12
|
+
{
|
13
|
+
stack_t *s = malloc(sizeof(stack_t));
|
14
|
+
s->elem_size = elem_size;
|
15
|
+
s->free_func = free_func;
|
16
|
+
s->size = 0;
|
17
|
+
s->capacity = 0;
|
18
|
+
return s;
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Add an element to the end of the stack
|
23
|
+
*/
|
24
|
+
void stack_push(stack_t *stack, void *element)
|
25
|
+
{
|
26
|
+
if (stack->size == stack->capacity)
|
27
|
+
{
|
28
|
+
if (stack->capacity == 0)
|
29
|
+
{
|
30
|
+
stack->capacity = STACK_CAPACITY;
|
31
|
+
stack->elements = malloc(stack->elem_size * stack->capacity);
|
32
|
+
}
|
33
|
+
else
|
34
|
+
{
|
35
|
+
stack->capacity += STACK_CAPACITY;
|
36
|
+
stack->elements = realloc(stack->elements, stack->elem_size * stack->capacity);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
stack->elements[stack->size] = element;
|
40
|
+
stack->size++;
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Get a pointer to the last element in the stack.
|
45
|
+
*/
|
46
|
+
void *stack_peek(stack_t *stack)
|
47
|
+
{
|
48
|
+
return stack->size == 0 ? NULL : stack->elements[stack->size - 1];
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Pop the last element off the stack and pass it to free_func.
|
53
|
+
*/
|
54
|
+
void stack_pop(stack_t *stack)
|
55
|
+
{
|
56
|
+
void *e;
|
57
|
+
|
58
|
+
if (stack->size > 0)
|
59
|
+
{
|
60
|
+
e = stack->elements[stack->size - 1];
|
61
|
+
if (stack->free_func)
|
62
|
+
{
|
63
|
+
stack->free_func(e);
|
64
|
+
}
|
65
|
+
// stack->elements[stack->size - 1] = NULL;
|
66
|
+
stack->size--;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Free the stack from memory. If it still contains any elements, pass them to
|
72
|
+
* free_func.
|
73
|
+
*/
|
74
|
+
void stack_free(stack_t *stack)
|
75
|
+
{
|
76
|
+
int i;
|
77
|
+
|
78
|
+
if (stack->free_func)
|
79
|
+
{
|
80
|
+
for (i = 0; i < stack->size; i++)
|
81
|
+
{
|
82
|
+
stack->free_func(stack->elements[i]);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
free(stack);
|
86
|
+
}
|
data/ext/readapt/stack.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef STACK_H_
|
2
|
+
#define STACK_H_
|
3
|
+
|
4
|
+
#include "stddef.h"
|
5
|
+
|
6
|
+
typedef struct stack_struct {
|
7
|
+
int size;
|
8
|
+
size_t elem_size;
|
9
|
+
void (*free_func)(void *);
|
10
|
+
int capacity;
|
11
|
+
void **elements;
|
12
|
+
} stack_t;
|
13
|
+
|
14
|
+
stack_t *stack_alloc(size_t elem_size, void(*free_func)(void*));
|
15
|
+
void stack_push(stack_t *stack, void *element);
|
16
|
+
void *stack_peek(stack_t *stack);
|
17
|
+
void stack_pop(stack_t *stack);
|
18
|
+
void stack_free(stack_t *stack);
|
19
|
+
|
20
|
+
#endif
|
data/ext/readapt/threads.c
CHANGED
@@ -1,15 +1,23 @@
|
|
1
1
|
#include "ruby.h"
|
2
2
|
#include "ruby/debug.h"
|
3
3
|
#include "threads.h"
|
4
|
+
#include "frame.h"
|
5
|
+
#include "inspector.h"
|
4
6
|
|
7
|
+
static VALUE c_Thread;
|
5
8
|
static VALUE threads;
|
6
9
|
|
7
|
-
void thread_reference_free(void* data)
|
10
|
+
static void thread_reference_free(void* data)
|
8
11
|
{
|
9
|
-
|
12
|
+
thread_reference_t* thr;
|
13
|
+
|
14
|
+
thr = data;
|
15
|
+
// stack_free(thr->calls);
|
16
|
+
stack_free(thr->frames);
|
17
|
+
free(thr);
|
10
18
|
}
|
11
19
|
|
12
|
-
size_t thread_reference_size(const void* data)
|
20
|
+
static size_t thread_reference_size(const void* data)
|
13
21
|
{
|
14
22
|
return sizeof(thread_reference_t);
|
15
23
|
}
|
@@ -25,20 +33,16 @@ static const rb_data_type_t thread_reference_type = {
|
|
25
33
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
26
34
|
};
|
27
35
|
|
28
|
-
|
29
|
-
{
|
30
|
-
threads = rb_hash_new();
|
31
|
-
rb_global_variable(&threads);
|
32
|
-
}
|
33
|
-
|
34
|
-
VALUE thread_reference_new(VALUE thr)
|
36
|
+
static VALUE thread_reference_new(VALUE thr)
|
35
37
|
{
|
36
38
|
thread_reference_t *data = malloc(sizeof(thread_reference_t));
|
37
|
-
VALUE obj = TypedData_Make_Struct(
|
39
|
+
VALUE obj = TypedData_Make_Struct(c_Thread, thread_reference_t, &thread_reference_type, data);
|
38
40
|
data->id = NUM2LONG(rb_funcall(thr, rb_intern("object_id"), 0));
|
39
|
-
data->depth = 0;
|
40
41
|
data->cursor = 0;
|
42
|
+
data->depth = 0;
|
41
43
|
data->control = rb_intern("continue");
|
44
|
+
data->frames = stack_alloc(sizeof(frame_t), frame_free);
|
45
|
+
// data->calls = stack_alloc(sizeof(frame_t), NULL);
|
42
46
|
return obj;
|
43
47
|
}
|
44
48
|
|
@@ -75,8 +79,7 @@ VALUE thread_add_reference(VALUE thr)
|
|
75
79
|
|
76
80
|
VALUE thread_delete_reference(VALUE thr)
|
77
81
|
{
|
78
|
-
rb_hash_delete(threads, thr);
|
79
|
-
return Qnil;
|
82
|
+
return rb_hash_delete(threads, rb_obj_id(thr));
|
80
83
|
}
|
81
84
|
|
82
85
|
void thread_pause()
|
@@ -95,7 +98,94 @@ void thread_pause()
|
|
95
98
|
}
|
96
99
|
}
|
97
100
|
|
98
|
-
void
|
101
|
+
void thread_clear()
|
99
102
|
{
|
100
103
|
rb_funcall(threads, rb_intern("clear"), 0);
|
101
104
|
}
|
105
|
+
|
106
|
+
static VALUE thread_allocate_s(VALUE self)
|
107
|
+
{
|
108
|
+
thread_reference_t *data = malloc(sizeof(thread_reference_t));
|
109
|
+
data->control = rb_intern("continue");
|
110
|
+
data->depth = 0;
|
111
|
+
data->cursor = 0;
|
112
|
+
data->frames = stack_alloc(sizeof(frame_t), frame_free);
|
113
|
+
// data->calls = stack_alloc(sizeof(frame_t), NULL);
|
114
|
+
data->id = 0;
|
115
|
+
return TypedData_Wrap_Struct(self, &thread_reference_type, data);
|
116
|
+
}
|
117
|
+
|
118
|
+
static VALUE thread_all_s(VALUE self)
|
119
|
+
{
|
120
|
+
return rb_funcall(threads, rb_intern("values"), 0);
|
121
|
+
}
|
122
|
+
|
123
|
+
static VALUE thread_find_s(VALUE self, VALUE id)
|
124
|
+
{
|
125
|
+
return thread_reference_id(id);
|
126
|
+
}
|
127
|
+
|
128
|
+
static VALUE thread_include_s(VALUE self, VALUE id)
|
129
|
+
{
|
130
|
+
return rb_funcall(threads, rb_intern("include?"), 1, id);
|
131
|
+
}
|
132
|
+
|
133
|
+
static VALUE thread_id_m(VALUE self)
|
134
|
+
{
|
135
|
+
thread_reference_t *data = thread_reference_pointer(self);
|
136
|
+
return LONG2NUM(data->id);
|
137
|
+
}
|
138
|
+
|
139
|
+
static VALUE frames_m(VALUE self)
|
140
|
+
{
|
141
|
+
thread_reference_t *data;
|
142
|
+
VALUE ary;
|
143
|
+
VALUE frm;
|
144
|
+
int i;
|
145
|
+
frame_t *fd;
|
146
|
+
|
147
|
+
ary = rb_ary_new();
|
148
|
+
data = thread_reference_pointer(self);
|
149
|
+
for (i = data->frames->size - 1; i >= 0; i--)
|
150
|
+
{
|
151
|
+
fd = data->frames->elements[i];
|
152
|
+
// TODO This condition should probably not be necessary.
|
153
|
+
if (fd->binding != Qnil)
|
154
|
+
{
|
155
|
+
frm = frame_new_from_data(fd);
|
156
|
+
rb_ary_push(ary, frm);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
return ary;
|
161
|
+
}
|
162
|
+
|
163
|
+
void thread_reference_build_frames(thread_reference_t *ptr)
|
164
|
+
{
|
165
|
+
inspector_inspect(ptr);
|
166
|
+
}
|
167
|
+
|
168
|
+
void thread_reference_clear_frames(thread_reference_t *ptr)
|
169
|
+
{
|
170
|
+
// TODO This is probably suboptimal
|
171
|
+
// while (ptr->frames->size > 0)
|
172
|
+
// {
|
173
|
+
// stack_pop(ptr->frames);
|
174
|
+
// }
|
175
|
+
stack_free(ptr->frames);
|
176
|
+
ptr->frames = stack_alloc(sizeof(frame_t), frame_free);
|
177
|
+
}
|
178
|
+
|
179
|
+
void initialize_threads(VALUE m_Readapt)
|
180
|
+
{
|
181
|
+
c_Thread = rb_define_class_under(m_Readapt, "Thread", rb_cData);
|
182
|
+
rb_define_alloc_func(c_Thread, thread_allocate_s);
|
183
|
+
rb_define_method(c_Thread, "id", thread_id_m, 0);
|
184
|
+
rb_define_method(c_Thread, "frames", frames_m, 0);
|
185
|
+
rb_define_singleton_method(c_Thread, "all", thread_all_s, 0);
|
186
|
+
rb_define_singleton_method(c_Thread, "find", thread_find_s, 1);
|
187
|
+
rb_define_singleton_method(c_Thread, "include?", thread_include_s, 1);
|
188
|
+
|
189
|
+
threads = rb_hash_new();
|
190
|
+
rb_global_variable(&threads);
|
191
|
+
}
|
data/ext/readapt/threads.h
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
#ifndef THREADS_H_
|
2
2
|
#define THREADS_H_
|
3
3
|
|
4
|
+
#include "ruby.h"
|
5
|
+
#include "frame.h"
|
6
|
+
#include "stack.h"
|
7
|
+
|
4
8
|
typedef struct thread_reference_struct {
|
5
9
|
long id;
|
6
|
-
int depth;
|
7
10
|
int cursor;
|
11
|
+
int depth;
|
8
12
|
ID control;
|
13
|
+
stack_t *frames;
|
9
14
|
} thread_reference_t;
|
10
15
|
|
11
|
-
void initialize_threads();
|
16
|
+
void initialize_threads(VALUE);
|
12
17
|
VALUE thread_current_reference();
|
13
18
|
VALUE thread_reference(VALUE);
|
14
19
|
VALUE thread_reference_id(VALUE);
|
15
20
|
VALUE thread_add_reference(VALUE);
|
16
21
|
VALUE thread_delete_reference(VALUE);
|
17
22
|
thread_reference_t *thread_reference_pointer(VALUE);
|
23
|
+
void thread_reference_build_frames(thread_reference_t *);
|
24
|
+
void thread_reference_clear_frames(thread_reference_t *);
|
18
25
|
void thread_pause();
|
19
|
-
void
|
26
|
+
void thread_clear();
|
20
27
|
|
21
28
|
#endif
|
data/lib/readapt.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'backport'
|
2
2
|
|
3
3
|
require 'readapt/version'
|
4
|
+
require 'readapt/readapt'
|
4
5
|
require 'readapt/breakpoint'
|
5
|
-
require 'readapt/location'
|
6
6
|
require 'readapt/thread'
|
7
7
|
require 'readapt/frame'
|
8
8
|
require 'readapt/monitor'
|
@@ -12,7 +12,6 @@ require 'readapt/debugger'
|
|
12
12
|
require 'readapt/message'
|
13
13
|
require 'readapt/variable'
|
14
14
|
require 'readapt/adapter'
|
15
|
-
require 'readapt/readapt'
|
16
15
|
require 'readapt/shell'
|
17
16
|
|
18
17
|
module Readapt
|
data/lib/readapt/adapter.rb
CHANGED
@@ -69,9 +69,9 @@ module Readapt
|
|
69
69
|
envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
|
70
70
|
write envelope
|
71
71
|
end
|
72
|
-
rescue
|
73
|
-
STDERR.puts e.message
|
74
|
-
STDERR.puts e.backtrace
|
72
|
+
rescue RuntimeError => e
|
73
|
+
STDERR.puts "[#{e.class}] #{e.message}"
|
74
|
+
STDERR.puts e.backtrace.join
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
data/lib/readapt/breakpoint.rb
CHANGED
@@ -3,11 +3,18 @@ module Readapt
|
|
3
3
|
attr_reader :source
|
4
4
|
attr_reader :line
|
5
5
|
attr_reader :condition
|
6
|
+
attr_reader :hit_condition
|
7
|
+
attr_writer :hit_cursor
|
6
8
|
|
7
|
-
def initialize source, line, condition
|
9
|
+
def initialize source, line, condition, hit_condition
|
8
10
|
@source = source
|
9
11
|
@line = line
|
10
12
|
@condition = condition
|
13
|
+
@hit_condition = hit_condition
|
14
|
+
end
|
15
|
+
|
16
|
+
def hit_cursor
|
17
|
+
@hit_cursor ||= 0
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
data/lib/readapt/debugger.rb
CHANGED
@@ -15,7 +15,6 @@ module Readapt
|
|
15
15
|
|
16
16
|
def initialize machine = Machine.new
|
17
17
|
@stack = []
|
18
|
-
@threads = {}
|
19
18
|
@frames = {}
|
20
19
|
@running = false
|
21
20
|
@attached = false
|
@@ -37,11 +36,11 @@ module Readapt
|
|
37
36
|
|
38
37
|
# @return [Readapt::Thread]
|
39
38
|
def thread id
|
40
|
-
|
39
|
+
Thread.find(id)
|
41
40
|
end
|
42
41
|
|
43
42
|
def threads
|
44
|
-
|
43
|
+
Thread.all
|
45
44
|
end
|
46
45
|
|
47
46
|
def frame id
|
@@ -74,7 +73,7 @@ module Readapt
|
|
74
73
|
end
|
75
74
|
yield if block_given?
|
76
75
|
rescue StandardError => e
|
77
|
-
STDERR.puts e.message
|
76
|
+
STDERR.puts "[#{e.class}] #{e.message}"
|
78
77
|
STDERR.puts e.backtrace.join("\n")
|
79
78
|
rescue SystemExit
|
80
79
|
# Ignore
|
@@ -82,6 +81,8 @@ module Readapt
|
|
82
81
|
Monitor.stop
|
83
82
|
@running = false
|
84
83
|
set_original_args
|
84
|
+
STDOUT.flush
|
85
|
+
STDERR.flush
|
85
86
|
changed
|
86
87
|
send_event 'terminated', nil
|
87
88
|
end
|
@@ -94,11 +95,11 @@ module Readapt
|
|
94
95
|
end
|
95
96
|
|
96
97
|
def get_breakpoint source, line
|
97
|
-
@breakpoints["#{source}:#{line}"] || Breakpoint.new(source, line, nil)
|
98
|
+
@breakpoints["#{source}:#{line}"] || Breakpoint.new(source, line, nil, 0)
|
98
99
|
end
|
99
100
|
|
100
|
-
def set_breakpoint source, line, condition
|
101
|
-
@breakpoints["#{source}:#{line}"] = Breakpoint.new(source, line, condition)
|
101
|
+
def set_breakpoint source, line, condition, hitcount
|
102
|
+
@breakpoints["#{source}:#{line}"] = Breakpoint.new(source, line, condition, hitcount)
|
102
103
|
end
|
103
104
|
|
104
105
|
def clear_breakpoints source
|
@@ -121,9 +122,9 @@ module Readapt
|
|
121
122
|
# @param [Snapshot]
|
122
123
|
# return [void]
|
123
124
|
def debug snapshot
|
125
|
+
sleep 0.001 # @todo Trying to let thread data sync
|
124
126
|
if snapshot.event == :thread_begin || snapshot.event == :entry
|
125
|
-
|
126
|
-
thr = @threads[snapshot.thread_id]
|
127
|
+
thr = Thread.find(snapshot.thread_id)
|
127
128
|
thr.control = :continue
|
128
129
|
send_event('thread', {
|
129
130
|
reason: 'started',
|
@@ -133,47 +134,66 @@ module Readapt
|
|
133
134
|
elsif snapshot.event == :thread_end
|
134
135
|
thr = thread(snapshot.thread_id)
|
135
136
|
thr.control = :continue
|
136
|
-
@threads.delete snapshot.thread_id
|
137
|
+
# @threads.delete snapshot.thread_id
|
137
138
|
send_event('thread', {
|
138
139
|
reason: 'exited',
|
139
140
|
threadId: snapshot.thread_id
|
140
141
|
})
|
141
142
|
snapshot.control = :continue
|
142
|
-
# elsif snapshot.event == :entry
|
143
|
-
# snapshot.control = :continue
|
144
143
|
else
|
144
|
+
confirmed_pause = true
|
145
|
+
thread = self.thread(snapshot.thread_id)
|
145
146
|
if snapshot.event == :breakpoint
|
146
147
|
bp = get_breakpoint(snapshot.file, snapshot.line)
|
147
148
|
unless bp.condition.nil? || bp.condition.empty?
|
148
149
|
# @type [Binding]
|
149
|
-
bnd =
|
150
|
+
bnd = thread.frames.first.frame_binding
|
150
151
|
begin
|
151
152
|
unless bnd.eval(bp.condition)
|
152
|
-
|
153
|
-
return
|
153
|
+
confirmed_pause = false
|
154
154
|
end
|
155
155
|
rescue Exception => e
|
156
156
|
STDERR.puts "Breakpoint condition raised an error"
|
157
157
|
STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
|
158
158
|
STDERR.puts "[#{e.class}] #{e.message}"
|
159
|
-
|
160
|
-
|
159
|
+
confirmed_pause = false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
unless !confirmed_pause || bp.hit_condition.nil? || bp.hit_condition.empty?
|
163
|
+
bp.hit_cursor += 1
|
164
|
+
bnd = thread.frames.first.frame_binding
|
165
|
+
begin
|
166
|
+
hit_count = bnd.eval(bp.hit_condition)
|
167
|
+
if bp.hit_cursor == hit_count
|
168
|
+
bp.hit_cursor = 0
|
169
|
+
else
|
170
|
+
confirmed_pause = false
|
171
|
+
end
|
172
|
+
rescue Exception => e
|
173
|
+
STDERR.puts "Breakpoint condition raised an error"
|
174
|
+
STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
|
175
|
+
STDERR.puts "[#{e.class}] #{e.message}"
|
176
|
+
confirmed_pause = false
|
161
177
|
end
|
162
178
|
end
|
163
179
|
end
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
180
|
+
if confirmed_pause
|
181
|
+
changed
|
182
|
+
thread.control = :pause
|
183
|
+
thread.frames.each do |frm|
|
184
|
+
@frames[frm.local_id] = frm
|
185
|
+
end
|
186
|
+
send_event('stopped', {
|
187
|
+
reason: snapshot.event,
|
188
|
+
threadId: thread.id
|
189
|
+
})
|
190
|
+
sleep 0.01 until thread.control != :pause || !Thread.include?(thread.id)
|
191
|
+
thread.frames.each do |frm|
|
192
|
+
@frames.delete frm.local_id
|
193
|
+
end
|
194
|
+
else
|
195
|
+
thread.control = :continue
|
196
|
+
end
|
177
197
|
snapshot.control = thread.control
|
178
198
|
end
|
179
199
|
end
|