debase 0.0.2

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.
@@ -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