debase 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,427 @@
1
+ #include <debase_internals.h>
2
+
3
+ static VALUE mDebase; /* Ruby Debase Module object */
4
+ static VALUE cContext;
5
+ static VALUE cDebugThread;
6
+
7
+ static VALUE debug = Qfalse;
8
+ static VALUE locker = Qnil;
9
+ static VALUE contexts;
10
+ static VALUE catchpoints;
11
+ static VALUE breakpoints;
12
+
13
+ static VALUE tpLine;
14
+ static VALUE tpCall;
15
+ static VALUE tpReturn;
16
+ static VALUE tpRaise;
17
+
18
+ static VALUE idAlive;
19
+ static VALUE idAtLine;
20
+ static VALUE idAtBreakpoint;
21
+ static VALUE idAtCatchpoint;
22
+
23
+ static VALUE
24
+ Debase_thread_context(VALUE self, VALUE thread)
25
+ {
26
+ VALUE context;
27
+
28
+ context = rb_hash_aref(contexts, thread);
29
+ if (context == Qnil) {
30
+ context = context_create(thread, cDebugThread);
31
+ rb_hash_aset(contexts, thread, context);
32
+ }
33
+ return context;
34
+ }
35
+
36
+ static VALUE
37
+ Debase_current_context(VALUE self)
38
+ {
39
+ return Debase_thread_context(self, rb_thread_current());
40
+ }
41
+
42
+ static int
43
+ remove_dead_threads(VALUE thread, VALUE context, VALUE ignored)
44
+ {
45
+ return (IS_THREAD_ALIVE(thread)) ? ST_CONTINUE : ST_DELETE;
46
+ }
47
+
48
+ static void
49
+ cleanup(debug_context_t *context)
50
+ {
51
+ VALUE thread;
52
+
53
+ context->stop_reason = CTX_STOP_NONE;
54
+
55
+ /* release a lock */
56
+ locker = Qnil;
57
+
58
+ /* let the next thread to run */
59
+ thread = remove_from_locked();
60
+ if(thread != Qnil)
61
+ rb_thread_run(thread);
62
+ }
63
+
64
+ static int
65
+ check_start_processing(debug_context_t *context, VALUE thread)
66
+ {
67
+ /* return if thread is marked as 'ignored'.
68
+ debugger's threads are marked this way
69
+ */
70
+ if(CTX_FL_TEST(context, CTX_FL_IGNORE)) return 0;
71
+
72
+ while(1)
73
+ {
74
+ /* halt execution of the current thread if the debugger
75
+ is activated in another
76
+ */
77
+ while(locker != Qnil && locker != thread)
78
+ {
79
+ add_to_locked(thread);
80
+ rb_thread_stop();
81
+ }
82
+
83
+ /* stop the current thread if it's marked as suspended */
84
+ if(CTX_FL_TEST(context, CTX_FL_SUSPEND) && locker != thread)
85
+ {
86
+ CTX_FL_SET(context, CTX_FL_WAS_RUNNING);
87
+ rb_thread_stop();
88
+ }
89
+ else break;
90
+ }
91
+
92
+ /* return if the current thread is the locker */
93
+ if(locker != Qnil) return 0;
94
+
95
+ /* only the current thread can proceed */
96
+ locker = thread;
97
+
98
+ /* ignore a skipped section of code */
99
+ if(CTX_FL_TEST(context, CTX_FL_SKIPPED)) {
100
+ cleanup(context);
101
+ return 0;
102
+ }
103
+ return 1;
104
+ }
105
+
106
+ static inline void
107
+ load_frame_info(VALUE trace_point, VALUE *path, VALUE *lineno, VALUE *binding, VALUE *self)
108
+ {
109
+ rb_trace_point_t *tp;
110
+
111
+ tp = rb_tracearg_from_tracepoint(trace_point);
112
+
113
+ *path = rb_tracearg_path(tp);
114
+ *lineno = rb_tracearg_lineno(tp);
115
+ *binding = rb_tracearg_binding(tp);
116
+ *self = rb_tracearg_self(tp);
117
+ }
118
+
119
+ static void
120
+ call_at_line(debug_context_t *context, char *file, int line, VALUE context_object, VALUE path, VALUE lineno)
121
+ {
122
+ CTX_FL_UNSET(context, CTX_FL_STEPPED);
123
+ CTX_FL_UNSET(context, CTX_FL_FORCE_MOVE);
124
+ context->last_file = file;
125
+ context->last_line = line;
126
+ rb_funcall(context_object, idAtLine, 2, path, lineno);
127
+ }
128
+
129
+ static void
130
+ process_line_event(VALUE trace_point, void *data)
131
+ {
132
+ VALUE path;
133
+ VALUE lineno;
134
+ VALUE binding;
135
+ VALUE self;
136
+ VALUE context_object;
137
+ VALUE breakpoint;
138
+ debug_context_t *context;
139
+ char *file;
140
+ int line;
141
+ int moved;
142
+
143
+ context_object = Debase_current_context(mDebase);
144
+ Data_Get_Struct(context_object, debug_context_t, context);
145
+ if (!check_start_processing(context, rb_thread_current())) return;
146
+
147
+ load_frame_info(trace_point, &path, &lineno, &binding, &self);
148
+ file = RSTRING_PTR(path);
149
+ line = FIX2INT(lineno);
150
+ update_frame(context_object, file, line, binding, self);
151
+
152
+ if (debug == Qtrue)
153
+ fprintf(stderr, "line: file=%s, line=%d, stack_size=%d\n", file, line, context->stack_size);
154
+
155
+ moved = context->last_line != line || context->last_file == NULL ||
156
+ strcmp(context->last_file, file) != 0;
157
+
158
+ if(context->dest_frame == -1 || context->stack_size == context->dest_frame)
159
+ {
160
+ if(moved || !CTX_FL_TEST(context, CTX_FL_FORCE_MOVE))
161
+ context->stop_next--;
162
+ if(context->stop_next < 0)
163
+ context->stop_next = -1;
164
+ if(moved || (CTX_FL_TEST(context, CTX_FL_STEPPED) && !CTX_FL_TEST(context, CTX_FL_FORCE_MOVE)))
165
+ {
166
+ context->stop_line--;
167
+ CTX_FL_UNSET(context, CTX_FL_STEPPED);
168
+ }
169
+ }
170
+ else if(context->stack_size < context->dest_frame)
171
+ {
172
+ context->stop_next = 0;
173
+ }
174
+
175
+ breakpoint = breakpoint_find(breakpoints, path, lineno, binding);
176
+ if(context->stop_next == 0 || context->stop_line == 0 || breakpoint != Qnil) {
177
+ context->stop_reason = CTX_STOP_STEP;
178
+ if (breakpoint != Qnil) {
179
+ rb_funcall(context_object, idAtBreakpoint, 1, breakpoint);
180
+ }
181
+ reset_stepping_stop_points(context);
182
+ call_at_line(context, file, line, context_object, path, lineno);
183
+ }
184
+ cleanup(context);
185
+ }
186
+
187
+ static void
188
+ process_return_event(VALUE trace_point, void *data)
189
+ {
190
+ VALUE path;
191
+ VALUE lineno;
192
+ VALUE binding;
193
+ VALUE self;
194
+ VALUE context_object;
195
+ debug_context_t *context;
196
+
197
+ context_object = Debase_current_context(mDebase);
198
+ Data_Get_Struct(context_object, debug_context_t, context);
199
+ if (!check_start_processing(context, rb_thread_current())) return;
200
+
201
+ if(context->stack_size == context->stop_frame)
202
+ {
203
+ context->stop_next = 1;
204
+ context->stop_frame = 0;
205
+ }
206
+
207
+ load_frame_info(trace_point, &path, &lineno, &binding, &self);
208
+ if (debug == Qtrue)
209
+ fprintf(stderr, "return: file=%s, line=%d, stack_size=%d\n", RSTRING_PTR(path), FIX2INT(lineno), context->stack_size);
210
+ // rb_funcall(context_object, idAtReturn, 2, path, lineno);
211
+ pop_frame(context_object);
212
+ cleanup(context);
213
+ }
214
+
215
+ static void
216
+ process_call_event(VALUE trace_point, void *data)
217
+ {
218
+ VALUE path;
219
+ VALUE lineno;
220
+ VALUE binding;
221
+ VALUE self;
222
+ VALUE context_object;
223
+ debug_context_t *context;
224
+
225
+ context_object = Debase_current_context(mDebase);
226
+ Data_Get_Struct(context_object, debug_context_t, context);
227
+ if (!check_start_processing(context, rb_thread_current())) return;
228
+
229
+ load_frame_info(trace_point, &path, &lineno, &binding, &self);
230
+ if (debug == Qtrue)
231
+ fprintf(stderr, "call: file=%s, line=%d, stack_size=%d\n", RSTRING_PTR(path), FIX2INT(lineno), context->stack_size);
232
+ push_frame(context_object, RSTRING_PTR(path), FIX2INT(lineno), binding, self);
233
+ cleanup(context);
234
+ }
235
+
236
+ static void
237
+ process_raise_event(VALUE trace_point, void *data)
238
+ {
239
+ VALUE path;
240
+ VALUE lineno;
241
+ VALUE binding;
242
+ VALUE self;
243
+ VALUE context_object;
244
+ VALUE hit_count;
245
+ VALUE exception_name;
246
+ debug_context_t *context;
247
+ char *file;
248
+ int line;
249
+ int c_hit_count;
250
+
251
+ context_object = Debase_current_context(mDebase);
252
+ Data_Get_Struct(context_object, debug_context_t, context);
253
+ if (!check_start_processing(context, rb_thread_current())) return;
254
+
255
+ load_frame_info(trace_point, &path, &lineno, &binding, &self);
256
+ file = RSTRING_PTR(path);
257
+ line = FIX2INT(lineno);
258
+ update_frame(context_object, file, line, binding, self);
259
+
260
+ if (catchpoint_hit_count(catchpoints, rb_errinfo(), &exception_name) != Qnil) {
261
+ /* On 64-bit systems with gcc and -O2 there seems to be
262
+ an optimization bug in running INT2FIX(FIX2INT...)..)
263
+ So we do this in two steps.
264
+ */
265
+ c_hit_count = FIX2INT(rb_hash_aref(catchpoints, exception_name)) + 1;
266
+ hit_count = INT2FIX(c_hit_count);
267
+ rb_hash_aset(catchpoints, exception_name, hit_count);
268
+ context->stop_reason = CTX_STOP_CATCHPOINT;
269
+ rb_funcall(context_object, idAtCatchpoint, 1, rb_errinfo());
270
+ call_at_line(context, file, line, context_object, path, lineno);
271
+ }
272
+
273
+ cleanup(context);
274
+ }
275
+
276
+ static VALUE
277
+ Debase_setup_tracepoints(VALUE self)
278
+ {
279
+ if (catchpoints != Qnil) return Qnil;
280
+ contexts = rb_hash_new();
281
+ breakpoints = rb_ary_new();
282
+ catchpoints = rb_hash_new();
283
+
284
+ tpLine = rb_tracepoint_new(Qnil, RUBY_EVENT_LINE, process_line_event, NULL);
285
+ rb_tracepoint_enable(tpLine);
286
+ tpReturn = rb_tracepoint_new(Qnil, RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN | RUBY_EVENT_B_RETURN | RUBY_EVENT_CLASS | RUBY_EVENT_END,
287
+ process_return_event, NULL);
288
+ rb_tracepoint_enable(tpReturn);
289
+ tpCall = rb_tracepoint_new(Qnil, RUBY_EVENT_CALL | RUBY_EVENT_C_CALL | RUBY_EVENT_B_CALL,
290
+ process_call_event, NULL);
291
+ rb_tracepoint_enable(tpCall);
292
+ tpRaise = rb_tracepoint_new(Qnil, RUBY_EVENT_RAISE, process_raise_event, NULL);
293
+ rb_tracepoint_enable(tpRaise);
294
+ return Qnil;
295
+ }
296
+
297
+ static VALUE
298
+ Debase_remove_tracepoints(VALUE self)
299
+ {
300
+ contexts = Qnil;
301
+ breakpoints = Qnil;
302
+ catchpoints = Qnil;
303
+
304
+ if (tpLine != Qnil) rb_tracepoint_disable(tpLine);
305
+ tpLine = Qnil;
306
+ if (tpReturn != Qnil) rb_tracepoint_disable(tpReturn);
307
+ tpReturn = Qnil;
308
+ if (tpCall != Qnil) rb_tracepoint_disable(tpCall);
309
+ tpCall = Qnil;
310
+ if (tpRaise != Qnil) rb_tracepoint_disable(tpRaise);
311
+ tpRaise = Qnil;
312
+ return Qnil;
313
+ }
314
+
315
+ static VALUE
316
+ debase_prepare_context(VALUE self, VALUE file, VALUE stop)
317
+ {
318
+ VALUE context_object;
319
+ debug_context_t *context;
320
+
321
+ context_object = Debase_current_context(self);
322
+ Data_Get_Struct(context_object, debug_context_t, context);
323
+
324
+ if(RTEST(stop)) context->stop_next = 1;
325
+ ruby_script(RSTRING_PTR(file));
326
+ return self;
327
+ }
328
+
329
+ static VALUE
330
+ Debase_debug_load(int argc, VALUE *argv, VALUE self)
331
+ {
332
+ VALUE file, stop, increment_start;
333
+ int state;
334
+
335
+ if(rb_scan_args(argc, argv, "12", &file, &stop, &increment_start) == 1)
336
+ {
337
+ stop = Qfalse;
338
+ increment_start = Qtrue;
339
+ }
340
+ Debase_setup_tracepoints(self);
341
+ debase_prepare_context(self, file, stop);
342
+ rb_load_protect(file, 0, &state);
343
+ if (0 != state)
344
+ {
345
+ return rb_errinfo();
346
+ }
347
+ return Qnil;
348
+ }
349
+
350
+ static int
351
+ values_i(VALUE key, VALUE value, VALUE ary)
352
+ {
353
+ rb_ary_push(ary, value);
354
+ return ST_CONTINUE;
355
+ }
356
+
357
+ static VALUE
358
+ Debase_contexts(VALUE self)
359
+ {
360
+ VALUE ary;
361
+
362
+ ary = rb_ary_new();
363
+ /* check that all contexts point to alive threads */
364
+ rb_hash_foreach(contexts, remove_dead_threads, 0);
365
+
366
+ rb_hash_foreach(contexts, values_i, ary);
367
+
368
+ return ary;
369
+ }
370
+
371
+ static VALUE
372
+ Debase_breakpoints(VALUE self)
373
+ {
374
+ return breakpoints;
375
+ }
376
+
377
+ static VALUE
378
+ Debase_catchpoints(VALUE self)
379
+ {
380
+ if (catchpoints == Qnil)
381
+ rb_raise(rb_eRuntimeError, "Debugger.start is not called yet.");
382
+ return catchpoints;
383
+ }
384
+
385
+ static VALUE
386
+ Debase_started(VALUE self)
387
+ {
388
+ return catchpoints != Qnil ? Qtrue : Qfalse;
389
+ }
390
+ /*
391
+ * Document-class: Debase
392
+ *
393
+ * == Summary
394
+ *
395
+ * This is a singleton class allows controlling the debugger. Use it to start/stop debugger,
396
+ * set/remove breakpoints, etc.
397
+ */
398
+ void
399
+ Init_debase_internals()
400
+ {
401
+ mDebase = rb_define_module("Debase");
402
+ rb_define_module_function(mDebase, "setup_tracepoints", Debase_setup_tracepoints, 0);
403
+ rb_define_module_function(mDebase, "remove_tracepoints", Debase_remove_tracepoints, 0);
404
+ rb_define_module_function(mDebase, "current_context", Debase_current_context, 0);
405
+ rb_define_module_function(mDebase, "debug_load", Debase_debug_load, -1);
406
+ rb_define_module_function(mDebase, "contexts", Debase_contexts, 0);
407
+ rb_define_module_function(mDebase, "breakpoints", Debase_breakpoints, 0);
408
+ rb_define_module_function(mDebase, "catchpoints", Debase_catchpoints, 0);
409
+ rb_define_module_function(mDebase, "started?", Debase_started, 0);
410
+
411
+ idAlive = rb_intern("alive?");
412
+ idAtLine = rb_intern("at_line");
413
+ idAtBreakpoint = rb_intern("at_breakpoint");
414
+ idAtCatchpoint = rb_intern("at_catchpoint");
415
+
416
+ cContext = Init_context(mDebase);
417
+ Init_breakpoint(mDebase);
418
+ cDebugThread = rb_define_class_under(mDebase, "DebugThread", rb_cThread);
419
+ contexts = Qnil;
420
+ catchpoints = Qnil;
421
+ breakpoints = Qnil;
422
+
423
+ rb_global_variable(&locker);
424
+ rb_global_variable(&breakpoints);
425
+ rb_global_variable(&catchpoints);
426
+ rb_global_variable(&contexts);
427
+ }
@@ -0,0 +1,94 @@
1
+ #ifndef DEBASE_INTERNALS
2
+ #define DEBASE_INTERNALS
3
+
4
+ #include <ruby.h>
5
+ #include <ruby/debug.h>
6
+
7
+ typedef struct rb_trace_arg_struct rb_trace_point_t;
8
+
9
+ /* Debase::Context */
10
+ /* flags */
11
+ #define CTX_FL_SUSPEND (1<<1)
12
+ #define CTX_FL_TRACING (1<<2)
13
+ #define CTX_FL_SKIPPED (1<<3)
14
+ #define CTX_FL_IGNORE (1<<4)
15
+ #define CTX_FL_DEAD (1<<5)
16
+ #define CTX_FL_WAS_RUNNING (1<<6)
17
+ #define CTX_FL_ENABLE_BKPT (1<<7)
18
+ #define CTX_FL_STEPPED (1<<8)
19
+ #define CTX_FL_FORCE_MOVE (1<<9)
20
+ #define CTX_FL_CATCHING (1<<10)
21
+
22
+ /* macro functions */
23
+ #define CTX_FL_TEST(c,f) ((c)->flags & (f))
24
+ #define CTX_FL_SET(c,f) do { (c)->flags |= (f); } while (0)
25
+ #define CTX_FL_UNSET(c,f) do { (c)->flags &= ~(f); } while (0)
26
+
27
+ #define IS_THREAD_ALIVE(t) (rb_funcall((t), idAlive, 0) == Qtrue)
28
+
29
+ /* types */
30
+ typedef enum {CTX_STOP_NONE, CTX_STOP_STEP, CTX_STOP_BREAKPOINT, CTX_STOP_CATCHPOINT} ctx_stop_reason;
31
+
32
+ typedef struct debug_frame_t
33
+ {
34
+ struct debug_frame_t *prev;
35
+ char *file;
36
+ int line;
37
+ VALUE binding;
38
+ VALUE self;
39
+ } debug_frame_t;
40
+
41
+ typedef struct {
42
+ debug_frame_t *stack;
43
+ int stack_size;
44
+
45
+ VALUE thread;
46
+ int thnum;
47
+ int flags;
48
+
49
+ ctx_stop_reason stop_reason;
50
+ int stop_next;
51
+ int dest_frame;
52
+ int stop_line;
53
+ int stop_frame;
54
+
55
+ char *last_file;
56
+ int last_line;
57
+ } debug_context_t;
58
+
59
+ /* functions */
60
+ extern VALUE Init_context(VALUE mDebase);
61
+ extern VALUE context_create(VALUE thread, VALUE cDebugThread);
62
+ extern void reset_stepping_stop_points(debug_context_t *context);
63
+ extern VALUE Context_ignored(VALUE self);
64
+ extern void push_frame(VALUE context_object, char* file, int line, VALUE binding, VALUE self);
65
+ extern void pop_frame(VALUE context_object);
66
+ extern void update_frame(VALUE context_object, char* file, int line, VALUE binding, VALUE self);
67
+
68
+ /* locked threads container */
69
+ /* types */
70
+ typedef struct locked_thread_t {
71
+ VALUE thread;
72
+ struct locked_thread_t *next;
73
+ } locked_thread_t;
74
+
75
+ /* functions */
76
+ extern int is_in_locked(VALUE thread_id);
77
+ extern void add_to_locked(VALUE thread);
78
+ extern VALUE remove_from_locked();
79
+
80
+ /* breakpoints and catchpoints */
81
+ /* types */
82
+ typedef struct
83
+ {
84
+ VALUE enabled;
85
+ VALUE source;
86
+ VALUE expr;
87
+ int line;
88
+ int id;
89
+ } breakpoint_t;
90
+
91
+ extern VALUE catchpoint_hit_count(VALUE catchpoints, VALUE exception, VALUE *exception_name);
92
+ extern VALUE breakpoint_find(VALUE breakpoints, VALUE source, VALUE pos, VALUE binding);
93
+ extern void Init_breakpoint(VALUE mDebase);
94
+ #endif