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 "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.
24static CliContext* sCurrentContext;
25
26
27// #pragma mark - Event
28
29
30struct 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
75private:
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
88CliContext::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
114CliContext::~CliContext()
115{
116	Cleanup();
117	sCurrentContext = NULL;
118
119	if (fBlockingSemaphore >= 0)
120		delete_sem(fBlockingSemaphore);
121}
122
123
124status_t
125CliContext::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
169void
170CliContext::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
209void
210CliContext::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
221thread_id
222CliContext::CurrentThreadID() const
223{
224	return fCurrentThread != NULL ? fCurrentThread->ID() : -1;
225}
226
227
228void
229CliContext::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
260void
261CliContext::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
273void
274CliContext::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
291const char*
292CliContext::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
307void
308CliContext::AddLineToInputHistory(const char* line)
309{
310	HistEvent historyEvent;
311	history(fHistory, &historyEvent, H_ENTER, line);
312}
313
314
315void
316CliContext::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
329void
330CliContext::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
372void
373CliContext::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
391void
392CliContext::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
454void
455CliContext::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
463void
464CliContext::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
472void
473CliContext::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
484void
485CliContext::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
497void
498CliContext::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
508void
509CliContext::DebugReportChanged(const Team::DebugReportEvent& event)
510{
511	if (event.GetFinalStatus() == B_OK) {
512		printf("Successfully saved debug report to %s\n",
513			event.GetReportPath());
514	} else {
515		fprintf(stderr, "Failed to write debug report: %s\n", strerror(
516				event.GetFinalStatus()));
517	}
518
519	_QueueEvent(new(std::nothrow) Event(EVENT_DEBUG_REPORT_CHANGED));
520	_SignalInputLoop(EVENT_DEBUG_REPORT_CHANGED);
521}
522
523
524void
525CliContext::CoreFileChanged(const Team::CoreFileChangedEvent& event)
526{
527	printf("Successfully saved core file to %s\n",
528		event.GetTargetPath());
529
530	_QueueEvent(new(std::nothrow) Event(EVENT_CORE_FILE_CHANGED));
531	_SignalInputLoop(EVENT_CORE_FILE_CHANGED);
532}
533
534
535void
536CliContext::MemoryBlockRetrieved(TeamMemoryBlock* block)
537{
538	_QueueEvent(
539		new(std::nothrow) Event(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED,
540			NULL, block));
541	_SignalInputLoop(EVENT_TEAM_MEMORY_BLOCK_RETRIEVED);
542}
543
544
545void
546CliContext::ValueNodeChanged(ValueNodeChild* nodeChild, ValueNode* oldNode,
547	ValueNode* newNode)
548{
549	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
550}
551
552
553void
554CliContext::ValueNodeChildrenCreated(ValueNode* node)
555{
556	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
557}
558
559
560void
561CliContext::ValueNodeChildrenDeleted(ValueNode* node)
562{
563	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
564}
565
566
567void
568CliContext::ValueNodeValueChanged(ValueNode* oldNode)
569{
570	_SignalInputLoop(EVENT_VALUE_NODE_CHANGED);
571}
572
573
574void
575CliContext::_QueueEvent(Event* event)
576{
577	if (event == NULL) {
578		// no memory -- can't do anything about it
579		return;
580	}
581
582	AutoLocker<BLocker> locker(fLock);
583	fPendingEvents.Add(event);
584}
585
586
587void
588CliContext::_PrepareToWaitForEvents(uint32 eventMask)
589{
590	// Set the events we're going to wait for -- always wait for "quit".
591	AutoLocker<BLocker> locker(fLock);
592	fInputLoopWaitingForEvents = eventMask | EVENT_QUIT;
593	fEventsOccurred = fTerminating ? EVENT_QUIT : 0;
594}
595
596
597uint32
598CliContext::_WaitForEvents()
599{
600	AutoLocker<BLocker> locker(fLock);
601
602	if (fEventsOccurred == 0) {
603		sem_id blockingSemaphore = fBlockingSemaphore;
604		fInputLoopWaiting = true;
605
606		locker.Unlock();
607
608		while (acquire_sem(blockingSemaphore) == B_INTERRUPTED) {
609		}
610
611		locker.Lock();
612	}
613
614	uint32 events = fEventsOccurred;
615	fEventsOccurred = 0;
616	return events;
617}
618
619
620void
621CliContext::_SignalInputLoop(uint32 events)
622{
623	AutoLocker<BLocker> locker(fLock);
624
625	if ((fInputLoopWaitingForEvents & events) == 0)
626		return;
627
628	fEventsOccurred = fInputLoopWaitingForEvents & events;
629	fInputLoopWaitingForEvents = 0;
630
631	if (fInputLoopWaiting) {
632		fInputLoopWaiting = false;
633		release_sem(fBlockingSemaphore);
634	}
635}
636
637
638/*static*/ const char*
639CliContext::_GetPrompt(EditLine* editLine)
640{
641	return sCurrentContext != NULL ? sCurrentContext->fPrompt : NULL;
642}
643