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 ExpressionInfo* info = NULL, status_t expressionResult = B_OK, 32 Value* expressionValue = NULL) 33 : 34 fType(type), 35 fThreadReference(thread), 36 fMemoryBlockReference(block), 37 fExpressionInfo(info), 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 ExpressionInfo* GetExpressionInfo() const 59 { 60 return fExpressionInfo; 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 BReference<ExpressionInfo> fExpressionInfo; 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 fExpressionInfo(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 fExpressionInfo = new(std::nothrow) ExpressionInfo(); 161 if (fExpressionInfo == NULL) 162 return B_NO_MEMORY; 163 fExpressionInfo->AddListener(this); 164 165 return B_OK; 166 } 167 168 169 void 170 CliContext::Cleanup() 171 { 172 Terminating(); 173 174 while (Event* event = fPendingEvents.RemoveHead()) 175 delete event; 176 177 if (fEditLine != NULL) { 178 el_end(fEditLine); 179 fEditLine = NULL; 180 } 181 182 if (fHistory != NULL) { 183 history_end(fHistory); 184 fHistory = NULL; 185 } 186 187 if (fTeam != NULL) { 188 fTeam->RemoveListener(this); 189 fTeam = NULL; 190 } 191 192 if (fNodeManager != NULL) { 193 fNodeManager->ReleaseReference(); 194 fNodeManager = NULL; 195 } 196 197 if (fCurrentBlock != NULL) { 198 fCurrentBlock->ReleaseReference(); 199 fCurrentBlock = NULL; 200 } 201 202 if (fExpressionInfo != NULL) { 203 fExpressionInfo->ReleaseReference(); 204 fExpressionInfo = NULL; 205 } 206 } 207 208 209 void 210 CliContext::Terminating() 211 { 212 AutoLocker<BLocker> locker(fLock); 213 214 fTerminating = true; 215 _SignalInputLoop(EVENT_QUIT); 216 217 // TODO: Signal the input loop, should it be in PromptUser()! 218 } 219 220 221 void 222 CliContext::SetInteractive(bool interactive) 223 { 224 fInteractive = interactive; 225 } 226 227 228 thread_id 229 CliContext::CurrentThreadID() const 230 { 231 return fCurrentThread != NULL ? fCurrentThread->ID() : -1; 232 } 233 234 235 void 236 CliContext::SetCurrentThread(Thread* thread) 237 { 238 AutoLocker<BLocker> locker(fLock); 239 240 if (fCurrentThread != NULL) 241 fCurrentThread->ReleaseReference(); 242 243 fCurrentThread = thread; 244 245 if (fCurrentStackTrace != NULL) { 246 fCurrentStackTrace->ReleaseReference(); 247 fCurrentStackTrace = NULL; 248 fCurrentStackFrameIndex = -1; 249 fNodeManager->SetStackFrame(NULL, NULL); 250 } 251 252 if (fCurrentThread != NULL) { 253 fCurrentThread->AcquireReference(); 254 StackTrace* stackTrace = fCurrentThread->GetStackTrace(); 255 // if the thread's stack trace has already been loaded, 256 // set it, otherwise we'll set it when we process the thread's 257 // stack trace changed event. 258 if (stackTrace != NULL) { 259 fCurrentStackTrace = stackTrace; 260 fCurrentStackTrace->AcquireReference(); 261 SetCurrentStackFrameIndex(0); 262 } 263 } 264 } 265 266 267 void 268 CliContext::PrintCurrentThread() 269 { 270 AutoLocker<Team> teamLocker(fTeam); 271 272 if (fCurrentThread != NULL) { 273 printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(), 274 fCurrentThread->Name()); 275 } else 276 printf("no current thread\n"); 277 } 278 279 280 void 281 CliContext::SetCurrentStackFrameIndex(int32 index) 282 { 283 AutoLocker<BLocker> locker(fLock); 284 285 if (fCurrentStackTrace == NULL) 286 return; 287 else if (index < 0 || index >= fCurrentStackTrace->CountFrames()) 288 return; 289 290 fCurrentStackFrameIndex = index; 291 292 StackFrame* frame = fCurrentStackTrace->FrameAt(index); 293 if (frame != NULL) 294 fNodeManager->SetStackFrame(fCurrentThread, frame); 295 } 296 297 298 const char* 299 CliContext::PromptUser(const char* prompt) 300 { 301 fPrompt = prompt; 302 303 int count; 304 const char* line = el_gets(fEditLine, &count); 305 306 fPrompt = NULL; 307 308 ProcessPendingEvents(); 309 310 return line; 311 } 312 313 314 void 315 CliContext::AddLineToInputHistory(const char* line) 316 { 317 HistEvent historyEvent; 318 history(fHistory, &historyEvent, H_ENTER, line); 319 } 320 321 322 void 323 CliContext::QuitSession(bool killTeam) 324 { 325 _PrepareToWaitForEvents(EVENT_QUIT); 326 327 fListener->UserInterfaceQuitRequested( 328 killTeam 329 ? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM 330 : UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM); 331 332 _WaitForEvents(); 333 } 334 335 336 void 337 CliContext::WaitForThreadOrUser() 338 { 339 ProcessPendingEvents(); 340 341 // TODO: Deal with SIGINT as well! 342 for (;;) { 343 _PrepareToWaitForEvents( 344 EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED); 345 346 // check whether there are any threads stopped already 347 Thread* stoppedThread = NULL; 348 BReference<Thread> stoppedThreadReference; 349 350 AutoLocker<Team> teamLocker(fTeam); 351 352 for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator(); 353 Thread* thread = it.Next();) { 354 if (thread->State() == THREAD_STATE_STOPPED) { 355 stoppedThread = thread; 356 stoppedThreadReference.SetTo(thread); 357 break; 358 } 359 } 360 361 teamLocker.Unlock(); 362 363 if (stoppedThread != NULL) { 364 if (fCurrentThread == NULL) 365 SetCurrentThread(stoppedThread); 366 367 _SignalInputLoop(EVENT_THREAD_STOPPED); 368 } 369 370 uint32 events = _WaitForEvents(); 371 if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) { 372 ProcessPendingEvents(); 373 return; 374 } 375 } 376 } 377 378 379 void 380 CliContext::WaitForEvents(int32 eventMask) 381 { 382 for (;;) { 383 _PrepareToWaitForEvents(eventMask | EVENT_USER_INTERRUPT); 384 uint32 events = fEventsOccurred; 385 if ((events & eventMask) == 0) { 386 events = _WaitForEvents(); 387 } 388 389 if ((events & EVENT_QUIT) != 0 || (events & eventMask) != 0) { 390 _SignalInputLoop(eventMask); 391 ProcessPendingEvents(); 392 return; 393 } 394 } 395 } 396 397 398 void 399 CliContext::ProcessPendingEvents() 400 { 401 AutoLocker<Team> teamLocker(fTeam); 402 403 for (;;) { 404 // get the next event 405 AutoLocker<BLocker> locker(fLock); 406 Event* event = fPendingEvents.RemoveHead(); 407 locker.Unlock(); 408 if (event == NULL) 409 break; 410 ObjectDeleter<Event> eventDeleter(event); 411 412 // process the event 413 Thread* thread = event->GetThread(); 414 415 switch (event->Type()) { 416 case EVENT_QUIT: 417 case EVENT_USER_INTERRUPT: 418 break; 419 case EVENT_THREAD_ADDED: 420 printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(), 421 thread->Name()); 422 break; 423 case EVENT_THREAD_REMOVED: 424 printf("[thread terminated: %" B_PRId32 " \"%s\"]\n", 425 thread->ID(), thread->Name()); 426 break; 427 case EVENT_THREAD_STOPPED: 428 printf("[thread stopped: %" B_PRId32 " \"%s\"]\n", 429 thread->ID(), thread->Name()); 430 break; 431 case EVENT_THREAD_STACK_TRACE_CHANGED: 432 if (thread == fCurrentThread) { 433 fCurrentStackTrace = thread->GetStackTrace(); 434 fCurrentStackTrace->AcquireReference(); 435 SetCurrentStackFrameIndex(0); 436 } 437 break; 438 case EVENT_TEAM_MEMORY_BLOCK_RETRIEVED: 439 if (fCurrentBlock != NULL) { 440 fCurrentBlock->ReleaseReference(); 441 fCurrentBlock = NULL; 442 } 443 fCurrentBlock = event->GetMemoryBlock(); 444 break; 445 case EVENT_EXPRESSION_EVALUATED: 446 fExpressionResult = event->GetExpressionResult(); 447 if (fExpressionValue != NULL) { 448 fExpressionValue->ReleaseReference(); 449 fExpressionValue = NULL; 450 } 451 fExpressionValue = event->GetExpressionValue(); 452 if (fExpressionValue != NULL) 453 fExpressionValue->AcquireReference(); 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(ExpressionInfo* info, status_t result, 512 Value* value) 513 { 514 _QueueEvent( 515 new(std::nothrow) Event(EVENT_EXPRESSION_EVALUATED, 516 NULL, NULL, info, result, value)); 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