1 /* 2 * Copyright 2012, Rene Gollent, rene@gollent.com. 3 * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "CliContext.h" 9 10 #include <AutoDeleter.h> 11 #include <AutoLocker.h> 12 13 #include "StackTrace.h" 14 #include "UserInterface.h" 15 #include "ValueNodeManager.h" 16 17 // NOTE: This is a simple work-around for EditLine not having any kind of user 18 // data field. Hence in _GetPrompt() we don't have access to the context object. 19 // ATM only one CLI is possible in Debugger, so a static variable works well 20 // enough. Should that ever change, we would need a thread-safe 21 // EditLine* -> CliContext* map. 22 static CliContext* sCurrentContext; 23 24 25 // #pragma mark - Event 26 27 28 struct CliContext::Event : DoublyLinkedListLinkImpl<CliContext::Event> { 29 Event(int type, Thread* thread = NULL, TeamMemoryBlock* block = NULL) 30 : 31 fType(type), 32 fThreadReference(thread), 33 fMemoryBlockReference(block) 34 { 35 } 36 37 int Type() const 38 { 39 return fType; 40 } 41 42 Thread* GetThread() const 43 { 44 return fThreadReference.Get(); 45 } 46 47 TeamMemoryBlock* GetMemoryBlock() const 48 { 49 return fMemoryBlockReference.Get(); 50 } 51 52 private: 53 int fType; 54 BReference<Thread> fThreadReference; 55 BReference<TeamMemoryBlock> fMemoryBlockReference; 56 }; 57 58 59 // #pragma mark - CliContext 60 61 62 CliContext::CliContext() 63 : 64 fLock("CliContext"), 65 fTeam(NULL), 66 fListener(NULL), 67 fNodeManager(NULL), 68 fEditLine(NULL), 69 fHistory(NULL), 70 fPrompt(NULL), 71 fBlockingSemaphore(-1), 72 fInputLoopWaitingForEvents(0), 73 fEventsOccurred(0), 74 fInputLoopWaiting(false), 75 fTerminating(false), 76 fCurrentThread(NULL), 77 fCurrentStackTrace(NULL), 78 fCurrentStackFrameIndex(-1), 79 fCurrentBlock(NULL) 80 { 81 sCurrentContext = this; 82 } 83 84 85 CliContext::~CliContext() 86 { 87 Cleanup(); 88 sCurrentContext = NULL; 89 90 if (fBlockingSemaphore >= 0) 91 delete_sem(fBlockingSemaphore); 92 } 93 94 95 status_t 96 CliContext::Init(Team* team, UserInterfaceListener* listener) 97 { 98 fTeam = team; 99 fListener = listener; 100 101 fTeam->AddListener(this); 102 103 status_t error = fLock.InitCheck(); 104 if (error != B_OK) 105 return error; 106 107 fBlockingSemaphore = create_sem(0, "CliContext block"); 108 if (fBlockingSemaphore < 0) 109 return fBlockingSemaphore; 110 111 fEditLine = el_init("Debugger", stdin, stdout, stderr); 112 if (fEditLine == NULL) 113 return B_ERROR; 114 115 fHistory = history_init(); 116 if (fHistory == NULL) 117 return B_ERROR; 118 119 HistEvent historyEvent; 120 history(fHistory, &historyEvent, H_SETSIZE, 100); 121 122 el_set(fEditLine, EL_HIST, &history, fHistory); 123 el_set(fEditLine, EL_EDITOR, "emacs"); 124 el_set(fEditLine, EL_PROMPT, &_GetPrompt); 125 126 fNodeManager = new(std::nothrow) ValueNodeManager(); 127 if (fNodeManager == NULL) 128 return B_NO_MEMORY; 129 fNodeManager->AddListener(this); 130 131 return B_OK; 132 } 133 134 135 void 136 CliContext::Cleanup() 137 { 138 Terminating(); 139 140 while (Event* event = fPendingEvents.RemoveHead()) 141 delete event; 142 143 if (fEditLine != NULL) { 144 el_end(fEditLine); 145 fEditLine = NULL; 146 } 147 148 if (fHistory != NULL) { 149 history_end(fHistory); 150 fHistory = NULL; 151 } 152 153 if (fTeam != NULL) { 154 fTeam->RemoveListener(this); 155 fTeam = NULL; 156 } 157 158 if (fNodeManager != NULL) { 159 fNodeManager->ReleaseReference(); 160 fNodeManager = NULL; 161 } 162 163 if (fCurrentBlock != NULL) { 164 fCurrentBlock->ReleaseReference(); 165 fCurrentBlock = NULL; 166 } 167 } 168 169 170 void 171 CliContext::Terminating() 172 { 173 AutoLocker<BLocker> locker(fLock); 174 175 fTerminating = true; 176 _SignalInputLoop(EVENT_QUIT); 177 178 // TODO: Signal the input loop, should it be in PromptUser()! 179 } 180 181 182 thread_id 183 CliContext::CurrentThreadID() const 184 { 185 return fCurrentThread != NULL ? fCurrentThread->ID() : -1; 186 } 187 188 189 void 190 CliContext::SetCurrentThread(Thread* thread) 191 { 192 AutoLocker<BLocker> locker(fLock); 193 194 if (fCurrentThread != NULL) 195 fCurrentThread->ReleaseReference(); 196 197 fCurrentThread = thread; 198 199 if (fCurrentStackTrace != NULL) { 200 fCurrentStackTrace->ReleaseReference(); 201 fCurrentStackTrace = NULL; 202 fCurrentStackFrameIndex = -1; 203 fNodeManager->SetStackFrame(NULL, NULL); 204 } 205 206 if (fCurrentThread != NULL) { 207 fCurrentThread->AcquireReference(); 208 StackTrace* stackTrace = fCurrentThread->GetStackTrace(); 209 // if the thread's stack trace has already been loaded, 210 // set it, otherwise we'll set it when we process the thread's 211 // stack trace changed event. 212 if (stackTrace != NULL) { 213 fCurrentStackTrace = stackTrace; 214 fCurrentStackTrace->AcquireReference(); 215 SetCurrentStackFrameIndex(0); 216 } 217 } 218 } 219 220 221 void 222 CliContext::PrintCurrentThread() 223 { 224 AutoLocker<Team> teamLocker(fTeam); 225 226 if (fCurrentThread != NULL) { 227 printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(), 228 fCurrentThread->Name()); 229 } else 230 printf("no current thread\n"); 231 } 232 233 234 void 235 CliContext::SetCurrentStackFrameIndex(int32 index) 236 { 237 AutoLocker<BLocker> locker(fLock); 238 239 if (fCurrentStackTrace == NULL) 240 return; 241 else if (index < 0 || index >= fCurrentStackTrace->CountFrames()) 242 return; 243 244 fCurrentStackFrameIndex = index; 245 246 StackFrame* frame = fCurrentStackTrace->FrameAt(index); 247 if (frame != NULL) 248 fNodeManager->SetStackFrame(fCurrentThread, frame); 249 } 250 251 252 const char* 253 CliContext::PromptUser(const char* prompt) 254 { 255 fPrompt = prompt; 256 257 int count; 258 const char* line = el_gets(fEditLine, &count); 259 260 fPrompt = NULL; 261 262 ProcessPendingEvents(); 263 264 return line; 265 } 266 267 268 void 269 CliContext::AddLineToInputHistory(const char* line) 270 { 271 HistEvent historyEvent; 272 history(fHistory, &historyEvent, H_ENTER, line); 273 } 274 275 276 void 277 CliContext::QuitSession(bool killTeam) 278 { 279 _PrepareToWaitForEvents(EVENT_QUIT); 280 281 fListener->UserInterfaceQuitRequested( 282 killTeam 283 ? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM 284 : UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM); 285 286 _WaitForEvents(); 287 } 288 289 290 void 291 CliContext::WaitForThreadOrUser() 292 { 293 ProcessPendingEvents(); 294 295 // TODO: Deal with SIGINT as well! 296 for (;;) { 297 _PrepareToWaitForEvents( 298 EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED); 299 300 // check whether there are any threads stopped already 301 Thread* stoppedThread = NULL; 302 BReference<Thread> stoppedThreadReference; 303 304 AutoLocker<Team> teamLocker(fTeam); 305 306 for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator(); 307 Thread* thread = it.Next();) { 308 if (thread->State() == THREAD_STATE_STOPPED) { 309 stoppedThread = thread; 310 stoppedThreadReference.SetTo(thread); 311 break; 312 } 313 } 314 315 teamLocker.Unlock(); 316 317 if (stoppedThread != NULL) { 318 if (fCurrentThread == NULL) 319 SetCurrentThread(stoppedThread); 320 321 _SignalInputLoop(EVENT_THREAD_STOPPED); 322 } 323 324 uint32 events = _WaitForEvents(); 325 if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) { 326 ProcessPendingEvents(); 327 return; 328 } 329 } 330 } 331 332 333 void 334 CliContext::WaitForEvents(int32 eventMask) 335 { 336 for (;;) { 337 _PrepareToWaitForEvents(eventMask | EVENT_USER_INTERRUPT); 338 uint32 events = fEventsOccurred; 339 if ((events & eventMask) == 0) { 340 events = _WaitForEvents(); 341 } 342 343 if ((events & EVENT_QUIT) != 0 || (events & eventMask) != 0) { 344 _SignalInputLoop(eventMask); 345 ProcessPendingEvents(); 346 return; 347 } 348 } 349 } 350 351 352 void 353 CliContext::ProcessPendingEvents() 354 { 355 AutoLocker<Team> teamLocker(fTeam); 356 357 for (;;) { 358 // get the next event 359 AutoLocker<BLocker> locker(fLock); 360 Event* event = fPendingEvents.RemoveHead(); 361 locker.Unlock(); 362 if (event == NULL) 363 break; 364 ObjectDeleter<Event> eventDeleter(event); 365 366 // process the event 367 Thread* thread = event->GetThread(); 368 369 switch (event->Type()) { 370 case EVENT_QUIT: 371 case EVENT_USER_INTERRUPT: 372 break; 373 case EVENT_THREAD_ADDED: 374 printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(), 375 thread->Name()); 376 break; 377 case EVENT_THREAD_REMOVED: 378 printf("[thread terminated: %" B_PRId32 " \"%s\"]\n", 379 thread->ID(), thread->Name()); 380 break; 381 case EVENT_THREAD_STOPPED: 382 printf("[thread stopped: %" B_PRId32 " \"%s\"]\n", 383 thread->ID(), thread->Name()); 384 break; 385 case EVENT_THREAD_STACK_TRACE_CHANGED: 386 if (thread == fCurrentThread) { 387 fCurrentStackTrace = thread->GetStackTrace(); 388 fCurrentStackTrace->AcquireReference(); 389 SetCurrentStackFrameIndex(0); 390 } 391 break; 392 case EVENT_TEAM_MEMORY_BLOCK_RETRIEVED: 393 if (fCurrentBlock != NULL) { 394 fCurrentBlock->ReleaseReference(); 395 fCurrentBlock = NULL; 396 } 397 fCurrentBlock = event->GetMemoryBlock(); 398 break; 399 } 400 } 401 } 402 403 404 void 405 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent) 406 { 407 _QueueEvent( 408 new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread())); 409 _SignalInputLoop(EVENT_THREAD_ADDED); 410 } 411 412 413 void 414 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent) 415 { 416 _QueueEvent( 417 new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread())); 418 _SignalInputLoop(EVENT_THREAD_REMOVED); 419 } 420 421 422 void 423 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent) 424 { 425 if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED) 426 return; 427 428 _QueueEvent( 429 new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread())); 430 _SignalInputLoop(EVENT_THREAD_STOPPED); 431 } 432 433 434 void 435 CliContext::ThreadStackTraceChanged(const Team::ThreadEvent& threadEvent) 436 { 437 if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED) 438 return; 439 440 _QueueEvent( 441 new(std::nothrow) Event(EVENT_THREAD_STACK_TRACE_CHANGED, 442 threadEvent.GetThread())); 443 _SignalInputLoop(EVENT_THREAD_STACK_TRACE_CHANGED); 444 } 445 446 447 void 448 CliContext::MemoryBlockRetrieved(TeamMemoryBlock* block) 449 { 450 _QueueEvent( 451 new(std::nothrow) Event(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED, 452 NULL, block)); 453 _SignalInputLoop(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED); 454 } 455 456 457 void 458 CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode, 459 ValueNode* newNode) 460 { 461 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 462 } 463 464 465 void 466 CliContext::ValueNodeChildrenCreated(ValueNode* node) 467 { 468 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 469 } 470 471 472 void 473 CliContext::ValueNodeChildrenDeleted(ValueNode* node) 474 { 475 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 476 } 477 478 479 void 480 CliContext::ValueNodeValueChanged(ValueNode* oldNode) 481 { 482 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 483 } 484 485 486 void 487 CliContext::_QueueEvent(Event* event) 488 { 489 if (event == NULL) { 490 // no memory -- can't do anything about it 491 return; 492 } 493 494 AutoLocker<BLocker> locker(fLock); 495 fPendingEvents.Add(event); 496 } 497 498 499 void 500 CliContext::_PrepareToWaitForEvents(uint32 eventMask) 501 { 502 // Set the events we're going to wait for -- always wait for "quit". 503 AutoLocker<BLocker> locker(fLock); 504 fInputLoopWaitingForEvents = eventMask | EVENT_QUIT; 505 fEventsOccurred = fTerminating ? EVENT_QUIT : 0; 506 } 507 508 509 uint32 510 CliContext::_WaitForEvents() 511 { 512 AutoLocker<BLocker> locker(fLock); 513 514 if (fEventsOccurred == 0) { 515 sem_id blockingSemaphore = fBlockingSemaphore; 516 fInputLoopWaiting = true; 517 518 locker.Unlock(); 519 520 while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) { 521 } 522 523 locker.Lock(); 524 } 525 526 uint32 events = fEventsOccurred; 527 fEventsOccurred = 0; 528 return events; 529 } 530 531 532 void 533 CliContext::_SignalInputLoop(uint32 events) 534 { 535 AutoLocker<BLocker> locker(fLock); 536 537 if ((fInputLoopWaitingForEvents & events) == 0) 538 return; 539 540 fEventsOccurred = fInputLoopWaitingForEvents & events; 541 fInputLoopWaitingForEvents = 0; 542 543 if (fInputLoopWaiting) { 544 fInputLoopWaiting = false; 545 release_sem(fBlockingSemaphore); 546 } 547 } 548 549 550 /*static*/ const char* 551 CliContext::_GetPrompt(EditLine* editLine) 552 { 553 return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL; 554 } 555