1/*
2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <DebugLooper.h>
8
9#include <new>
10
11#include <AutoLocker.h>
12#include <DebugMessageHandler.h>
13#include <TeamDebugger.h>
14#include <util/DoublyLinkedList.h>
15
16
17struct BDebugLooper::Debugger {
18	BTeamDebugger*			debugger;
19	BDebugMessageHandler*	handler;
20
21	Debugger(BTeamDebugger* debugger, BDebugMessageHandler* handler)
22		:
23		debugger(debugger),
24		handler(handler)
25	{
26	}
27};
28
29
30struct BDebugLooper::Job : DoublyLinkedListLinkImpl<Job> {
31	Job()
32		:
33		fDoneSemaphore(-1)
34	{
35	}
36
37	virtual ~Job()
38	{
39	}
40
41	status_t Wait(BLocker& lock)
42	{
43		fDoneSemaphore = create_sem(0, "debug looper job");
44
45		lock.Unlock();
46
47		while (acquire_sem(fDoneSemaphore) == B_INTERRUPTED) {
48		}
49
50		lock.Lock();
51
52		delete_sem(fDoneSemaphore);
53		fDoneSemaphore = -1;
54
55		return fResult;
56	}
57
58	void Done(status_t result)
59	{
60		fResult = result;
61		release_sem(fDoneSemaphore);
62	}
63
64	virtual status_t Do(BDebugLooper* looper) = 0;
65
66protected:
67	sem_id			fDoneSemaphore;
68	status_t		fResult;
69};
70
71
72struct BDebugLooper::JobList : DoublyLinkedList<Job> {
73};
74
75
76struct BDebugLooper::AddDebuggerJob : Job {
77	AddDebuggerJob(BTeamDebugger* debugger,
78		BDebugMessageHandler* handler)
79		:
80		fDebugger(debugger),
81		fHandler(handler)
82	{
83	}
84
85	virtual status_t Do(BDebugLooper* looper)
86	{
87		Debugger* debugger = new(std::nothrow) Debugger(fDebugger, fHandler);
88		if (debugger == NULL || !looper->fDebuggers.AddItem(debugger)) {
89			delete debugger;
90			return B_NO_MEMORY;
91		}
92
93		return B_OK;
94	}
95
96private:
97	BTeamDebugger*			fDebugger;
98	BDebugMessageHandler*	fHandler;
99};
100
101
102struct BDebugLooper::RemoveDebuggerJob : Job {
103	RemoveDebuggerJob(team_id team)
104		:
105		fTeam(team)
106	{
107	}
108
109	virtual status_t Do(BDebugLooper* looper)
110	{
111		for (int32 i = 0; Debugger* debugger = looper->fDebuggers.ItemAt(i);
112				i++) {
113			if (debugger->debugger->Team() == fTeam) {
114				delete looper->fDebuggers.RemoveItemAt(i);
115				return B_OK;
116			}
117		}
118
119		return B_ENTRY_NOT_FOUND;
120	}
121
122private:
123	team_id	fTeam;
124};
125
126
127// #pragma mark -
128
129
130BDebugLooper::BDebugLooper()
131	:
132	fLock("debug looper"),
133	fThread(-1),
134	fOwnsThread(false),
135	fTerminating(false),
136	fNotified(false),
137	fJobs(NULL),
138	fEventSemaphore(-1)
139{
140}
141
142
143BDebugLooper::~BDebugLooper()
144{
145}
146
147
148status_t
149BDebugLooper::Init()
150{
151	status_t error = fLock.InitCheck();
152	if (error != B_OK)
153		return error;
154
155	AutoLocker<BLocker> locker(fLock);
156
157	if (fThread >= 0)
158		return B_BAD_VALUE;
159
160	if (fJobs == NULL) {
161		fJobs = new(std::nothrow) JobList;
162		if (fJobs == NULL)
163			return B_NO_MEMORY;
164	}
165
166	if (fEventSemaphore < 0) {
167		fEventSemaphore = create_sem(0, "debug looper event");
168		if (fEventSemaphore < 0)
169			return fEventSemaphore;
170	}
171
172	return B_OK;
173}
174
175
176thread_id
177BDebugLooper::Run(bool spawnThread)
178{
179	AutoLocker<BLocker> locker(fLock);
180
181	if (fThread >= 0)
182		return B_BAD_VALUE;
183
184	fNotified = false;
185
186	if (spawnThread) {
187		fThread = spawn_thread(&_MessageLoopEntry, "debug looper",
188			B_NORMAL_PRIORITY, this);
189		if (fThread < 0)
190			return fThread;
191
192		fOwnsThread = true;
193
194		resume_thread(fThread);
195		return B_OK;
196	}
197
198	fThread = find_thread(NULL);
199	fOwnsThread = false;
200
201	_MessageLoop();
202	return B_OK;
203}
204
205
206void
207BDebugLooper::Quit()
208{
209	AutoLocker<BLocker> locker(fLock);
210
211	fTerminating = true;
212	_Notify();
213}
214
215
216status_t
217BDebugLooper::AddTeamDebugger(BTeamDebugger* debugger,
218	BDebugMessageHandler* handler)
219{
220	if (debugger == NULL || handler == NULL)
221		return B_BAD_VALUE;
222
223	AddDebuggerJob job(debugger, handler);
224	return _DoJob(&job);
225}
226
227
228bool
229BDebugLooper::RemoveTeamDebugger(BTeamDebugger* debugger)
230{
231	if (debugger == NULL)
232		return false;
233
234	RemoveDebuggerJob job(debugger->Team());
235	return _DoJob(&job) == B_OK;
236}
237
238
239bool
240BDebugLooper::RemoveTeamDebugger(team_id team)
241{
242	if (team < 0)
243		return false;
244
245	RemoveDebuggerJob job(team);
246	return _DoJob(&job) == B_OK;
247}
248
249
250/*static*/ status_t
251BDebugLooper::_MessageLoopEntry(void* data)
252{
253	return ((BDebugLooper*)data)->_MessageLoop();
254}
255
256
257status_t
258BDebugLooper::_MessageLoop()
259{
260	while (true) {
261		// prepare the wait info array
262		int32 debuggerCount = fDebuggers.CountItems();
263		object_wait_info waitInfos[debuggerCount + 1];
264
265		for (int32 i = 0; i < debuggerCount; i++) {
266			waitInfos[i].object
267				= fDebuggers.ItemAt(i)->debugger->DebuggerPort();
268			waitInfos[i].type = B_OBJECT_TYPE_PORT;
269			waitInfos[i].events = B_EVENT_READ;
270		}
271
272		waitInfos[debuggerCount].object = fEventSemaphore;
273		waitInfos[debuggerCount].type = B_OBJECT_TYPE_SEMAPHORE;
274		waitInfos[debuggerCount].events = B_EVENT_ACQUIRE_SEMAPHORE;
275
276		// wait for the next event
277		wait_for_objects(waitInfos, debuggerCount + 1);
278
279		AutoLocker<BLocker> locker(fLock);
280
281		// handle all pending jobs
282		bool handledJobs = fJobs->Head() != NULL;
283		while (Job* job = fJobs->RemoveHead())
284			job->Done(job->Do(this));
285
286		// acquire notification semaphore and mark unnotified
287		if ((waitInfos[debuggerCount].events & B_EVENT_ACQUIRE_SEMAPHORE) != 0)
288			acquire_sem(fEventSemaphore);
289		fNotified = false;
290
291		if (fTerminating)
292			return B_OK;
293
294		// Always loop when jobs were executed, since that might add/remove
295		// debuggers.
296		if (handledJobs)
297			continue;
298
299		// read a pending port message
300		for (int32 i = 0; i < debuggerCount; i++) {
301			if ((waitInfos[i].events & B_EVENT_READ) != 0) {
302				Debugger* debugger = fDebuggers.ItemAt(i);
303
304				// read the message
305				debug_debugger_message_data message;
306				int32 code;
307				ssize_t messageSize = read_port(
308					debugger->debugger->DebuggerPort(), &code, &message,
309					sizeof(message));
310				if (messageSize < 0)
311					continue;
312
313				// handle the message
314				bool continueThread = debugger->handler->HandleDebugMessage(
315					code, message);
316
317				// If requested, tell the thread to continue (only when there
318				// is a thread and the message was synchronous).
319				if (continueThread && message.origin.thread >= 0
320						&& message.origin.nub_port >= 0) {
321					debugger->debugger->ContinueThread(message.origin.thread);
322				}
323
324				// Handle only one message -- the hook might have added/removed
325				// debuggers which makes further iteration problematic.
326				break;
327			}
328		}
329	}
330}
331
332
333status_t
334BDebugLooper::_DoJob(Job* job)
335{
336	AutoLocker<BLocker> locker(fLock);
337
338	// execute directly, if in looper thread or not running yet
339	if (fThread < 0 || fThread == find_thread(NULL))
340		return job->Do(this);
341
342	// execute in the looper thread
343	fJobs->Add(job);
344	_Notify();
345
346	return job->Wait(fLock);
347}
348
349
350void
351BDebugLooper::_Notify()
352{
353	if (fNotified)
354		return;
355
356	fNotified = true;
357	release_sem(fEventSemaphore);
358}
359