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