1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2011, Rene Gollent, rene@gollent.com. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include <getopt.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 13 #include <new> 14 15 #include <Application.h> 16 #include <Message.h> 17 18 #include <AutoLocker.h> 19 #include <ObjectList.h> 20 21 #include "debug_utils.h" 22 23 #include "CommandLineUserInterface.h" 24 #include "GraphicalUserInterface.h" 25 #include "MessageCodes.h" 26 #include "SettingsManager.h" 27 #include "TeamDebugger.h" 28 #include "TeamsWindow.h" 29 #include "TypeHandlerRoster.h" 30 #include "ValueHandlerRoster.h" 31 32 33 extern const char* __progname; 34 const char* kProgramName = __progname; 35 36 static const char* const kDebuggerSignature 37 = "application/x-vnd.Haiku-Debugger"; 38 39 40 static const char* kUsage = 41 "Usage: %s [ <options> ]\n" 42 " %s [ <options> ] <command line>\n" 43 " %s [ <options> ] --team <team>\n" 44 " %s [ <options> ] --thread <thread>\n" 45 "\n" 46 "The first form starts the debugger displaying a requester to choose a\n" 47 "running team to debug respectively to specify the program to run and\n" 48 "debug.\n" 49 "\n" 50 "The second form runs the given command line and attaches the debugger to\n" 51 "the new team. Unless specified otherwise the program will be stopped at\n" 52 "the beginning of its main() function.\n" 53 "\n" 54 "The third and fourth forms attach the debugger to a running team. The\n" 55 "fourth form additionally stops the specified thread.\n" 56 "\n" 57 "Options:\n" 58 " -h, --help - Print this usage info and exit.\n" 59 " -c, --cli - Use command line user interface (not yet implemented)\n" 60 ; 61 62 63 static void 64 print_usage_and_exit(bool error) 65 { 66 fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName, 67 kProgramName, kProgramName); 68 exit(error ? 1 : 0); 69 } 70 71 72 struct Options { 73 int commandLineArgc; 74 const char* const* commandLineArgv; 75 team_id team; 76 thread_id thread; 77 bool useCLI; 78 79 Options() 80 : 81 commandLineArgc(0), 82 commandLineArgv(NULL), 83 team(-1), 84 thread(-1), 85 useCLI(false) 86 { 87 } 88 }; 89 90 91 static bool 92 parse_arguments(int argc, const char* const* argv, bool noOutput, 93 Options& options) 94 { 95 optind = 1; 96 97 while (true) { 98 static struct option sLongOptions[] = { 99 { "help", no_argument, 0, 'h' }, 100 { "team", required_argument, 0, 't' }, 101 { "thread", required_argument, 0, 'T' }, 102 { "cli", no_argument, 0, 'c' }, 103 { 0, 0, 0, 0 } 104 }; 105 106 opterr = 0; // don't print errors 107 108 int c = getopt_long(argc, (char**)argv, "+ch", sLongOptions, NULL); 109 if (c == -1) 110 break; 111 112 switch (c) { 113 case 'c': 114 options.useCLI = true; 115 break; 116 117 case 'h': 118 if (noOutput) 119 return false; 120 print_usage_and_exit(false); 121 break; 122 123 case 't': 124 { 125 options.team = strtol(optarg, NULL, 0); 126 if (options.team <= 0) { 127 if (noOutput) 128 return false; 129 print_usage_and_exit(true); 130 } 131 break; 132 } 133 134 case 'T': 135 { 136 options.thread = strtol(optarg, NULL, 0); 137 if (options.thread <= 0) { 138 if (noOutput) 139 return false; 140 print_usage_and_exit(true); 141 } 142 break; 143 } 144 145 default: 146 if (noOutput) 147 return false; 148 print_usage_and_exit(true); 149 break; 150 } 151 } 152 153 if (optind < argc) { 154 options.commandLineArgc = argc - optind; 155 options.commandLineArgv = argv + optind; 156 } 157 158 int exclusiveParams = 0; 159 if (options.team > 0) 160 exclusiveParams++; 161 if (options.thread > 0) 162 exclusiveParams++; 163 if (options.commandLineArgc > 0) 164 exclusiveParams++; 165 166 if (exclusiveParams == 0) { 167 return true; 168 } else if (exclusiveParams != 1) { 169 if (noOutput) 170 return false; 171 print_usage_and_exit(true); 172 } 173 174 return true; 175 } 176 177 178 static TeamDebugger* 179 start_team_debugger(team_id teamID, SettingsManager* settingsManager, 180 TeamDebugger::Listener* listener, thread_id threadID = -1, 181 bool stopInMain = false, bool useCLI = false) 182 { 183 if (teamID < 0) 184 return NULL; 185 186 UserInterface* userInterface = useCLI 187 ? (UserInterface*)new(std::nothrow) CommandLineUserInterface 188 : (UserInterface*)new(std::nothrow) GraphicalUserInterface; 189 190 if (userInterface == NULL) { 191 // TODO: Notify the user! 192 fprintf(stderr, "Error: Out of memory!\n"); 193 return NULL; 194 } 195 BReference<UserInterface> userInterfaceReference(userInterface, true); 196 197 status_t error = B_NO_MEMORY; 198 199 TeamDebugger* debugger = new(std::nothrow) TeamDebugger(listener, 200 userInterface, settingsManager); 201 if (debugger) 202 error = debugger->Init(teamID, threadID, stopInMain); 203 204 if (error != B_OK) { 205 printf("Error: debugger for team %ld failed to init: %s!\n", 206 teamID, strerror(error)); 207 delete debugger; 208 return NULL; 209 } else 210 printf("debugger for team %ld created and initialized successfully!\n", 211 teamID); 212 213 return debugger; 214 } 215 216 // #pragma mark - Debugger application class 217 218 219 class Debugger : public BApplication, private TeamDebugger::Listener { 220 public: 221 Debugger(); 222 ~Debugger(); 223 224 status_t Init(); 225 virtual void MessageReceived(BMessage* message); 226 virtual void ReadyToRun(); 227 virtual void ArgvReceived(int32 argc, char** argv); 228 229 private: 230 typedef BObjectList<TeamDebugger> TeamDebuggerList; 231 232 private: 233 // TeamDebugger::Listener 234 virtual void TeamDebuggerStarted(TeamDebugger* debugger); 235 virtual void TeamDebuggerQuit(TeamDebugger* debugger); 236 237 virtual bool QuitRequested(); 238 virtual void Quit(); 239 240 TeamDebugger* _FindTeamDebugger(team_id teamID) const; 241 242 private: 243 SettingsManager fSettingsManager; 244 TeamDebuggerList fTeamDebuggers; 245 int32 fRunningTeamDebuggers; 246 TeamsWindow* fTeamsWindow; 247 }; 248 249 250 Debugger::Debugger() 251 : 252 BApplication(kDebuggerSignature), 253 fRunningTeamDebuggers(0), 254 fTeamsWindow(NULL) 255 { 256 } 257 258 259 Debugger::~Debugger() 260 { 261 ValueHandlerRoster::DeleteDefault(); 262 TypeHandlerRoster::DeleteDefault(); 263 } 264 265 266 status_t 267 Debugger::Init() 268 { 269 status_t error = TypeHandlerRoster::CreateDefault(); 270 if (error != B_OK) 271 return error; 272 273 error = ValueHandlerRoster::CreateDefault(); 274 if (error != B_OK) 275 return error; 276 277 return fSettingsManager.Init(); 278 } 279 280 281 void 282 Debugger::MessageReceived(BMessage* message) 283 { 284 switch (message->what) { 285 case MSG_SHOW_TEAMS_WINDOW: 286 { 287 if (fTeamsWindow) { 288 fTeamsWindow->Activate(true); 289 break; 290 } 291 292 try { 293 fTeamsWindow = TeamsWindow::Create(&fSettingsManager); 294 if (fTeamsWindow != NULL) 295 fTeamsWindow->Show(); 296 } catch (...) { 297 // TODO: Notify the user! 298 fprintf(stderr, "Error: Failed to create Teams window\n"); 299 } 300 break; 301 } 302 case MSG_TEAMS_WINDOW_CLOSED: 303 { 304 fTeamsWindow = NULL; 305 Quit(); 306 break; 307 } 308 case MSG_DEBUG_THIS_TEAM: 309 { 310 int32 teamID; 311 if (message->FindInt32("team", &teamID) != B_OK) 312 break; 313 314 start_team_debugger(teamID, &fSettingsManager, this); 315 break; 316 } 317 case MSG_TEAM_DEBUGGER_QUIT: 318 { 319 int32 threadID; 320 if (message->FindInt32("thread", &threadID) == B_OK) 321 wait_for_thread(threadID, NULL); 322 323 --fRunningTeamDebuggers; 324 Quit(); 325 break; 326 } 327 default: 328 BApplication::MessageReceived(message); 329 break; 330 } 331 } 332 333 334 void 335 Debugger::ReadyToRun() 336 { 337 if (fRunningTeamDebuggers == 0) 338 PostMessage(MSG_SHOW_TEAMS_WINDOW); 339 } 340 341 342 void 343 Debugger::ArgvReceived(int32 argc, char** argv) 344 { 345 Options options; 346 if (!parse_arguments(argc, argv, true, options)) { 347 printf("Debugger::ArgvReceived(): parsing args failed!\n"); 348 return; 349 } 350 351 team_id team = options.team; 352 thread_id thread = options.thread; 353 bool stopInMain = false; 354 355 // If command line arguments were given, start the program. 356 if (options.commandLineArgc > 0) { 357 printf("loading program: \"%s\" ...\n", options.commandLineArgv[0]); 358 // TODO: What about the CWD? 359 thread = load_program(options.commandLineArgv, 360 options.commandLineArgc, false); 361 if (thread < 0) { 362 // TODO: Notify the user! 363 fprintf(stderr, "Error: Failed to load program \"%s\": %s\n", 364 options.commandLineArgv[0], strerror(thread)); 365 return; 366 } 367 368 team = thread; 369 // main thread ID == team ID 370 stopInMain = true; 371 } 372 373 // no parameters given, prompt the user to attach to a team 374 if (team < 0 && thread < 0) 375 return; 376 377 // If we've got 378 if (team < 0) { 379 printf("no team yet, getting thread info...\n"); 380 thread_info threadInfo; 381 status_t error = get_thread_info(thread, &threadInfo); 382 if (error != B_OK) { 383 // TODO: Notify the user! 384 fprintf(stderr, "Error: Failed to get info for thread \"%ld\": " 385 "%s\n", thread, strerror(error)); 386 return; 387 } 388 389 team = threadInfo.team; 390 } 391 printf("team: %ld, thread: %ld\n", team, thread); 392 393 TeamDebugger* debugger = _FindTeamDebugger(team); 394 if (debugger != NULL) { 395 printf("There's already a debugger for team: %ld\n", team); 396 debugger->Activate(); 397 return; 398 } 399 400 start_team_debugger(team, &fSettingsManager, this, thread, stopInMain); 401 } 402 403 404 // TeamDebugger::Listener 405 406 407 void 408 Debugger::TeamDebuggerStarted(TeamDebugger* debugger) 409 { 410 printf("debugger for team %ld started...\n", 411 debugger->TeamID()); 412 413 // Note: see TeamDebuggerQuit() note about locking 414 AutoLocker<Debugger> locker(this); 415 fTeamDebuggers.AddItem(debugger); 416 fRunningTeamDebuggers++; 417 locker.Unlock(); 418 } 419 420 421 void 422 Debugger::TeamDebuggerQuit(TeamDebugger* debugger) 423 { 424 // Note: Locking here only works, since we're never locking the other 425 // way around. If we even need to do that, we'll have to introduce a 426 // separate lock to protect the list. 427 428 printf("debugger for team %ld quit.\n", 429 debugger->TeamID()); 430 431 AutoLocker<Debugger> locker(this); 432 fTeamDebuggers.RemoveItem(debugger); 433 locker.Unlock(); 434 435 if (debugger->Thread() >= 0) { 436 BMessage message(MSG_TEAM_DEBUGGER_QUIT); 437 message.AddInt32("thread", debugger->Thread()); 438 PostMessage(&message); 439 } 440 } 441 442 443 bool 444 Debugger::QuitRequested() 445 { 446 // NOTE: The default implementation will just ask all windows' 447 // QuitRequested() hooks. This in turn will ask the TeamWindows. 448 // For now, this is what we want. If we have more windows later, 449 // like the global TeamsWindow, then we want to just ask the 450 // TeamDebuggers, the TeamsWindow should of course not go away already 451 // if one or more TeamDebuggers want to stay later. There are multiple 452 // ways how to do this. For example, TeamDebugger could get a 453 // QuitRequested() hook or the TeamsWindow and other global windows 454 // could always return false in their QuitRequested(). 455 return BApplication::QuitRequested(); 456 // TODO: This is ugly. The team debuggers own the windows, not the 457 // other way around. 458 } 459 460 void 461 Debugger::Quit() 462 { 463 // don't quit before all team debuggers have been quit 464 if (fRunningTeamDebuggers <= 0 && fTeamsWindow == NULL) 465 BApplication::Quit(); 466 } 467 468 469 TeamDebugger* 470 Debugger::_FindTeamDebugger(team_id teamID) const 471 { 472 for (int32 i = 0; TeamDebugger* debugger = fTeamDebuggers.ItemAt(i); 473 i++) { 474 if (debugger->TeamID() == teamID) 475 return debugger; 476 } 477 478 return NULL; 479 } 480 481 482 // #pragma mark - 483 484 int 485 main(int argc, const char* const* argv) 486 { 487 // We test-parse the arguments here, so that, when we're started from the 488 // terminal and there's an instance already running, we can print an error 489 // message to the terminal, if something's wrong with the arguments. 490 // Otherwise, the arguments are reparsed in the actual application, 491 // unless the option to use the command line interface was chosen. 492 493 Options options; 494 parse_arguments(argc, argv, false, options); 495 496 if (options.useCLI) { 497 // TODO: implement 498 fprintf(stderr, "Error: Command line interface unimplemented\n"); 499 return 1; 500 } else { 501 Debugger app; 502 status_t error = app.Init(); 503 if (error != B_OK) { 504 fprintf(stderr, "Error: Failed to init application: %s\n", 505 strerror(error)); 506 return 1; 507 } 508 509 app.Run(); 510 } 511 return 0; 512 } 513