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