1 /* 2 * Copyright 2012, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "CliContext.h" 8 9 #include <AutoDeleter.h> 10 #include <AutoLocker.h> 11 12 #include "UserInterface.h" 13 14 15 // NOTE: This is a simple work-around for EditLine not having any kind of user 16 // data field. Hence in _GetPrompt() we don't have access to the context object. 17 // ATM only one CLI is possible in Debugger, so a static variable works well 18 // enough. Should that ever change, we would need a thread-safe 19 // EditLine* -> CliContext* map. 20 static CliContext* sCurrentContext; 21 22 23 // #pragma mark - Event 24 25 26 struct CliContext::Event : DoublyLinkedListLinkImpl<CliContext::Event> { 27 Event(int type, Thread* thread = NULL) 28 : 29 fType(type), 30 fThreadReference(thread) 31 { 32 } 33 34 int Type() const 35 { 36 return fType; 37 } 38 39 Thread* GetThread() const 40 { 41 return fThreadReference.Get(); 42 } 43 44 private: 45 int fType; 46 BReference<Thread> fThreadReference; 47 }; 48 49 50 // #pragma mark - CliContext 51 52 53 CliContext::CliContext() 54 : 55 fLock("CliContext"), 56 fTeam(NULL), 57 fListener(NULL), 58 fEditLine(NULL), 59 fHistory(NULL), 60 fPrompt(NULL), 61 fBlockingSemaphore(-1), 62 fInputLoopWaitingForEvents(0), 63 fEventsOccurred(0), 64 fInputLoopWaiting(false), 65 fTerminating(false), 66 fCurrentThread(NULL) 67 { 68 sCurrentContext = this; 69 } 70 71 72 CliContext::~CliContext() 73 { 74 Cleanup(); 75 sCurrentContext = NULL; 76 77 if (fBlockingSemaphore >= 0) 78 delete_sem(fBlockingSemaphore); 79 } 80 81 82 status_t 83 CliContext::Init(Team* team, UserInterfaceListener* listener) 84 { 85 fTeam = team; 86 fListener = listener; 87 88 fTeam->AddListener(this); 89 90 status_t error = fLock.InitCheck(); 91 if (error != B_OK) 92 return error; 93 94 fBlockingSemaphore = create_sem(0, "CliContext block"); 95 if (fBlockingSemaphore < 0) 96 return fBlockingSemaphore; 97 98 fEditLine = el_init("Debugger", stdin, stdout, stderr); 99 if (fEditLine == NULL) 100 return B_ERROR; 101 102 fHistory = history_init(); 103 if (fHistory == NULL) 104 return B_ERROR; 105 106 HistEvent historyEvent; 107 history(fHistory, &historyEvent, H_SETSIZE, 100); 108 109 el_set(fEditLine, EL_HIST, &history, fHistory); 110 el_set(fEditLine, EL_EDITOR, "emacs"); 111 el_set(fEditLine, EL_PROMPT, &_GetPrompt); 112 113 return B_OK; 114 } 115 116 117 void 118 CliContext::Cleanup() 119 { 120 Terminating(); 121 122 while (Event* event = fPendingEvents.RemoveHead()) 123 delete event; 124 125 if (fEditLine != NULL) { 126 el_end(fEditLine); 127 fEditLine = NULL; 128 } 129 130 if (fHistory != NULL) { 131 history_end(fHistory); 132 fHistory = NULL; 133 } 134 135 if (fTeam != NULL) { 136 fTeam->RemoveListener(this); 137 fTeam = NULL; 138 } 139 } 140 141 142 void 143 CliContext::Terminating() 144 { 145 AutoLocker<BLocker> locker(fLock); 146 147 fTerminating = true; 148 _SignalInputLoop(EVENT_QUIT); 149 150 // TODO: Signal the input loop, should it be in PromptUser()! 151 } 152 153 154 thread_id 155 CliContext::CurrentThreadID() const 156 { 157 return fCurrentThread != NULL ? fCurrentThread->ID() : -1; 158 } 159 160 161 void 162 CliContext::SetCurrentThread(Thread* thread) 163 { 164 AutoLocker<BLocker> locker(fLock); 165 166 if (fCurrentThread != NULL) 167 fCurrentThread->ReleaseReference(); 168 169 fCurrentThread = thread; 170 171 if (fCurrentThread != NULL) 172 fCurrentThread->AcquireReference(); 173 } 174 175 176 void 177 CliContext::PrintCurrentThread() 178 { 179 AutoLocker<Team> teamLocker(fTeam); 180 181 if (fCurrentThread != NULL) { 182 printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(), 183 fCurrentThread->Name()); 184 } else 185 printf("no current thread\n"); 186 } 187 188 189 const char* 190 CliContext::PromptUser(const char* prompt) 191 { 192 fPrompt = prompt; 193 194 int count; 195 const char* line = el_gets(fEditLine, &count); 196 197 fPrompt = NULL; 198 199 ProcessPendingEvents(); 200 201 return line; 202 } 203 204 205 void 206 CliContext::AddLineToInputHistory(const char* line) 207 { 208 HistEvent historyEvent; 209 history(fHistory, &historyEvent, H_ENTER, line); 210 } 211 212 213 void 214 CliContext::QuitSession(bool killTeam) 215 { 216 _PrepareToWaitForEvents(EVENT_QUIT); 217 218 fListener->UserInterfaceQuitRequested( 219 killTeam 220 ? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM 221 : UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM); 222 223 _WaitForEvents(); 224 } 225 226 227 void 228 CliContext::WaitForThreadOrUser() 229 { 230 ProcessPendingEvents(); 231 232 // TODO: Deal with SIGINT as well! 233 for (;;) { 234 _PrepareToWaitForEvents( 235 EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED); 236 237 // check whether there are any threads stopped already 238 Thread* stoppedThread = NULL; 239 BReference<Thread> stoppedThreadReference; 240 241 AutoLocker<Team> teamLocker(fTeam); 242 243 for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator(); 244 Thread* thread = it.Next();) { 245 if (thread->State() == THREAD_STATE_STOPPED) { 246 stoppedThread = thread; 247 stoppedThreadReference.SetTo(thread); 248 break; 249 } 250 } 251 252 teamLocker.Unlock(); 253 254 if (stoppedThread != NULL) { 255 if (fCurrentThread == NULL) 256 fCurrentThread = stoppedThread; 257 258 _SignalInputLoop(EVENT_THREAD_STOPPED); 259 } 260 261 uint32 events = _WaitForEvents(); 262 if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) { 263 ProcessPendingEvents(); 264 return; 265 } 266 } 267 } 268 269 270 void 271 CliContext::ProcessPendingEvents() 272 { 273 AutoLocker<Team> teamLocker(fTeam); 274 275 for (;;) { 276 // get the next event 277 AutoLocker<BLocker> locker(fLock); 278 Event* event = fPendingEvents.RemoveHead(); 279 locker.Unlock(); 280 if (event == NULL) 281 break; 282 ObjectDeleter<Event> eventDeleter(event); 283 284 // process the event 285 Thread* thread = event->GetThread(); 286 287 switch (event->Type()) { 288 case EVENT_QUIT: 289 case EVENT_USER_INTERRUPT: 290 break; 291 case EVENT_THREAD_ADDED: 292 printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(), 293 thread->Name()); 294 break; 295 case EVENT_THREAD_REMOVED: 296 printf("[thread terminated: %" B_PRId32 " \"%s\"]\n", 297 thread->ID(), thread->Name()); 298 break; 299 case EVENT_THREAD_STOPPED: 300 printf("[thread stopped: %" B_PRId32 " \"%s\"]\n", 301 thread->ID(), thread->Name()); 302 break; 303 } 304 } 305 } 306 307 308 void 309 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent) 310 { 311 _QueueEvent( 312 new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread())); 313 _SignalInputLoop(EVENT_THREAD_ADDED); 314 } 315 316 317 void 318 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent) 319 { 320 _QueueEvent( 321 new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread())); 322 _SignalInputLoop(EVENT_THREAD_REMOVED); 323 } 324 325 326 void 327 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent) 328 { 329 if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED) 330 return; 331 332 _QueueEvent( 333 new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread())); 334 _SignalInputLoop(EVENT_THREAD_STOPPED); 335 } 336 337 338 void 339 CliContext::_QueueEvent(Event* event) 340 { 341 if (event == NULL) { 342 // no memory -- can't do anything about it 343 return; 344 } 345 346 AutoLocker<BLocker> locker(fLock); 347 fPendingEvents.Add(event); 348 } 349 350 351 void 352 CliContext::_PrepareToWaitForEvents(uint32 eventMask) 353 { 354 // Set the events we're going to wait for -- always wait for "quit". 355 AutoLocker<BLocker> locker(fLock); 356 fInputLoopWaitingForEvents = eventMask | EVENT_QUIT; 357 fEventsOccurred = fTerminating ? EVENT_QUIT : 0; 358 } 359 360 361 uint32 362 CliContext::_WaitForEvents() 363 { 364 AutoLocker<BLocker> locker(fLock); 365 366 if (fEventsOccurred == 0) { 367 sem_id blockingSemaphore = fBlockingSemaphore; 368 fInputLoopWaiting = true; 369 370 locker.Unlock(); 371 372 while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) { 373 } 374 375 locker.Lock(); 376 } 377 378 uint32 events = fEventsOccurred; 379 fEventsOccurred = 0; 380 return events; 381 } 382 383 384 void 385 CliContext::_SignalInputLoop(uint32 events) 386 { 387 AutoLocker<BLocker> locker(fLock); 388 389 if ((fInputLoopWaitingForEvents & events) == 0) 390 return; 391 392 fEventsOccurred = fInputLoopWaitingForEvents & events; 393 fInputLoopWaitingForEvents = 0; 394 395 if (fInputLoopWaiting) { 396 fInputLoopWaiting = false; 397 release_sem(fBlockingSemaphore); 398 } 399 } 400 401 402 /*static*/ const char* 403 CliContext::_GetPrompt(EditLine* editLine) 404 { 405 return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL; 406 } 407