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