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. 22static CliContext* sCurrentContext; 23 24 25// #pragma mark - Event 26 27 28struct 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 52private: 53 int fType; 54 BReference<Thread> fThreadReference; 55 BReference<TeamMemoryBlock> fMemoryBlockReference; 56}; 57 58 59// #pragma mark - CliContext 60 61 62CliContext::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 85CliContext::~CliContext() 86{ 87 Cleanup(); 88 sCurrentContext = NULL; 89 90 if (fBlockingSemaphore >= 0) 91 delete_sem(fBlockingSemaphore); 92} 93 94 95status_t 96CliContext::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 135void 136CliContext::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 170void 171CliContext::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 182thread_id 183CliContext::CurrentThreadID() const 184{ 185 return fCurrentThread != NULL ? fCurrentThread->ID() : -1; 186} 187 188 189void 190CliContext::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 221void 222CliContext::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 234void 235CliContext::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 252const char* 253CliContext::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 268void 269CliContext::AddLineToInputHistory(const char* line) 270{ 271 HistEvent historyEvent; 272 history(fHistory, &historyEvent, H_ENTER, line); 273} 274 275 276void 277CliContext::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 290void 291CliContext::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 333void 334CliContext::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 352void 353CliContext::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 404void 405CliContext::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 413void 414CliContext::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 422void 423CliContext::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 434void 435CliContext::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 447void 448CliContext::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 457void 458CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode, 459 ValueNode* newNode) 460{ 461 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 462} 463 464 465void 466CliContext::ValueNodeChildrenCreated(ValueNode* node) 467{ 468 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 469} 470 471 472void 473CliContext::ValueNodeChildrenDeleted(ValueNode* node) 474{ 475 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 476} 477 478 479void 480CliContext::ValueNodeValueChanged(ValueNode* oldNode) 481{ 482 _SignalInputLoop(EVENT_VALUE_NODE_CHANGED); 483} 484 485 486void 487CliContext::_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 499void 500CliContext::_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 509uint32 510CliContext::_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 532void 533CliContext::_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* 551CliContext::_GetPrompt(EditLine* editLine) 552{ 553 return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL; 554} 555