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