xref: /haiku/src/apps/debugger/user_interface/cli/CliContext.cpp (revision 71cc4d49983c5897e72bfd8b2eb1b6b6d704d0d7)
1 /*
2  * Copyright 2012-2015, 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 	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 thread_id
222 CliContext::CurrentThreadID() const
223 {
224 	return fCurrentThread != NULL ? fCurrentThread->ID() : -1;
225 }
226 
227 
228 void
229 CliContext::SetCurrentThread(Thread* thread)
230 {
231 	AutoLocker<BLocker> locker(fLock);
232 
233 	if (fCurrentThread != NULL)
234 		fCurrentThread->ReleaseReference();
235 
236 	fCurrentThread = thread;
237 
238 	if (fCurrentStackTrace != NULL) {
239 		fCurrentStackTrace->ReleaseReference();
240 		fCurrentStackTrace = NULL;
241 		fCurrentStackFrameIndex = -1;
242 		fNodeManager->SetStackFrame(NULL, NULL);
243 	}
244 
245 	if (fCurrentThread != NULL) {
246 		fCurrentThread->AcquireReference();
247 		StackTrace* stackTrace = fCurrentThread->GetStackTrace();
248 		// if the thread's stack trace has already been loaded,
249 		// set it, otherwise we'll set it when we process the thread's
250 		// stack trace changed event.
251 		if (stackTrace != NULL) {
252 			fCurrentStackTrace = stackTrace;
253 			fCurrentStackTrace->AcquireReference();
254 			SetCurrentStackFrameIndex(0);
255 		}
256 	}
257 }
258 
259 
260 void
261 CliContext::PrintCurrentThread()
262 {
263 	AutoLocker<Team> teamLocker(fTeam);
264 
265 	if (fCurrentThread != NULL) {
266 		printf("current thread: %" B_PRId32 " \"%s\"\n", fCurrentThread->ID(),
267 			fCurrentThread->Name());
268 	} else
269 		printf("no current thread\n");
270 }
271 
272 
273 void
274 CliContext::SetCurrentStackFrameIndex(int32 index)
275 {
276 	AutoLocker<BLocker> locker(fLock);
277 
278 	if (fCurrentStackTrace == NULL)
279 		return;
280 	else if (index < 0 || index >= fCurrentStackTrace->CountFrames())
281 		return;
282 
283 	fCurrentStackFrameIndex = index;
284 
285 	StackFrame* frame = fCurrentStackTrace->FrameAt(index);
286 	if (frame != NULL)
287 		fNodeManager->SetStackFrame(fCurrentThread, frame);
288 }
289 
290 
291 const char*
292 CliContext::PromptUser(const char* prompt)
293 {
294 	fPrompt = prompt;
295 
296 	int count;
297 	const char* line = el_gets(fEditLine, &count);
298 
299 	fPrompt = NULL;
300 
301 	ProcessPendingEvents();
302 
303 	return line;
304 }
305 
306 
307 void
308 CliContext::AddLineToInputHistory(const char* line)
309 {
310 	HistEvent historyEvent;
311 	history(fHistory, &historyEvent, H_ENTER, line);
312 }
313 
314 
315 void
316 CliContext::QuitSession(bool killTeam)
317 {
318 	_PrepareToWaitForEvents(EVENT_QUIT);
319 
320 	fListener->UserInterfaceQuitRequested(
321 		killTeam
322 			? UserInterfaceListener::QUIT_OPTION_ASK_KILL_TEAM
323 			: UserInterfaceListener::QUIT_OPTION_ASK_RESUME_TEAM);
324 
325 	_WaitForEvents();
326 }
327 
328 
329 void
330 CliContext::WaitForThreadOrUser()
331 {
332 	ProcessPendingEvents();
333 
334 // TODO: Deal with SIGINT as well!
335 	for (;;) {
336 		_PrepareToWaitForEvents(
337 			EVENT_USER_INTERRUPT | EVENT_THREAD_STOPPED);
338 
339 		// check whether there are any threads stopped already
340 		Thread* stoppedThread = NULL;
341 		BReference<Thread> stoppedThreadReference;
342 
343 		AutoLocker<Team> teamLocker(fTeam);
344 
345 		for (ThreadList::ConstIterator it = fTeam->Threads().GetIterator();
346 				Thread* thread = it.Next();) {
347 			if (thread->State() == THREAD_STATE_STOPPED) {
348 				stoppedThread = thread;
349 				stoppedThreadReference.SetTo(thread);
350 				break;
351 			}
352 		}
353 
354 		teamLocker.Unlock();
355 
356 		if (stoppedThread != NULL) {
357 			if (fCurrentThread == NULL)
358 				SetCurrentThread(stoppedThread);
359 
360 			_SignalInputLoop(EVENT_THREAD_STOPPED);
361 		}
362 
363 		uint32 events = _WaitForEvents();
364 		if ((events & EVENT_QUIT) != 0 || stoppedThread != NULL) {
365 			ProcessPendingEvents();
366 			return;
367 		}
368 	}
369 }
370 
371 
372 void
373 CliContext::WaitForEvents(int32 eventMask)
374 {
375 	for (;;) {
376 		_PrepareToWaitForEvents(eventMask | EVENT_USER_INTERRUPT);
377 		uint32 events = fEventsOccurred;
378 		if ((events & eventMask) == 0) {
379 			events = _WaitForEvents();
380 		}
381 
382 		if ((events & EVENT_QUIT) != 0 || (events & eventMask) != 0) {
383 			_SignalInputLoop(eventMask);
384 			ProcessPendingEvents();
385 			return;
386 		}
387 	}
388 }
389 
390 
391 void
392 CliContext::ProcessPendingEvents()
393 {
394 	AutoLocker<Team> teamLocker(fTeam);
395 
396 	for (;;) {
397 		// get the next event
398 		AutoLocker<BLocker> locker(fLock);
399 		Event* event = fPendingEvents.RemoveHead();
400 		locker.Unlock();
401 		if (event == NULL)
402 			break;
403 		ObjectDeleter<Event> eventDeleter(event);
404 
405 		// process the event
406 		Thread* thread = event->GetThread();
407 
408 		switch (event->Type()) {
409 			case EVENT_QUIT:
410 			case EVENT_DEBUG_REPORT_CHANGED:
411 			case EVENT_USER_INTERRUPT:
412 				break;
413 			case EVENT_THREAD_ADDED:
414 				printf("[new thread: %" B_PRId32 " \"%s\"]\n", thread->ID(),
415 					thread->Name());
416 				break;
417 			case EVENT_THREAD_REMOVED:
418 				printf("[thread terminated: %" B_PRId32 " \"%s\"]\n",
419 					thread->ID(), thread->Name());
420 				break;
421 			case EVENT_THREAD_STOPPED:
422 				printf("[thread stopped: %" B_PRId32 " \"%s\"]\n",
423 					thread->ID(), thread->Name());
424 				break;
425 			case EVENT_THREAD_STACK_TRACE_CHANGED:
426 				if (thread == fCurrentThread) {
427 					fCurrentStackTrace = thread->GetStackTrace();
428 					fCurrentStackTrace->AcquireReference();
429 					SetCurrentStackFrameIndex(0);
430 				}
431 				break;
432 			case EVENT_TEAM_MEMORY_BLOCK_RETRIEVED:
433 				if (fCurrentBlock != NULL) {
434 					fCurrentBlock->ReleaseReference();
435 					fCurrentBlock = NULL;
436 				}
437 				fCurrentBlock = event->GetMemoryBlock();
438 				break;
439 			case EVENT_EXPRESSION_EVALUATED:
440 				fExpressionResult = event->GetExpressionResult();
441 				if (fExpressionValue != NULL) {
442 					fExpressionValue->ReleaseReference();
443 					fExpressionValue = NULL;
444 				}
445 				fExpressionValue = event->GetExpressionValue();
446 				if (fExpressionValue != NULL)
447 					fExpressionValue->AcquireReference();
448 				break;
449 		}
450 	}
451 }
452 
453 
454 void
455 CliContext::ThreadAdded(const Team::ThreadEvent& threadEvent)
456 {
457 	_QueueEvent(
458 		new(std::nothrow) Event(EVENT_THREAD_ADDED, threadEvent.GetThread()));
459 	_SignalInputLoop(EVENT_THREAD_ADDED);
460 }
461 
462 
463 void
464 CliContext::ThreadRemoved(const Team::ThreadEvent& threadEvent)
465 {
466 	_QueueEvent(
467 		new(std::nothrow) Event(EVENT_THREAD_REMOVED, threadEvent.GetThread()));
468 	_SignalInputLoop(EVENT_THREAD_REMOVED);
469 }
470 
471 
472 void
473 CliContext::ThreadStateChanged(const Team::ThreadEvent& threadEvent)
474 {
475 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
476 		return;
477 
478 	_QueueEvent(
479 		new(std::nothrow) Event(EVENT_THREAD_STOPPED, threadEvent.GetThread()));
480 	_SignalInputLoop(EVENT_THREAD_STOPPED);
481 }
482 
483 
484 void
485 CliContext::ThreadStackTraceChanged(const Team::ThreadEvent& threadEvent)
486 {
487 	if (threadEvent.GetThread()->State() != THREAD_STATE_STOPPED)
488 		return;
489 
490 	_QueueEvent(
491 		new(std::nothrow) Event(EVENT_THREAD_STACK_TRACE_CHANGED,
492 			threadEvent.GetThread()));
493 	_SignalInputLoop(EVENT_THREAD_STACK_TRACE_CHANGED);
494 }
495 
496 
497 void
498 CliContext::ExpressionEvaluated(ExpressionInfo* info, status_t result,
499 	ExpressionResult* value)
500 {
501 	_QueueEvent(
502 		new(std::nothrow) Event(EVENT_EXPRESSION_EVALUATED,
503 			NULL, NULL, info, result, value));
504 	_SignalInputLoop(EVENT_EXPRESSION_EVALUATED);
505 }
506 
507 
508 void
509 CliContext::DebugReportChanged(const Team::DebugReportEvent& event)
510 {
511 	printf("Successfully saved debug report to %s\n",
512 		event.GetReportPath());
513 
514 	_QueueEvent(new(std::nothrow) Event(EVENT_DEBUG_REPORT_CHANGED));
515 	_SignalInputLoop(EVENT_DEBUG_REPORT_CHANGED);
516 }
517 
518 
519 void
520 CliContext::MemoryBlockRetrieved(TeamMemoryBlock* block)
521 {
522 	_QueueEvent(
523 		new(std::nothrow) Event(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED,
524 			NULL, block));
525 	_SignalInputLoop(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED);
526 }
527 
528 
529 void
530 CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode,
531 	ValueNode* newNode)
532 {
533 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
534 }
535 
536 
537 void
538 CliContext::ValueNodeChildrenCreated(ValueNode* node)
539 {
540 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
541 }
542 
543 
544 void
545 CliContext::ValueNodeChildrenDeleted(ValueNode* node)
546 {
547 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
548 }
549 
550 
551 void
552 CliContext::ValueNodeValueChanged(ValueNode* oldNode)
553 {
554 	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
555 }
556 
557 
558 void
559 CliContext::_QueueEvent(Event* event)
560 {
561 	if (event == NULL) {
562 		// no memory -- can't do anything about it
563 		return;
564 	}
565 
566 	AutoLocker<BLocker> locker(fLock);
567 	fPendingEvents.Add(event);
568 }
569 
570 
571 void
572 CliContext::_PrepareToWaitForEvents(uint32 eventMask)
573 {
574 	// Set the events we're going to wait for -- always wait for "quit".
575 	AutoLocker<BLocker> locker(fLock);
576 	fInputLoopWaitingForEvents = eventMask | EVENT_QUIT;
577 	fEventsOccurred = fTerminating ? EVENT_QUIT : 0;
578 }
579 
580 
581 uint32
582 CliContext::_WaitForEvents()
583 {
584 	AutoLocker<BLocker> locker(fLock);
585 
586 	if (fEventsOccurred == 0) {
587 		sem_id blockingSemaphore = fBlockingSemaphore;
588 		fInputLoopWaiting = true;
589 
590 		locker.Unlock();
591 
592 		while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) {
593 		}
594 
595 		locker.Lock();
596 	}
597 
598 	uint32 events = fEventsOccurred;
599 	fEventsOccurred = 0;
600 	return events;
601 }
602 
603 
604 void
605 CliContext::_SignalInputLoop(uint32 events)
606 {
607 	AutoLocker<BLocker> locker(fLock);
608 
609 	if ((fInputLoopWaitingForEvents & events) == 0)
610 		return;
611 
612 	fEventsOccurred = fInputLoopWaitingForEvents & events;
613 	fInputLoopWaitingForEvents = 0;
614 
615 	if (fInputLoopWaiting) {
616 		fInputLoopWaiting = false;
617 		release_sem(fBlockingSemaphore);
618 	}
619 }
620 
621 
622 /*static*/ const char*
623 CliContext::_GetPrompt(EditLine* editLine)
624 {
625 	return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL;
626 }
627