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 "TypeHandlerRoster.h" 27 #include "ValueHandlerRoster.h" 28 29 30 extern const char* __progname; 31 const char* kProgramName = __progname; 32 33 static const char* const kDebuggerSignature 34 = "application/x-vnd.Haiku-Debugger"; 35 36 37 static const char* kUsage = 38 "Usage: %s [ <options> ]\n" 39 " %s [ <options> ] <command line>\n" 40 " %s [ <options> ] --team <team>\n" 41 " %s [ <options> ] --thread <thread>\n" 42 "\n" 43 "The first form starts the debugger displaying a requester to choose a\n" 44 "running team to debug respectively to specify the program to run and\n" 45 "debug.\n" 46 "\n" 47 "The second form runs the given command line and attaches the debugger to\n" 48 "the new team. Unless specified otherwise the program will be stopped at\n" 49 "the beginning of its main() function.\n" 50 "\n" 51 "The third and fourth forms attach the debugger to a running team. The\n" 52 "fourth form additionally stops the specified thread.\n" 53 "\n" 54 "Options:\n" 55 " -h, --help - Print this usage info and exit.\n" 56 ; 57 58 59 static void 60 print_usage_and_exit(bool error) 61 { 62 fprintf(error ? stderr : stdout, kUsage, kProgramName, kProgramName, 63 kProgramName, kProgramName); 64 exit(error ? 1 : 0); 65 } 66 67 68 struct Options { 69 int commandLineArgc; 70 const char* const* commandLineArgv; 71 team_id team; 72 thread_id thread; 73 74 Options() 75 : 76 commandLineArgc(0), 77 commandLineArgv(NULL), 78 team(-1), 79 thread(-1) 80 { 81 } 82 }; 83 84 85 static bool 86 parse_arguments(int argc, const char* const* argv, bool noOutput, 87 Options& options) 88 { 89 optind = 1; 90 91 while (true) { 92 static struct option sLongOptions[] = { 93 { "help", no_argument, 0, 'h' }, 94 { "team", required_argument, 0, 't' }, 95 { "thread", required_argument, 0, 'T' }, 96 { 0, 0, 0, 0 } 97 }; 98 99 opterr = 0; // don't print errors 100 101 int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); 102 if (c == -1) 103 break; 104 105 switch (c) { 106 case 'h': 107 if (noOutput) 108 return false; 109 print_usage_and_exit(false); 110 break; 111 112 case 't': 113 { 114 options.team = strtol(optarg, NULL, 0); 115 if (options.team <= 0) { 116 if (noOutput) 117 return false; 118 print_usage_and_exit(true); 119 } 120 break; 121 } 122 123 case 'T': 124 { 125 options.thread = strtol(optarg, NULL, 0); 126 if (options.thread <= 0) { 127 if (noOutput) 128 return false; 129 print_usage_and_exit(true); 130 } 131 break; 132 } 133 134 default: 135 if (noOutput) 136 return false; 137 print_usage_and_exit(true); 138 break; 139 } 140 } 141 142 if (optind < argc) { 143 options.commandLineArgc = argc - optind; 144 options.commandLineArgv = argv + optind; 145 } 146 147 int exclusiveParams = 0; 148 if (options.team > 0) 149 exclusiveParams++; 150 if (options.thread > 0) 151 exclusiveParams++; 152 if (options.commandLineArgc > 0) 153 exclusiveParams++; 154 155 if (exclusiveParams == 0) { 156 // TODO: Support! 157 if (noOutput) 158 return false; 159 fprintf(stderr, "Sorry, running without team/thread to debug not " 160 "supported yet.\n"); 161 exit(1); 162 } else if (exclusiveParams != 1) { 163 if (noOutput) 164 return false; 165 print_usage_and_exit(true); 166 } 167 168 return true; 169 } 170 171 172 class Debugger : public BApplication, private TeamDebugger::Listener { 173 public: 174 Debugger() 175 : 176 BApplication(kDebuggerSignature), 177 fRunningTeamDebuggers(0) 178 { 179 } 180 181 ~Debugger() 182 { 183 ValueHandlerRoster::DeleteDefault(); 184 TypeHandlerRoster::DeleteDefault(); 185 } 186 187 status_t Init() 188 { 189 status_t error = TypeHandlerRoster::CreateDefault(); 190 if (error != B_OK) 191 return error; 192 193 error = ValueHandlerRoster::CreateDefault(); 194 if (error != B_OK) 195 return error; 196 197 return fSettingsManager.Init(); 198 } 199 200 virtual void MessageReceived(BMessage* message) 201 { 202 switch (message->what) { 203 case MSG_TEAM_DEBUGGER_QUIT: 204 { 205 int32 threadID; 206 if (message->FindInt32("thread", &threadID) == B_OK) 207 wait_for_thread(threadID, NULL); 208 209 if (--fRunningTeamDebuggers == 0) 210 Quit(); 211 break; 212 } 213 default: 214 BApplication::MessageReceived(message); 215 break; 216 } 217 } 218 219 virtual void ReadyToRun() 220 { 221 } 222 223 virtual void ArgvReceived(int32 argc, char** argv) 224 { 225 Options options; 226 if (!parse_arguments(argc, argv, true, options)) 227 { 228 printf("Debugger::ArgvReceived(): parsing args failed!\n"); 229 return; 230 } 231 232 team_id team = options.team; 233 thread_id thread = options.thread; 234 bool stopInMain = false; 235 236 // If command line arguments were given, start the program. 237 if (options.commandLineArgc > 0) { 238 printf("loading program: \"%s\" ...\n", options.commandLineArgv[0]); 239 // TODO: What about the CWD? 240 thread = load_program(options.commandLineArgv, 241 options.commandLineArgc, false); 242 if (thread < 0) { 243 // TODO: Notify the user! 244 fprintf(stderr, "Error: Failed to load program \"%s\": %s\n", 245 options.commandLineArgv[0], strerror(thread)); 246 return; 247 } 248 249 team = thread; 250 // main thread ID == team ID 251 stopInMain = true; 252 } 253 254 // If we've got 255 if (team < 0) { 256 printf("no team yet, getting thread info...\n"); 257 thread_info threadInfo; 258 status_t error = get_thread_info(thread, &threadInfo); 259 if (error != B_OK) { 260 // TODO: Notify the user! 261 fprintf(stderr, "Error: Failed to get info for thread \"%ld\": " 262 "%s\n", thread, strerror(error)); 263 return; 264 } 265 266 team = threadInfo.team; 267 } 268 printf("team: %ld, thread: %ld\n", team, thread); 269 270 TeamDebugger* debugger = _TeamDebuggerForTeam(team); 271 if (debugger != NULL) { 272 // TODO: Activate the respective window! 273 printf("There's already a debugger for team: %ld\n", team); 274 return; 275 } 276 277 UserInterface* userInterface = new(std::nothrow) GraphicalUserInterface; 278 if (userInterface == NULL) { 279 // TODO: Notify the user! 280 fprintf(stderr, "Error: Out of memory!\n"); 281 } 282 Reference<UserInterface> userInterfaceReference(userInterface, true); 283 284 debugger = new(std::nothrow) TeamDebugger(this, userInterface, 285 &fSettingsManager); 286 if (debugger == NULL) { 287 // TODO: Notify the user! 288 fprintf(stderr, "Error: Out of memory!\n"); 289 } 290 291 status_t error = debugger->Init(team, thread, stopInMain); 292 if (debugger->Thread()) 293 fRunningTeamDebuggers++; 294 295 if (error == B_OK && fTeamDebuggers.AddItem(debugger)) { 296 printf("debugger for team %ld created and initialized successfully!\n", team); 297 } else 298 delete debugger; 299 } 300 301 private: 302 typedef BObjectList<TeamDebugger> TeamDebuggerList; 303 304 private: 305 // TeamDebugger::Listener 306 virtual void TeamDebuggerQuit(TeamDebugger* debugger) 307 { 308 // Note: Locking here only works, since we're never locking the other 309 // way around. If we even need to do that, we'll have to introduce a 310 // separate lock to protect the list. 311 AutoLocker<Debugger> locker(this); 312 fTeamDebuggers.RemoveItem(debugger); 313 locker.Unlock(); 314 315 if (debugger->Thread() >= 0) { 316 BMessage message(MSG_TEAM_DEBUGGER_QUIT); 317 message.AddInt32("thread", debugger->Thread()); 318 PostMessage(&message); 319 } 320 } 321 322 virtual bool QuitRequested() 323 { 324 // NOTE: The default implementation will just ask all windows' 325 // QuitRequested() hooks. This in turn will ask the TeamWindows. 326 // For now, this is what we want. If we have more windows later, 327 // like the global TeamsWindow, then we want to just ask the 328 // TeamDebuggers, the TeamsWindow should of course not go away already 329 // if one or more TeamDebuggers want to stay later. There are multiple 330 // ways how to do this. For examaple, TeamDebugger could get a 331 // QuitReqested() hook or the TeamsWindow and other global windows 332 // could always return false in their QuitRequested(). 333 return BApplication::QuitRequested(); 334 // TODO: This is ugly. The team debuggers own the windows, not the 335 // other way around. 336 } 337 338 virtual void Quit() 339 { 340 // don't quit before all team debuggers have been quit 341 if (fRunningTeamDebuggers <= 0) 342 BApplication::Quit(); 343 } 344 345 TeamDebugger* _TeamDebuggerForTeam(team_id teamID) const 346 { 347 for (int32 i = 0; TeamDebugger* debugger = fTeamDebuggers.ItemAt(i); 348 i++) { 349 if (debugger->TeamID() == teamID) 350 return debugger; 351 } 352 353 return NULL; 354 } 355 356 private: 357 SettingsManager fSettingsManager; 358 TeamDebuggerList fTeamDebuggers; 359 int32 fRunningTeamDebuggers; 360 }; 361 362 363 int 364 main(int argc, const char* const* argv) 365 { 366 // We test-parse the arguments here, so, when we're started from the 367 // terminal and there's an instance already running, we can print an error 368 // message to the terminal, if something's wrong with the arguments. 369 { 370 Options options; 371 parse_arguments(argc, argv, false, options); 372 } 373 374 Debugger app; 375 status_t error = app.Init(); 376 if (error != B_OK) { 377 fprintf(stderr, "Error: Failed to init application: %s\n", 378 strerror(error)); 379 return 1; 380 } 381 382 app.Run(); 383 return 0; 384 } 385