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