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