xref: /haiku/src/apps/debugger/user_interface/cli/CliContext.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
1 /*
2  * Copyright 2012-2016, 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 #include "Variable.h"
18 
19 
20 // NOTE: This is a simple work-around for EditLine not having any kind of user
21 // data field. Hence in _GetPrompt() we don't have access to the context object.
22 // ATM only one CLI is possible in Debugger, so a static variable works well
23 // enough. Should that ever change, we would need a thread-safe
24 // EditLine* -> CliContext* map.
25 static CliContext* sCurrentContext;
26 
27 
28 // #pragma mark - Event
29 
30 
31 struct CliContext::Event : DoublyLinkedListLinkImpl<CliContext::Event> {
32 	Event(int type, Thread* thread = NULL, TeamMemoryBlock* block = NULL,
33 		ExpressionInfo* info = NULL, status_t expressionResult = B_OK,
34 		ExpressionResult* expressionValue = NULL)
35 		:
36 		fType(type),
37 		fThreadReference(thread),
38 		fMemoryBlockReference(block),
39 		fExpressionInfo(info),
40 		fExpressionResult(expressionResult),
41 		fExpressionValue(expressionValue)
42 	{
43 	}
44 
45 	int Type() const
46 	{
47 		return fType;
48 	}
49 
50 	Thread* GetThread() const
51 	{
52 		return fThreadReference.Get();
53 	}
54 
55 	TeamMemoryBlock* GetMemoryBlock() const
56 	{
57 		return fMemoryBlockReference.Get();
58 	}
59 
60 	ExpressionInfo* GetExpressionInfo() const
61 	{
62 		return fExpressionInfo;
63 	}
64 
65 	status_t GetExpressionResult() const
66 	{
67 		return fExpressionResult;
68 	}
69 
70 	ExpressionResult* GetExpressionValue() const
71 	{
72 		return fExpressionValue.Get();
73 	}
74 
75 
76 private:
77 	int					fType;
78 	BReference<Thread>	fThreadReference;
79 	BReference<TeamMemoryBlock> fMemoryBlockReference;
80 	BReference<ExpressionInfo> fExpressionInfo;
81 	status_t			fExpressionResult;
82 	BReference<ExpressionResult> fExpressionValue;
83 };
84 
85 
86 // #pragma mark - CliContext
87 
88 
89 CliContext::CliContext()
90 	:
91 	fLock("CliContext"),
92 	fTeam(NULL),
93 	fListener(NULL),
94 	fNodeManager(NULL),
95 	fEditLine(NULL),
96 	fHistory(NULL),
97 	fPrompt(NULL),
98 	fBlockingSemaphore(-1),
99 	fInputLoopWaitingForEvents(0),
100 	fEventsOccurred(0),
101 	fInputLoopWaiting(false),
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 thread_id
223 CliContext::CurrentThreadID() const
224 {
225 	return fCurrentThread != NULL ? fCurrentThread->ID() : -1;
226 }
227 
228 
229 void
230 CliContext::SetCurrentThread(Thread* thread)
231 {
232 	AutoLocker<BLocker> locker(fLock);
233 
234 	if (fCurrentThread != NULL)
235 		fCurrentThread->ReleaseReference();
236 
237 	fCurrentThread = thread;
238 
239 	if (fCurrentStackTrace != NULL) {
240 		fCurrentStackTrace->ReleaseReference();
241 		fCurrentStackTrace = NULL;
242 		fCurrentStackFrameIndex = -1;
243 		fNodeManager->SetStackFrame(NULL, NULL);
244 	}
245 
246 	if (fCurrentThread != NULL) {
247 		fCurrentThread->AcquireReference();
248 		StackTrace* stackTrace = fCurrentThread->GetStackTrace();
249 		// if the thread's stack trace has already been loaded,
250 		// set it, otherwise we'll set it when we process the thread's
251 		// stack trace changed event.
252 		if (stackTrace != NULL) {
253 			fCurrentStackTrace = stackTrace;
254 			fCurrentStackTrace->AcquireReference();
255 			SetCurrentStackFrameIndex(0);
256 		}
257 	}
258 }
259 
260 
261 void
262 CliContext::PrintCurrentThread()
263 {
264 	AutoLocker<Team> teamLocker(fTeam);
265 
266 	if (fCurrentThread != NULL) {
267 		printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(),
268 			fCurrentThread->Name());
269 	} else
270 		printf("no current thread\n");
271 }
272 
273 
274 void
275 CliContext::SetCurrentStackFrameIndex(int32 index)
276 {
277 	AutoLocker<BLocker> locker(fLock);
278 
279 	if (fCurrentStackTrace == NULL)
280 		return;
281 	else if (index < 0 || index >= fCurrentStackTrace->CountFrames())
282 		return;
283 
284 	fCurrentStackFrameIndex = index;
285 
286 	StackFrame* frame = fCurrentStackTrace->FrameAt(index);
287 	if (frame != NULL)
288 		fNodeManager->SetStackFrame(fCurrentThread, frame);
289 }
290 
291 
292 status_t
293 CliContext::EvaluateExpression(const char* expression,
294 		SourceLanguage* language, target_addr_t& address)
295 {
296 	fExpressionInfo->SetTo(expression);
297 
298 	fListener->ExpressionEvaluationRequested(
299 		language, fExpressionInfo);
300 	WaitForEvents(CliContext::EVENT_EXPRESSION_EVALUATED);
301 	if (fTerminating)
302 		return B_INTERRUPTED;
303 
304 	BString errorMessage;
305 	if (fExpressionValue != NULL) {
306 		if (fExpressionValue->Kind() == EXPRESSION_RESULT_KIND_PRIMITIVE) {
307 			Value* value = fExpressionValue->PrimitiveValue();
308 			BVariant variantValue;
309 			value->ToVariant(variantValue);
310 			if (variantValue.Type() == B_STRING_TYPE)
311 				errorMessage.SetTo(variantValue.ToString());
312 			else
313 				address = variantValue.ToUInt64();
314 		}
315 	} else
316 		errorMessage = strerror(fExpressionResult);
317 
318 	if (!errorMessage.IsEmpty()) {
319 		printf("Unable to evaluate expression: %s\n",
320 			errorMessage.String());
321 		return B_ERROR;
322 	}
323 
324 	return B_OK;
325 }
326 
327 
328 status_t
329 CliContext::GetMemoryBlock(target_addr_t address, TeamMemoryBlock*& block)
330 {
331 	if (fCurrentBlock == NULL || !fCurrentBlock->Contains(address)) {
332 		GetUserInterfaceListener()->InspectRequested(address, this);
333 		WaitForEvents(CliContext::EVENT_TEAM_MEMORY_BLOCK_RETRIEVED);
334 		if (fTerminating)
335 			return B_INTERRUPTED;
336 	}
337 
338 	block = fCurrentBlock;
339 	return B_OK;
340 }
341 
342 
343 const char*
344 CliContext::PromptUser(const char* prompt)
345 {
346 	fPrompt = prompt;
347 
348 	int count;
349 	const char* line = el_gets(fEditLine, &count);
350 
351 	fPrompt = NULL;
352 
353 	ProcessPendingEvents();
354 
355 	return line;
356 }
357 
358 
359 void
360 CliContext::AddLineToInputHistory(const char* line)
361 {
362 	HistEvent historyEvent;
363 	history(fHistory, &historyEvent, H_ENTER, line);
364 }
365 
366 
367 void
368 CliContext::QuitSession(bool killTeam)
369 {
370 	_PrepareToWaitForEvents(EVENT_QUIT);
371 
372 	fListener->UserInterfaceQuitRequested(
373 		killTeam
374 			? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM
375 			: UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM);
376 
377 	_WaitForEvents();
378 }
379 
380 
381 void
382 CliContext::WaitForThreadOrUser()
383 {
384 	ProcessPendingEvents();
385 
386 // TODO: Deal with SIGINT as well!
387 	for (;;) {
388 		_PrepareToWaitForEvents(
389 			EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED);
390 
391 		// check whether there are any threads stopped already
392 		Thread* stoppedThread = NULL;
393 		BReference<Thread> stoppedThreadReference;
394 
395 		AutoLocker<Team> teamLocker(fTeam);
396 
397 		for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
398 				Thread* thread = it.Next();) {
399 			if (thread->State() == THREAD_STATE_STOPPED) {
400 				stoppedThread = thread;
401 				stoppedThreadReference.SetTo(thread);
402 				break;
403 			}
404 		}
405 
406 		teamLocker.Unlock();
407 
408 		if (stoppedThread != NULL) {
409 			if (fCurrentThread == NULL)
410 				SetCurrentThread(stoppedThread);
411 
412 			_SignalInputLoop(EVENT_THREAD_STOPPED);
413 		}
414 
415 		uint32 events = _WaitForEvents();
416 		if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) {
417 			ProcessPendingEvents();
418 			return;
419 		}
420 	}
421 }
422 
423 
424 void
425 CliContext::WaitForEvents(int32 eventMask)
426 {
427 	for (;;) {
428 		_PrepareToWaitForEvents(eventMask | EVENT_USER_INTERRUPT);
429 		uint32 events = fEventsOccurred;
430 		if ((events & eventMask) == 0) {
431 			events = _WaitForEvents();
432 		}
433 
434 		if ((events & EVENT_QUIT) != 0 || (events & eventMask) != 0) {
435 			_SignalInputLoop(eventMask);
436 			ProcessPendingEvents();
437 			return;
438 		}
439 	}
440 }
441 
442 
443 void
444 CliContext::ProcessPendingEvents()
445 {
446 	AutoLocker<Team> teamLocker(fTeam);
447 
448 	for (;;) {
449 		// get the next event
450 		AutoLocker<BLocker> locker(fLock);
451 		Event* event = fPendingEvents.RemoveHead();
452 		locker.Unlock();
453 		if (event == NULL)
454 			break;
455 		ObjectDeleter<Event> eventDeleter(event);
456 
457 		// process the event
458 		Thread* thread = event->GetThread();
459 
460 		switch (event->Type()) {
461 			case EVENT_QUIT:
462 			case EVENT_DEBUG_REPORT_CHANGED:
463 			case EVENT_USER_INTERRUPT:
464 				break;
465 			case EVENT_THREAD_ADDED:
466 				printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(),
467 					thread->Name());
468 				break;
469 			case EVENT_THREAD_REMOVED:
470 				printf("[thread terminated: %" B_PRId32 " \"%s\"]\n",
471 					thread->ID(), thread->Name());
472 				break;
473 			case EVENT_THREAD_STOPPED:
474 				printf("[thread stopped: %" B_PRId32 " \"%s\"]\n",
475 					thread->ID(), thread->Name());
476 				break;
477 			case EVENT_THREAD_STACK_TRACE_CHANGED:
478 				if (thread == fCurrentThread) {
479 					fCurrentStackTrace = thread->GetStackTrace();
480 					fCurrentStackTrace->AcquireReference();
481 					SetCurrentStackFrameIndex(0);
482 				}
483 				break;
484 			case EVENT_TEAM_MEMORY_BLOCK_RETRIEVED:
485 				if (fCurrentBlock != NULL) {
486 					fCurrentBlock->ReleaseReference();
487 					fCurrentBlock = NULL;
488 				}
489 				fCurrentBlock = event->GetMemoryBlock();
490 				break;
491 			case EVENT_EXPRESSION_EVALUATED:
492 				fExpressionResult = event->GetExpressionResult();
493 				if (fExpressionValue != NULL) {
494 					fExpressionValue->ReleaseReference();
495 					fExpressionValue = NULL;
496 				}
497 				fExpressionValue = event->GetExpressionValue();
498 				if (fExpressionValue != NULL)
499 					fExpressionValue->AcquireReference();
500 				break;
501 		}
502 	}
503 }
504 
505 
506 void
507 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent)
508 {
509 	_QueueEvent(
510 		new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread()));
511 	_SignalInputLoop(EVENT_THREAD_ADDED);
512 }
513 
514 
515 void
516 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent)
517 {
518 	_QueueEvent(
519 		new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread()));
520 	_SignalInputLoop(EVENT_THREAD_REMOVED);
521 }
522 
523 
524 void
525 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent)
526 {
527 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
528 		return;
529 
530 	_QueueEvent(
531 		new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread()));
532 	_SignalInputLoop(EVENT_THREAD_STOPPED);
533 }
534 
535 
536 void
537 CliContext::ThreadStackTraceChanged(const Team::ThreadEvent& threadEvent)
538 {
539 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
540 		return;
541 
542 	_QueueEvent(
543 		new(std::nothrow) Event(EVENT_THREAD_STACK_TRACE_CHANGED,
544 			threadEvent.GetThread()));
545 	_SignalInputLoop(EVENT_THREAD_STACK_TRACE_CHANGED);
546 }
547 
548 
549 void
550 CliContext::ExpressionEvaluated(ExpressionInfo* info, status_t result,
551 	ExpressionResult* value)
552 {
553 	_QueueEvent(
554 		new(std::nothrow) Event(EVENT_EXPRESSION_EVALUATED,
555 			NULL, NULL, info, result, value));
556 	_SignalInputLoop(EVENT_EXPRESSION_EVALUATED);
557 }
558 
559 
560 void
561 CliContext::DebugReportChanged(const Team::DebugReportEvent& event)
562 {
563 	if (event.GetFinalStatus() == B_OK) {
564 		printf("Successfully saved debug report to %s\n",
565 			event.GetReportPath());
566 	} else {
567 		fprintf(stderr, "Failed to write debug report: %s\n", strerror(
568 				event.GetFinalStatus()));
569 	}
570 
571 	_QueueEvent(new(std::nothrow) Event(EVENT_DEBUG_REPORT_CHANGED));
572 	_SignalInputLoop(EVENT_DEBUG_REPORT_CHANGED);
573 }
574 
575 
576 void
577 CliContext::CoreFileChanged(const Team::CoreFileChangedEvent& event)
578 {
579 	printf("Successfully saved core file to %s\n",
580 		event.GetTargetPath());
581 
582 	_QueueEvent(new(std::nothrow) Event(EVENT_CORE_FILE_CHANGED));
583 	_SignalInputLoop(EVENT_CORE_FILE_CHANGED);
584 }
585 
586 
587 void
588 CliContext::MemoryBlockRetrieved(TeamMemoryBlock* block)
589 {
590 	_QueueEvent(
591 		new(std::nothrow) Event(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED,
592 			NULL, block));
593 	_SignalInputLoop(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED);
594 }
595 
596 
597 void
598 CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode,
599 	ValueNode* newNode)
600 {
601 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
602 }
603 
604 
605 void
606 CliContext::ValueNodeChildrenCreated(ValueNode* node)
607 {
608 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
609 }
610 
611 
612 void
613 CliContext::ValueNodeChildrenDeleted(ValueNode* node)
614 {
615 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
616 }
617 
618 
619 void
620 CliContext::ValueNodeValueChanged(ValueNode* oldNode)
621 {
622 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
623 }
624 
625 
626 void
627 CliContext::_QueueEvent(Event* event)
628 {
629 	if (event == NULL) {
630 		// no memory -- can't do anything about it
631 		return;
632 	}
633 
634 	AutoLocker<BLocker> locker(fLock);
635 	fPendingEvents.Add(event);
636 }
637 
638 
639 void
640 CliContext::_PrepareToWaitForEvents(uint32 eventMask)
641 {
642 	// Set the events we're going to wait for -- always wait for "quit".
643 	AutoLocker<BLocker> locker(fLock);
644 	fInputLoopWaitingForEvents = eventMask | EVENT_QUIT;
645 	fEventsOccurred = fTerminating ? EVENT_QUIT : 0;
646 }
647 
648 
649 uint32
650 CliContext::_WaitForEvents()
651 {
652 	AutoLocker<BLocker> locker(fLock);
653 
654 	if (fEventsOccurred == 0) {
655 		sem_id blockingSemaphore = fBlockingSemaphore;
656 		fInputLoopWaiting = true;
657 
658 		locker.Unlock();
659 
660 		while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) {
661 		}
662 
663 		locker.Lock();
664 	}
665 
666 	uint32 events = fEventsOccurred;
667 	fEventsOccurred = 0;
668 	return events;
669 }
670 
671 
672 void
673 CliContext::_SignalInputLoop(uint32 events)
674 {
675 	AutoLocker<BLocker> locker(fLock);
676 
677 	if ((fInputLoopWaitingForEvents & events) == 0)
678 		return;
679 
680 	fEventsOccurred = fInputLoopWaitingForEvents & events;
681 	fInputLoopWaitingForEvents = 0;
682 
683 	if (fInputLoopWaiting) {
684 		fInputLoopWaiting = false;
685 		release_sem(fBlockingSemaphore);
686 	}
687 }
688 
689 
690 /*static*/ const char*
691 CliContext::_GetPrompt(EditLine* editLine)
692 {
693 	return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL;
694 }
695