Skip to content
This repository
Newer
Older
100644 422 lines (384 sloc) 9.947 kb
cf73725a » Laurent Sansonetti
2010-01-17 experimental debugger
1 /*
2 * MacRuby debugger.
3 *
4 * This file is covered by the Ruby license. See COPYING for more details.
5 *
6 * Copyright (C) 2010, Apple Inc. All rights reserved.
7 */
8
9 #include <llvm/Module.h>
10 #include <llvm/DerivedTypes.h>
11 #include <llvm/Constants.h>
12 #include <llvm/CallingConv.h>
13 #include <llvm/Instructions.h>
14 #include <llvm/ModuleProvider.h>
15 #include <llvm/Intrinsics.h>
16 #include <llvm/Analysis/DebugInfo.h>
17 #include <llvm/ExecutionEngine/JIT.h>
18 #include <llvm/PassManager.h>
19 #include <llvm/Target/TargetData.h>
20 using namespace llvm;
21
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25
26 #include "ruby/ruby.h"
27 #include "ruby/node.h"
28 #include "vm.h"
29 #include "compiler.h"
30 #include "debugger.h"
31
32 void
33 RoxorCompiler::compile_debug_trap(void)
34 {
35 if (bb == NULL || inside_eval) {
36 return;
37 }
38
39 if (debugTrapFunc == NULL) {
40 // void rb_vm_debug_trap(const char *file, int line,
41 // VALUE self, rb_vm_block_t *current_block, int lvars_size, ...);
42 std::vector<const Type *> types;
43 types.push_back(PtrTy);
44 types.push_back(Int32Ty);
45 types.push_back(RubyObjTy);
46 types.push_back(PtrTy);
47 types.push_back(Int32Ty);
48 FunctionType *ft = FunctionType::get(VoidTy, types, true);
49 debugTrapFunc = cast<Function>
50 (module->getOrInsertFunction( "rb_vm_debug_trap", ft));
51 }
52
53 std::vector<Value *> params;
54 params.push_back(compile_const_pointer((void *)fname));
55 params.push_back(ConstantInt::get(Int32Ty, current_line));
56 params.push_back(current_self);
57 params.push_back(running_block == NULL
58 ? compile_const_pointer(NULL) : running_block);
59
60 // Lvars.
61 params.push_back(ConstantInt::get(Int32Ty, (int)lvars.size()));
62 for (std::map<ID, Value *>::iterator iter = lvars.begin();
63 iter != lvars.end(); ++iter) {
64
65 ID lvar = iter->first;
66 params.push_back(compile_id(lvar));
67 params.push_back(iter->second);
68 }
69
70 CallInst::Create(debugTrapFunc, params.begin(), params.end(), "", bb);
71 }
72
73 extern "C"
74 void
75 rb_vm_debug_trap(const char *file, const int line, VALUE self,
76 rb_vm_block_t *current_block, int lvars_size, ...)
77 {
78 if (RoxorDebugger::shared == NULL) {
79 RoxorDebugger::shared = RoxorDebugger::unix_server();
80 }
81
82 va_list args;
83 va_start(args, lvars_size);
84 RoxorDebugger::shared->trap(file, line, self, current_block,
85 lvars_size, args);
86 va_end(args);
87 }
88
89 unsigned int RoxorBreakPoint::IDs = 0;
90 RoxorDebugger *RoxorDebugger::shared = NULL;
91
92 RoxorDebugger *
93 RoxorDebugger::unix_server(void)
94 {
95 // Create the socket.
96 const int fd = socket(PF_LOCAL, SOCK_STREAM, 0);
97 if (fd < 0) {
98 perror("socket()");
99 exit(1);
100 }
101
102 // Bind it to the given path.
103 assert(ruby_debug_socket_path != Qfalse);
104 const char *path = RSTRING_PTR(ruby_debug_socket_path);
105 struct sockaddr_un name;
106 name.sun_family = PF_LOCAL;
107 strncpy(name.sun_path, path, sizeof(name.sun_path));
108 if (bind(fd, (struct sockaddr *)&name, SUN_LEN(&name)) != 0) {
109 perror("bind()");
110 exit(1);
111 }
112
113 // Prepare for listening, backlog of 1 connection.
114 if (listen(fd, 1) != 0) {
115 perror("listen()");
116 exit(1);
117 }
118
119 // Accept first connection.
120 struct sockaddr_un remote;
121 socklen_t remotelen = sizeof(remote);
122 const int pipe = accept(fd, (struct sockaddr *)&remote, &remotelen);
123 if (socket < 0) {
124 perror("accept()");
125 exit(1);
126 }
127
128 //printf("DBG: connected with fd %d\n", pipe);
129
130 return new RoxorDebugger(pipe);
131 }
132
133 RoxorDebugger::RoxorDebugger(int _pipe)
134 {
135 pipe = _pipe;
136 binding = NULL;
137 breakpoint = NULL;
138 frame = 0;
139
140 // We want to break at the very first line.
141 break_at_next = true;
142 }
143
144 RoxorDebugger::~RoxorDebugger()
145 {
146 close(pipe);
147 }
148
149 bool
150 RoxorDebugger::send(std::string &data)
151 {
152 //printf("DBG: send %s\n", data.c_str());
153 const ssize_t len = ::send(pipe, data.c_str(), data.length(), 0);
154 if (len != (ssize_t)data.length()) {
155 if (len == -1) {
156 perror("send()");
157 }
158 else {
159 fprintf(stderr, "expected to send %ld bytes instead of %ld\n",
160 data.length(), len);
161 }
162 return false;
163 }
164 return true;
165 }
166
167 bool
168 RoxorDebugger::recv(std::string &data)
169 {
170 char buf[512];
171 const ssize_t len = ::recv(pipe, buf, sizeof buf, 0);
172 if (len == -1) {
173 perror("recv()");
174 return false;
175 }
176 data.clear();
177 data.append(buf, len);
178 //printf("DBG: recv %s\n", data.c_str());
179 return true;
180 }
181
182 void
183 RoxorDebugger::trap(const char *file, const int line, VALUE self,
184 rb_vm_block_t *block, int lvars_size, va_list lvars)
185 {
186 // Compute location.
187 char buf[100];
188 snprintf(buf, sizeof buf, "%s:%d", file, line);
189 std::string loc(buf);
190
191 // Should we break here?
192 bool should_break = false;
193 RoxorBreakPoint *bp = NULL;
194 if (break_at_next) {
195 should_break = true;
196 break_at_next = false;
197 }
198 else {
199 std::map<std::string, RoxorBreakPoint *>::iterator iter =
200 breakpoints.find(loc);
201 if (iter != breakpoints.end()) {
202 bp = iter->second;
203 if (bp->enabled) {
204 if (bp->condition.length() > 0) {
205 VALUE obj = evaluate_expression(self, block, lvars_size,
206 lvars, bp->condition);
207 if (obj != Qnil && obj != Qfalse) {
208 should_break = true;
209 }
210 }
211 else {
212 should_break = true;
213 }
214 }
215 }
216 }
217
218 if (!should_break) {
219 return;
220 }
221
222 // Send the current location to the client.
223 if (!send(loc)) {
224 return;
225 }
226
227 // Invalidate latest binding.
228 if (binding != NULL) {
229 GC_RELEASE(binding);
230 binding = NULL;
231 }
232 frame = 0;
233
234 // Enter the main loop.
235 std::string data;
236 while (true) {
237 // Receive command.
238 if (!recv(data)) {
239 return;
240 }
241 const char *cmd = data.c_str();
242
243 // Handle command.
244 if (strcmp(cmd, "exit") == 0) {
245 exit(0);
246 }
247 if (strcmp(cmd, "continue") == 0) {
248 break;
249 }
250 if (strcmp(cmd, "next") == 0) {
251 break_at_next = true;
252 break;
253 }
254 if (strncmp(cmd, "break ", 6) == 0) {
255 std::string location = data.substr(6);
256 if (location.length() >= 3) {
257 unsigned int id = add_breakpoint(location);
258 char buf[10];
259 snprintf(buf, sizeof buf, "%d", id);
260 send(buf);
261 continue;
262 }
263 }
264 if (strncmp(cmd, "enable ", 7) == 0) {
265 const unsigned int bpid = (unsigned int)strtol(cmd + 7, NULL, 10);
266 RoxorBreakPoint *bp = find_breakpoint(bpid);
267 if (bp != NULL) {
268 bp->enabled = true;
269 }
270 continue;
271 }
272 if (strncmp(cmd, "disable ", 8) == 0) {
273 const unsigned int bpid = (unsigned int)strtol(cmd + 8, NULL, 10);
274 RoxorBreakPoint *bp = find_breakpoint(bpid);
275 if (bp != NULL) {
276 bp->enabled = false;
277 }
278 continue;
279 }
280 if (strncmp(cmd, "delete ", 7) == 0) {
281 const unsigned int bpid = (unsigned int)strtol(cmd + 7, NULL, 10);
282 delete_breakpoint(bpid);
283 continue;
284 }
285 if (strncmp(cmd, "condition ", 10) == 0) {
286 const char *p = strchr(cmd + 10, ' ');
287 if (p != NULL) {
288 std::string bpstr = data.substr(10, p - cmd - 10);
289 const unsigned int bpid = (unsigned int)strtol(bpstr.c_str(),
290 NULL, 10);
291 std::string condstr = data.substr(10 + bpstr.length());
292 RoxorBreakPoint *bp = find_breakpoint(bpid);
293 if (bp != NULL) {
294 bp->condition = condstr;
295 }
296 continue;
297 }
298 }
299 if (strcmp(cmd, "info breakpoints") == 0) {
300 std::string resp;
301 for (std::map<std::string, RoxorBreakPoint *>::iterator iter
302 = breakpoints.begin();
303 iter != breakpoints.end();
304 ++iter) {
305 char buf[100];
306 snprintf(buf, sizeof buf, "id=%d,location=%s,enabled=%d\n",
307 iter->second->id, iter->first.c_str(),
308 iter->second->enabled);
309 resp.append(buf);
310 }
311 if (resp.length() == 0) {
312 resp.append("nil");
313 }
314 send(resp);
315 continue;
316 }
317 if (strcmp(cmd, "backtrace") == 0) {
0849733d » Laurent Sansonetti
2010-02-14 Kernel#caller: now honor the skip argument
318 VALUE bt = rb_vm_backtrace(0);
cf73725a » Laurent Sansonetti
2010-01-17 experimental debugger
319 VALUE str = rb_ary_join(bt, rb_str_new2("\n"));
320 send(RSTRING_PTR(str));
321 continue;
322 }
323 if (strncmp(cmd, "frame ", 6) == 0) {
324 unsigned int frid = (unsigned int)strtol(cmd + 6, NULL, 10);
325 if (frame != frid) {
326 frame = frid;
327 if (binding != NULL) {
328 GC_RELEASE(binding);
329 binding = NULL;
330 }
331 // TODO update location
332 }
333 continue;
334 }
335 if (strncmp(cmd, "eval ", 5) == 0) {
336 std::string expr = data.substr(5);
337 if (expr.length() > 0) {
338 VALUE obj = evaluate_expression(self, block, lvars_size, lvars,
339 expr);
340 send(RSTRING_PTR(rb_inspect(obj)));
341 continue;
342 }
343 }
344
345 // Unknown command, let's exit the program. This should never happen!
346 fprintf(stderr, "unknown command: %s\n", cmd);
347 exit(1);
348 }
349 }
350
351 unsigned int
352 RoxorDebugger::add_breakpoint(std::string &location)
353 {
354 std::map<std::string, RoxorBreakPoint *>::iterator iter
355 = breakpoints.find(location);
356
357 RoxorBreakPoint *bp;
358 if (iter == breakpoints.end()) {
359 bp = new RoxorBreakPoint();
360 breakpoints[location] = bp;
361 }
362 else {
363 bp = iter->second;
364 }
365
366 return bp->id;
367 }
368
369 VALUE
370 RoxorDebugger::evaluate_expression(VALUE self, rb_vm_block_t *block,
371 int lvars_size, va_list lvars, std::string expr)
372 {
373 if (binding == NULL) {
374 if (frame == 0) {
375 binding = rb_vm_create_binding(self, block, lvars_size, lvars,
376 false);
377 }
378 else {
379 binding = GET_VM()->get_binding(frame - 1);
380 assert(binding != NULL);
381 }
382 GC_RETAIN(binding);
383 }
384
385 try {
386 return rb_vm_eval_string(self, 0, rb_str_new2(expr.c_str()), binding,
387 "(eval)", 1);
388 }
389 catch (...) {
390 rb_vm_print_current_exception();
391 return Qnil;
392 }
393 }
394
395 RoxorBreakPoint *
396 RoxorDebugger::find_breakpoint(unsigned int bpid)
397 {
398 for (std::map<std::string, RoxorBreakPoint *>::iterator iter
399 = breakpoints.begin();
400 iter != breakpoints.end();
401 ++iter) {
402 if (iter->second->id == bpid) {
403 return iter->second;
404 }
405 }
406 return NULL;
407 }
408
409 bool
410 RoxorDebugger::delete_breakpoint(unsigned int bpid)
411 {
412 for (std::map<std::string, RoxorBreakPoint *>::iterator iter
413 = breakpoints.begin();
414 iter != breakpoints.end();
415 ++iter) {
416 if (iter->second->id == bpid) {
417 breakpoints.erase(iter);
418 return true;
419 }
420 }
421 return false;
422 }
Something went wrong with that request. Please try again.