xref: /haiku/src/apps/debugger/user_interface/cli/CliContext.cpp (revision 81c848a14a380679a439cccb9e10b60d05bd6fbc)
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