xref: /haiku/src/apps/debugger/user_interface/cli/CliContext.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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 "Value.h"
16 #include "ValueNodeManager.h"
17 
18 // NOTE: This is a simple work-around for EditLine not having any kind of user
19 // data field. Hence in _GetPrompt() we don't have access to the context object.
20 // ATM only one CLI is possible in Debugger, so a static variable works well
21 // enough. Should that ever change, we would need a thread-safe
22 // EditLine* -> CliContext* map.
23 static CliContext* sCurrentContext;
24 
25 
26 // #pragma mark - Event
27 
28 
29 struct CliContext::Event : DoublyLinkedListLinkImpl<CliContext::Event> {
30 	Event(int type, Thread* thread = NULL, TeamMemoryBlock* block = NULL,
31 		ExpressionInfo* info = NULL, status_t expressionResult = B_OK,
32 		Value* expressionValue = NULL)
33 		:
34 		fType(type),
35 		fThreadReference(thread),
36 		fMemoryBlockReference(block),
37 		fExpressionInfo(info),
38 		fExpressionResult(expressionResult),
39 		fExpressionValue(expressionValue)
40 	{
41 	}
42 
43 	int Type() const
44 	{
45 		return fType;
46 	}
47 
48 	Thread* GetThread() const
49 	{
50 		return fThreadReference.Get();
51 	}
52 
53 	TeamMemoryBlock* GetMemoryBlock() const
54 	{
55 		return fMemoryBlockReference.Get();
56 	}
57 
58 	ExpressionInfo* GetExpressionInfo() const
59 	{
60 		return fExpressionInfo;
61 	}
62 
63 	status_t GetExpressionResult() const
64 	{
65 		return fExpressionResult;
66 	}
67 
68 	Value* GetExpressionValue() const
69 	{
70 		return fExpressionValue.Get();
71 	}
72 
73 
74 private:
75 	int					fType;
76 	BReference<Thread>	fThreadReference;
77 	BReference<TeamMemoryBlock> fMemoryBlockReference;
78 	BReference<ExpressionInfo> fExpressionInfo;
79 	status_t			fExpressionResult;
80 	BReference<Value>	fExpressionValue;
81 };
82 
83 
84 // #pragma mark - CliContext
85 
86 
87 CliContext::CliContext()
88 	:
89 	fLock("CliContext"),
90 	fTeam(NULL),
91 	fListener(NULL),
92 	fNodeManager(NULL),
93 	fEditLine(NULL),
94 	fHistory(NULL),
95 	fPrompt(NULL),
96 	fBlockingSemaphore(-1),
97 	fInputLoopWaitingForEvents(0),
98 	fEventsOccurred(0),
99 	fInputLoopWaiting(false),
100 	fInteractive(true),
101 	fTerminating(false),
102 	fCurrentThread(NULL),
103 	fCurrentStackTrace(NULL),
104 	fCurrentStackFrameIndex(-1),
105 	fCurrentBlock(NULL),
106 	fExpressionInfo(NULL),
107 	fExpressionResult(B_OK),
108 	fExpressionValue(NULL)
109 {
110 	sCurrentContext = this;
111 }
112 
113 
114 CliContext::~CliContext()
115 {
116 	Cleanup();
117 	sCurrentContext = NULL;
118 
119 	if (fBlockingSemaphore >= 0)
120 		delete_sem(fBlockingSemaphore);
121 }
122 
123 
124 status_t
125 CliContext::Init(Team* team, UserInterfaceListener* listener)
126 {
127 	fTeam = team;
128 	fListener = listener;
129 
130 	fTeam->AddListener(this);
131 
132 	status_t error = fLock.InitCheck();
133 	if (error != B_OK)
134 		return error;
135 
136 	fBlockingSemaphore = create_sem(0, "CliContext block");
137 	if (fBlockingSemaphore < 0)
138 		return fBlockingSemaphore;
139 
140 	fEditLine = el_init("Debugger", stdin, stdout, stderr);
141 	if (fEditLine == NULL)
142 		return B_ERROR;
143 
144 	fHistory = history_init();
145 	if (fHistory == NULL)
146 		return B_ERROR;
147 
148 	HistEvent historyEvent;
149 	history(fHistory, &historyEvent, H_SETSIZE, 100);
150 
151 	el_set(fEditLine, EL_HIST, &history, fHistory);
152 	el_set(fEditLine, EL_EDITOR, "emacs");
153 	el_set(fEditLine, EL_PROMPT, &_GetPrompt);
154 
155 	fNodeManager = new(std::nothrow) ValueNodeManager();
156 	if (fNodeManager == NULL)
157 		return B_NO_MEMORY;
158 	fNodeManager->AddListener(this);
159 
160 	fExpressionInfo = new(std::nothrow) ExpressionInfo();
161 	if (fExpressionInfo == NULL)
162 		return B_NO_MEMORY;
163 	fExpressionInfo->AddListener(this);
164 
165 	return B_OK;
166 }
167 
168 
169 void
170 CliContext::Cleanup()
171 {
172 	Terminating();
173 
174 	while (Event* event = fPendingEvents.RemoveHead())
175 		delete event;
176 
177 	if (fEditLine != NULL) {
178 		el_end(fEditLine);
179 		fEditLine = NULL;
180 	}
181 
182 	if (fHistory != NULL) {
183 		history_end(fHistory);
184 		fHistory = NULL;
185 	}
186 
187 	if (fTeam != NULL) {
188 		fTeam->RemoveListener(this);
189 		fTeam = NULL;
190 	}
191 
192 	if (fNodeManager != NULL) {
193 		fNodeManager->ReleaseReference();
194 		fNodeManager = NULL;
195 	}
196 
197 	if (fCurrentBlock != NULL) {
198 		fCurrentBlock->ReleaseReference();
199 		fCurrentBlock = NULL;
200 	}
201 
202 	if (fExpressionInfo != NULL) {
203 		fExpressionInfo->ReleaseReference();
204 		fExpressionInfo = NULL;
205 	}
206 }
207 
208 
209 void
210 CliContext::Terminating()
211 {
212 	AutoLocker<BLocker> locker(fLock);
213 
214 	fTerminating = true;
215 	_SignalInputLoop(EVENT_QUIT);
216 
217 	// TODO: Signal the input loop, should it be in PromptUser()!
218 }
219 
220 
221 void
222 CliContext::SetInteractive(bool interactive)
223 {
224 	fInteractive = interactive;
225 }
226 
227 
228 thread_id
229 CliContext::CurrentThreadID() const
230 {
231 	return fCurrentThread != NULL ? fCurrentThread->ID() : -1;
232 }
233 
234 
235 void
236 CliContext::SetCurrentThread(Thread* thread)
237 {
238 	AutoLocker<BLocker> locker(fLock);
239 
240 	if (fCurrentThread != NULL)
241 		fCurrentThread->ReleaseReference();
242 
243 	fCurrentThread = thread;
244 
245 	if (fCurrentStackTrace != NULL) {
246 		fCurrentStackTrace->ReleaseReference();
247 		fCurrentStackTrace = NULL;
248 		fCurrentStackFrameIndex = -1;
249 		fNodeManager->SetStackFrame(NULL, NULL);
250 	}
251 
252 	if (fCurrentThread != NULL) {
253 		fCurrentThread->AcquireReference();
254 		StackTrace* stackTrace = fCurrentThread->GetStackTrace();
255 		// if the thread's stack trace has already been loaded,
256 		// set it, otherwise we'll set it when we process the thread's
257 		// stack trace changed event.
258 		if (stackTrace != NULL) {
259 			fCurrentStackTrace = stackTrace;
260 			fCurrentStackTrace->AcquireReference();
261 			SetCurrentStackFrameIndex(0);
262 		}
263 	}
264 }
265 
266 
267 void
268 CliContext::PrintCurrentThread()
269 {
270 	AutoLocker<Team> teamLocker(fTeam);
271 
272 	if (fCurrentThread != NULL) {
273 		printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(),
274 			fCurrentThread->Name());
275 	} else
276 		printf("no current thread\n");
277 }
278 
279 
280 void
281 CliContext::SetCurrentStackFrameIndex(int32 index)
282 {
283 	AutoLocker<BLocker> locker(fLock);
284 
285 	if (fCurrentStackTrace == NULL)
286 		return;
287 	else if (index < 0 || index >= fCurrentStackTrace->CountFrames())
288 		return;
289 
290 	fCurrentStackFrameIndex = index;
291 
292 	StackFrame* frame = fCurrentStackTrace->FrameAt(index);
293 	if (frame != NULL)
294 		fNodeManager->SetStackFrame(fCurrentThread, frame);
295 }
296 
297 
298 const char*
299 CliContext::PromptUser(const char* prompt)
300 {
301 	fPrompt = prompt;
302 
303 	int count;
304 	const char* line = el_gets(fEditLine, &count);
305 
306 	fPrompt = NULL;
307 
308 	ProcessPendingEvents();
309 
310 	return line;
311 }
312 
313 
314 void
315 CliContext::AddLineToInputHistory(const char* line)
316 {
317 	HistEvent historyEvent;
318 	history(fHistory, &historyEvent, H_ENTER, line);
319 }
320 
321 
322 void
323 CliContext::QuitSession(bool killTeam)
324 {
325 	_PrepareToWaitForEvents(EVENT_QUIT);
326 
327 	fListener->UserInterfaceQuitRequested(
328 		killTeam
329 			? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM
330 			: UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM);
331 
332 	_WaitForEvents();
333 }
334 
335 
336 void
337 CliContext::WaitForThreadOrUser()
338 {
339 	ProcessPendingEvents();
340 
341 // TODO: Deal with SIGINT as well!
342 	for (;;) {
343 		_PrepareToWaitForEvents(
344 			EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED);
345 
346 		// check whether there are any threads stopped already
347 		Thread* stoppedThread = NULL;
348 		BReference<Thread> stoppedThreadReference;
349 
350 		AutoLocker<Team> teamLocker(fTeam);
351 
352 		for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
353 				Thread* thread = it.Next();) {
354 			if (thread->State() == THREAD_STATE_STOPPED) {
355 				stoppedThread = thread;
356 				stoppedThreadReference.SetTo(thread);
357 				break;
358 			}
359 		}
360 
361 		teamLocker.Unlock();
362 
363 		if (stoppedThread != NULL) {
364 			if (fCurrentThread == NULL)
365 				SetCurrentThread(stoppedThread);
366 
367 			_SignalInputLoop(EVENT_THREAD_STOPPED);
368 		}
369 
370 		uint32 events = _WaitForEvents();
371 		if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) {
372 			ProcessPendingEvents();
373 			return;
374 		}
375 	}
376 }
377 
378 
379 void
380 CliContext::WaitForEvents(int32 eventMask)
381 {
382 	for (;;) {
383 		_PrepareToWaitForEvents(eventMask | EVENT_USER_INTERRUPT);
384 		uint32 events = fEventsOccurred;
385 		if ((events & eventMask) == 0) {
386 			events = _WaitForEvents();
387 		}
388 
389 		if ((events & EVENT_QUIT) != 0 || (events & eventMask) != 0) {
390 			_SignalInputLoop(eventMask);
391 			ProcessPendingEvents();
392 			return;
393 		}
394 	}
395 }
396 
397 
398 void
399 CliContext::ProcessPendingEvents()
400 {
401 	AutoLocker<Team> teamLocker(fTeam);
402 
403 	for (;;) {
404 		// get the next event
405 		AutoLocker<BLocker> locker(fLock);
406 		Event* event = fPendingEvents.RemoveHead();
407 		locker.Unlock();
408 		if (event == NULL)
409 			break;
410 		ObjectDeleter<Event> eventDeleter(event);
411 
412 		// process the event
413 		Thread* thread = event->GetThread();
414 
415 		switch (event->Type()) {
416 			case EVENT_QUIT:
417 			case EVENT_USER_INTERRUPT:
418 				break;
419 			case EVENT_THREAD_ADDED:
420 				printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(),
421 					thread->Name());
422 				break;
423 			case EVENT_THREAD_REMOVED:
424 				printf("[thread terminated: %" B_PRId32 " \"%s\"]\n",
425 					thread->ID(), thread->Name());
426 				break;
427 			case EVENT_THREAD_STOPPED:
428 				printf("[thread stopped: %" B_PRId32 " \"%s\"]\n",
429 					thread->ID(), thread->Name());
430 				break;
431 			case EVENT_THREAD_STACK_TRACE_CHANGED:
432 				if (thread == fCurrentThread) {
433 					fCurrentStackTrace = thread->GetStackTrace();
434 					fCurrentStackTrace->AcquireReference();
435 					SetCurrentStackFrameIndex(0);
436 				}
437 				break;
438 			case EVENT_TEAM_MEMORY_BLOCK_RETRIEVED:
439 				if (fCurrentBlock != NULL) {
440 					fCurrentBlock->ReleaseReference();
441 					fCurrentBlock = NULL;
442 				}
443 				fCurrentBlock = event->GetMemoryBlock();
444 				break;
445 			case EVENT_EXPRESSION_EVALUATED:
446 				fExpressionResult = event->GetExpressionResult();
447 				if (fExpressionValue != NULL) {
448 					fExpressionValue->ReleaseReference();
449 					fExpressionValue = NULL;
450 				}
451 				fExpressionValue = event->GetExpressionValue();
452 				if (fExpressionValue != NULL)
453 					fExpressionValue->AcquireReference();
454 				break;
455 			case EVENT_DEBUG_REPORT_CHANGED:
456 				if (!IsInteractive()) {
457 					Terminating();
458 					QuitSession(true);
459 				}
460 				break;
461 
462 		}
463 	}
464 }
465 
466 
467 void
468 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent)
469 {
470 	_QueueEvent(
471 		new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread()));
472 	_SignalInputLoop(EVENT_THREAD_ADDED);
473 }
474 
475 
476 void
477 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent)
478 {
479 	_QueueEvent(
480 		new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread()));
481 	_SignalInputLoop(EVENT_THREAD_REMOVED);
482 }
483 
484 
485 void
486 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent)
487 {
488 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
489 		return;
490 
491 	_QueueEvent(
492 		new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread()));
493 	_SignalInputLoop(EVENT_THREAD_STOPPED);
494 }
495 
496 
497 void
498 CliContext::ThreadStackTraceChanged(const Team::ThreadEvent& threadEvent)
499 {
500 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
501 		return;
502 
503 	_QueueEvent(
504 		new(std::nothrow) Event(EVENT_THREAD_STACK_TRACE_CHANGED,
505 			threadEvent.GetThread()));
506 	_SignalInputLoop(EVENT_THREAD_STACK_TRACE_CHANGED);
507 }
508 
509 
510 void
511 CliContext::ExpressionEvaluated(ExpressionInfo* info, status_t result,
512 	Value* value)
513 {
514 	_QueueEvent(
515 		new(std::nothrow) Event(EVENT_EXPRESSION_EVALUATED,
516 			NULL, NULL, info, result, value));
517 	_SignalInputLoop(EVENT_EXPRESSION_EVALUATED);
518 }
519 
520 
521 void
522 CliContext::DebugReportChanged(const Team::DebugReportEvent& event)
523 {
524 	printf("Successfully saved debug report to %s\n",
525 		event.GetReportPath());
526 
527 	_QueueEvent(new(std::nothrow) Event(EVENT_DEBUG_REPORT_CHANGED));
528 	_SignalInputLoop(EVENT_DEBUG_REPORT_CHANGED);
529 }
530 
531 
532 void
533 CliContext::MemoryBlockRetrieved(TeamMemoryBlock* block)
534 {
535 	_QueueEvent(
536 		new(std::nothrow) Event(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED,
537 			NULL, block));
538 	_SignalInputLoop(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED);
539 }
540 
541 
542 void
543 CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode,
544 	ValueNode* newNode)
545 {
546 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
547 }
548 
549 
550 void
551 CliContext::ValueNodeChildrenCreated(ValueNode* node)
552 {
553 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
554 }
555 
556 
557 void
558 CliContext::ValueNodeChildrenDeleted(ValueNode* node)
559 {
560 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
561 }
562 
563 
564 void
565 CliContext::ValueNodeValueChanged(ValueNode* oldNode)
566 {
567 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
568 }
569 
570 
571 void
572 CliContext::_QueueEvent(Event* event)
573 {
574 	if (event == NULL) {
575 		// no memory -- can't do anything about it
576 		return;
577 	}
578 
579 	AutoLocker<BLocker> locker(fLock);
580 	fPendingEvents.Add(event);
581 }
582 
583 
584 void
585 CliContext::_PrepareToWaitForEvents(uint32 eventMask)
586 {
587 	// Set the events we're going to wait for -- always wait for "quit".
588 	AutoLocker<BLocker> locker(fLock);
589 	fInputLoopWaitingForEvents = eventMask | EVENT_QUIT;
590 	fEventsOccurred = fTerminating ? EVENT_QUIT : 0;
591 }
592 
593 
594 uint32
595 CliContext::_WaitForEvents()
596 {
597 	AutoLocker<BLocker> locker(fLock);
598 
599 	if (fEventsOccurred == 0) {
600 		sem_id blockingSemaphore = fBlockingSemaphore;
601 		fInputLoopWaiting = true;
602 
603 		locker.Unlock();
604 
605 		while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) {
606 		}
607 
608 		locker.Lock();
609 	}
610 
611 	uint32 events = fEventsOccurred;
612 	fEventsOccurred = 0;
613 	return events;
614 }
615 
616 
617 void
618 CliContext::_SignalInputLoop(uint32 events)
619 {
620 	AutoLocker<BLocker> locker(fLock);
621 
622 	if ((fInputLoopWaitingForEvents & events) == 0)
623 		return;
624 
625 	fEventsOccurred = fInputLoopWaitingForEvents & events;
626 	fInputLoopWaitingForEvents = 0;
627 
628 	if (fInputLoopWaiting) {
629 		fInputLoopWaiting = false;
630 		release_sem(fBlockingSemaphore);
631 	}
632 }
633 
634 
635 /*static*/ const char*
636 CliContext::_GetPrompt(EditLine* editLine)
637 {
638 	return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL;
639 }
640