/*
 * Copyright 2019, Adrien Destugues, pulkomandy@pulkomandy.tk.
 * Copyright 2011-2014, Rene Gollent, rene@gollent.com.
 * Copyright 2005-2009, Ingo Weinhold, bonefish@users.sf.net.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "DebugWindow.h"
 
#include <map>
 
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
 
#include <AppMisc.h>
#include <AutoDeleter.h>
#include <Autolock.h>
#include <debug_support.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Invoker.h>
#include <Path.h>
 
#include <DriverSettings.h>
#include <MessengerPrivate.h>
#include <RegExp.h>
#include <RegistrarDefs.h>
#include <RosterPrivate.h>
#include <Server.h>
#include <StringList.h>
 
#include <util/DoublyLinkedList.h>
 
 
static const char* kDebuggerSignature = "application/x-vnd.Haiku-Debugger";
static const int32 MSG_DEBUG_THIS_TEAM = 'dbtt';
 
 
//#define TRACE_DEBUG_SERVER
#ifdef TRACE_DEBUG_SERVER
#	define TRACE(x) debug_printf x
#else
#	define TRACE(x) ;
#endif
 
 
using std::map;
using std::nothrow;
 
 
static const char *kSignature = "application/x-vnd.Haiku-debug_server";
 
 
static status_t
action_for_string(const char* action, int32& _action)
{
	if (strcmp(action, "kill") == 0)
		_action = kActionKillTeam;
	else if (strcmp(action, "debug") == 0)
		_action = kActionDebugTeam;
	else if (strcmp(action, "log") == 0
		|| strcmp(action, "report") == 0) {
		_action = kActionSaveReportTeam;
	} else if (strcasecmp(action, "core") == 0)
		_action = kActionWriteCoreFile;
	else if (strcasecmp(action, "user") == 0)
		_action = kActionPromptUser;
	else
		return B_BAD_VALUE;
 
	return B_OK;
}
 
 
static bool
match_team_name(const char* teamName, const char* parameterName)
{
	RegExp expressionMatcher;
	if (expressionMatcher.SetPattern(parameterName,
		RegExp::PATTERN_TYPE_WILDCARD)) {
		BString value = teamName;
		if (parameterName[0] != '/') {
			// the expression in question is a team name match only,
			// so we need to extract that.
			BPath path(teamName);
			if (path.InitCheck() == B_OK)
				value = path.Leaf();
		}
 
		RegExp::MatchResult match = expressionMatcher.Match(value);
		if (match.HasMatched())
			return true;
	}
 
	return false;
}
 
 
static status_t
action_for_team(const char* teamName, int32& _action,
	bool& _explicitActionFound)
{
	status_t error = B_OK;
	BPath path;
	error = find_directory(B_USER_SETTINGS_DIRECTORY, &path);
	if (error != B_OK)
		return error;
 
	path.Append("system/debug_server/settings");
	BDriverSettings settings;
	error = settings.Load(path.Path());
	if (error != B_OK)
		return error;
 
	int32 tempAction;
	if (action_for_string(settings.GetParameterValue("default_action",
		"user", "user"), tempAction) == B_OK) {
		_action = tempAction;
	} else
		_action = kActionPromptUser;
	_explicitActionFound = false;
 
	BDriverParameter parameter = settings.GetParameter("executable_actions");
	for (BDriverParameterIterator iterator = parameter.ParameterIterator();
		iterator.HasNext();) {
		BDriverParameter child = iterator.Next();
		if (!match_team_name(teamName, child.Name()))
			continue;
 
		if (child.CountValues() > 0) {
			if (action_for_string(child.ValueAt(0), tempAction) == B_OK) {
				_action = tempAction;
				_explicitActionFound = true;
			}
		}
 
		break;
	}
 
	return B_OK;
}
 
 
static void
KillTeam(team_id team, const char *appName = NULL)
{
	// get a team info to verify the team still lives
	team_info info;
	if (!appName) {
		status_t error = get_team_info(team, &info);
		if (error != B_OK) {
			debug_printf("debug_server: KillTeam(): Error getting info for "
				"team %" B_PRId32 ": %s\n", team, strerror(error));
			info.args[0] = '\0';
		}
 
		appName = info.args;
	}
 
	debug_printf("debug_server: Killing team %" B_PRId32 " (%s)\n", team,
		appName);
 
	kill_team(team);
}
 
 
// #pragma mark -
 
 
class DebugMessage : public DoublyLinkedListLinkImpl<DebugMessage> {
public:
	DebugMessage()
	{
	}
 
	void SetCode(debug_debugger_message code)		{ fCode = code; }
	debug_debugger_message Code() const				{ return fCode; }
 
	debug_debugger_message_data &Data()				{ return fData; }
	const debug_debugger_message_data &Data() const	{ return fData; }
 
private:
	debug_debugger_message		fCode;
	debug_debugger_message_data	fData;
};
 
typedef DoublyLinkedList<DebugMessage>	DebugMessageList;
 
 
class TeamDebugHandler : public BLocker {
public:
	TeamDebugHandler(team_id team);
	~TeamDebugHandler();
 
	status_t Init(port_id nubPort);
 
	team_id Team() const;
 
	status_t PushMessage(DebugMessage *message);
 
private:
	status_t _PopMessage(DebugMessage *&message);
 
	thread_id _EnterDebugger(bool saveReport);
	status_t _SetupGDBArguments(BStringList &arguments, bool usingConsoled);
	status_t _WriteCoreFile();
	void _KillTeam();
 
	int32 _HandleMessage(DebugMessage *message);
 
	void _LookupSymbolAddress(debug_symbol_lookup_context *lookupContext,
		const void *address, char *buffer, int32 bufferSize);
	void _PrintStackTrace(thread_id thread);
	void _NotifyAppServer(team_id team);
	void _NotifyRegistrar(team_id team, bool openAlert, bool stopShutdown);
 
	status_t _InitGUI();
 
	static status_t _HandlerThreadEntry(void *data);
	status_t _HandlerThread();
 
	bool _ExecutableNameEquals(const char *name) const;
	bool _IsAppServer() const;
	bool _IsInputServer() const;
	bool _IsRegistrar() const;
	bool _IsGUIServer() const;
 
	static const char *_LastPathComponent(const char *path);
	static team_id _FindTeam(const char *name);
	static bool _AreGUIServersAlive();
 
private:
	DebugMessageList		fMessages;
	sem_id					fMessageCountSem;
	team_id					fTeam;
	team_info				fTeamInfo;
	char					fExecutablePath[B_PATH_NAME_LENGTH];
	thread_id				fHandlerThread;
	debug_context			fDebugContext;
};
 
 
class TeamDebugHandlerRoster : public BLocker {
private:
	TeamDebugHandlerRoster()
		:
		BLocker("team debug handler roster")
	{
	}
 
public:
	static TeamDebugHandlerRoster *CreateDefault()
	{
		if (!sRoster)
			sRoster = new(nothrow) TeamDebugHandlerRoster;
 
		return sRoster;
	}
 
	static TeamDebugHandlerRoster *Default()
	{
		return sRoster;
	}
 
	bool AddHandler(TeamDebugHandler *handler)
	{
		if (!handler)
			return false;
 
		BAutolock _(this);
 
		fHandlers[handler->Team()] = handler;
 
		return true;
	}
 
	TeamDebugHandler *RemoveHandler(team_id team)
	{
		BAutolock _(this);
 
		TeamDebugHandler *handler = NULL;
 
		TeamDebugHandlerMap::iterator it = fHandlers.find(team);
		if (it != fHandlers.end()) {
			handler = it->second;
			fHandlers.erase(it);
		}
 
		return handler;
	}
 
	TeamDebugHandler *HandlerFor(team_id team)
	{
		BAutolock _(this);
 
		TeamDebugHandler *handler = NULL;
 
		TeamDebugHandlerMap::iterator it = fHandlers.find(team);
		if (it != fHandlers.end())
			handler = it->second;
 
		return handler;
	}
 
	status_t DispatchMessage(DebugMessage *message)
	{
		if (!message)
			return B_BAD_VALUE;
 
		ObjectDeleter<DebugMessage> messageDeleter(message);
 
		team_id team = message->Data().origin.team;
 
		// get the responsible team debug handler
		BAutolock _(this);
 
		TeamDebugHandler *handler = HandlerFor(team);
		if (!handler) {
			// no handler yet, we need to create one
			handler = new(nothrow) TeamDebugHandler(team);
			if (!handler) {
				KillTeam(team);
				return B_NO_MEMORY;
			}
 
			status_t error = handler->Init(message->Data().origin.nub_port);
			if (error != B_OK) {
				delete handler;
				KillTeam(team);
				return error;
			}
 
			if (!AddHandler(handler)) {
				delete handler;
				KillTeam(team);
				return B_NO_MEMORY;
			}
		}
 
		// hand over the message to it
		handler->PushMessage(message);
		messageDeleter.Detach();
 
		return B_OK;
	}
 
private:
	typedef map<team_id, TeamDebugHandler*>	TeamDebugHandlerMap;
 
	static TeamDebugHandlerRoster	*sRoster;
 
	TeamDebugHandlerMap				fHandlers;
};
 
 
TeamDebugHandlerRoster *TeamDebugHandlerRoster::sRoster = NULL;
 
 
class DebugServer : public BServer {
public:
	DebugServer(status_t &error);
 
	status_t Init();
 
	virtual bool QuitRequested();
 
private:
	static status_t _ListenerEntry(void *data);
	status_t _Listener();
 
	void _DeleteTeamDebugHandler(TeamDebugHandler *handler);
 
private:
	typedef map<team_id, TeamDebugHandler*>	TeamDebugHandlerMap;
 
	port_id				fListenerPort;
	thread_id			fListener;
	bool				fTerminating;
};
 
 
// #pragma mark -
 
 
TeamDebugHandler::TeamDebugHandler(team_id team)
	:
	BLocker("team debug handler"),
	fMessages(),
	fMessageCountSem(-1),
	fTeam(team),
	fHandlerThread(-1)
{
	fDebugContext.nub_port = -1;
	fDebugContext.reply_port = -1;
 
	fExecutablePath[0] = '\0';
}
 
 
TeamDebugHandler::~TeamDebugHandler()
{
	// delete the message count semaphore and wait for the thread to die
	if (fMessageCountSem >= 0)
		delete_sem(fMessageCountSem);
 
	if (fHandlerThread >= 0 && find_thread(NULL) != fHandlerThread) {
		status_t result;
		wait_for_thread(fHandlerThread, &result);
	}
 
	// destroy debug context
	if (fDebugContext.nub_port >= 0)
		destroy_debug_context(&fDebugContext);
 
	// delete the remaining messages
	while (DebugMessage *message = fMessages.Head()) {
		fMessages.Remove(message);
		delete message;
	}
}
 
 
status_t
TeamDebugHandler::Init(port_id nubPort)
{
	// get the team info for the team
	status_t error = get_team_info(fTeam, &fTeamInfo);
	if (error != B_OK) {
		debug_printf("debug_server: TeamDebugHandler::Init(): Failed to get "
			"info for team %" B_PRId32 ": %s\n", fTeam, strerror(error));
		return error;
	}
 
	// get the executable path
	error = BPrivate::get_app_path(fTeam, fExecutablePath);
	if (error != B_OK) {
		debug_printf("debug_server: TeamDebugHandler::Init(): Failed to get "
			"executable path of team %" B_PRId32 ": %s\n", fTeam,
			strerror(error));
 
		fExecutablePath[0] = '\0';
	}
 
	// init a debug context for the handler
	error = init_debug_context(&fDebugContext, fTeam, nubPort);
	if (error != B_OK) {
		debug_printf("debug_server: TeamDebugHandler::Init(): Failed to init "
			"debug context for team %" B_PRId32 ", port %" B_PRId32 ": %s\n",
			fTeam, nubPort, strerror(error));
		return error;
	}
 
	// set team flags
	debug_nub_set_team_flags message;
	message.flags = B_TEAM_DEBUG_PREVENT_EXIT;
 
	send_debug_message(&fDebugContext, B_DEBUG_MESSAGE_SET_TEAM_FLAGS, &message,
		sizeof(message), NULL, 0);
 
	// create the message count semaphore
	char name[B_OS_NAME_LENGTH];
	snprintf(name, sizeof(name), "team %" B_PRId32 " message count", fTeam);
	fMessageCountSem = create_sem(0, name);
	if (fMessageCountSem < 0) {
		debug_printf("debug_server: TeamDebugHandler::Init(): Failed to create "
			"message count semaphore: %s\n", strerror(fMessageCountSem));
		return fMessageCountSem;
	}
 
	// spawn the handler thread
	snprintf(name, sizeof(name), "team %" B_PRId32 " handler", fTeam);
	fHandlerThread = spawn_thread(&_HandlerThreadEntry, name, B_NORMAL_PRIORITY,
		this);
	if (fHandlerThread < 0) {
		debug_printf("debug_server: TeamDebugHandler::Init(): Failed to spawn "
			"handler thread: %s\n", strerror(fHandlerThread));
		return fHandlerThread;
	}
 
	resume_thread(fHandlerThread);
 
	return B_OK;
}
 
 
team_id
TeamDebugHandler::Team() const
{
	return fTeam;
}
 
 
status_t
TeamDebugHandler::PushMessage(DebugMessage *message)
{
	BAutolock _(this);
 
	fMessages.Add(message);
	release_sem(fMessageCountSem);
 
	return B_OK;
}
 
 
status_t
TeamDebugHandler::_PopMessage(DebugMessage *&message)
{
	// acquire the semaphore
	status_t error;
	do {
		error = acquire_sem(fMessageCountSem);
	} while (error == B_INTERRUPTED);
 
	if (error != B_OK)
		return error;
 
	// get the message
	BAutolock _(this);
 
	message = fMessages.Head();
	fMessages.Remove(message);
 
	return B_OK;
}
 
 
status_t
TeamDebugHandler::_SetupGDBArguments(BStringList &arguments, bool usingConsoled)
{
	// prepare the argument vector
	BString teamString;
	teamString.SetToFormat("--pid=%" B_PRId32, fTeam);
 
	status_t error;
	BPath terminalPath;
	if (usingConsoled) {
		error = find_directory(B_SYSTEM_BIN_DIRECTORY, &terminalPath);
		if (error != B_OK) {
			debug_printf("debug_server: can't find system-bin directory: %s\n",
				strerror(error));
			return error;
		}
		error = terminalPath.Append("consoled");
		if (error != B_OK) {
			debug_printf("debug_server: can't append to system-bin path: %s\n",
				strerror(error));
			return error;
		}
	} else {
		error = find_directory(B_SYSTEM_APPS_DIRECTORY, &terminalPath);
		if (error != B_OK) {
			debug_printf("debug_server: can't find system-apps directory: %s\n",
				strerror(error));
			return error;
		}
		error = terminalPath.Append("Terminal");
		if (error != B_OK) {
			debug_printf("debug_server: can't append to system-apps path: %s\n",
				strerror(error));
			return error;
		}
	}
 
	arguments.MakeEmpty();
	if (!arguments.Add(terminalPath.Path()))
		return B_NO_MEMORY;
 
	if (!usingConsoled) {
		BString windowTitle;
		windowTitle.SetToFormat("Debug of Team %" B_PRId32 ": %s", fTeam,
			_LastPathComponent(fExecutablePath));
		if (!arguments.Add("-t") || !arguments.Add(windowTitle))
			return B_NO_MEMORY;
	}
 
	BPath gdbPath;
	error = find_directory(B_SYSTEM_BIN_DIRECTORY, &gdbPath);
	if (error != B_OK) {
		debug_printf("debug_server: can't find system-bin directory: %s\n",
			strerror(error));
		return error;
	}
	error = gdbPath.Append("gdb");
	if (error != B_OK) {
		debug_printf("debug_server: can't append to system-bin path: %s\n",
			strerror(error));
		return error;
	}
	if (!arguments.Add(gdbPath.Path()) || !arguments.Add(teamString))
		return B_NO_MEMORY;
 
	if (strlen(fExecutablePath) > 0 && !arguments.Add(fExecutablePath))
		return B_NO_MEMORY;
 
	return B_OK;
}
 
 
thread_id
TeamDebugHandler::_EnterDebugger(bool saveReport)
{
	TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): team %" B_PRId32
		"\n", fTeam));
 
	// prepare a debugger handover
	TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): preparing "
		"debugger handover for team %" B_PRId32 "...\n", fTeam));
 
	status_t error = send_debug_message(&fDebugContext,
		B_DEBUG_MESSAGE_PREPARE_HANDOVER, NULL, 0, NULL, 0);
	if (error != B_OK) {
		debug_printf("debug_server: Failed to prepare debugger handover: %s\n",
			strerror(error));
		return error;
	}
 
	BStringList arguments;
	const char *argv[16];
	int argc = 0;
 
	bool debugInConsoled = _IsGUIServer() || !_AreGUIServersAlive();
#ifdef HANDOVER_USE_GDB
 
	error = _SetupGDBArguments(arguments, debugInConsoled);
	if (error != B_OK) {
		debug_printf("debug_server: Failed to set up gdb arguments: %s\n",
			strerror(error));
		return error;
	}
 
	// start the terminal
	TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): starting  "
		"terminal (debugger) for team %" B_PRId32 "...\n", fTeam));
 
#elif defined(HANDOVER_USE_DEBUGGER)
	if (!debugInConsoled && !saveReport
		&& be_roster->IsRunning(kDebuggerSignature)) {
 
		// for graphical handovers, check if Debugger is already running,
		// and if it is, simply send it a message to attach to the requested
		// team.
		BMessenger messenger(kDebuggerSignature);
		BMessage message(MSG_DEBUG_THIS_TEAM);
		if (message.AddInt32("team", fTeam) == B_OK
			&& messenger.SendMessage(&message) == B_OK) {
			return 0;
		}
	}
 
	// prepare the argument vector
	BPath debuggerPath;
	if (debugInConsoled) {
		error = find_directory(B_SYSTEM_BIN_DIRECTORY, &debuggerPath);
		if (error != B_OK) {
			debug_printf("debug_server: can't find system-bin directory: %s\n",
				strerror(error));
			return error;
		}
		error = debuggerPath.Append("consoled");
		if (error != B_OK) {
			debug_printf("debug_server: can't append to system-bin path: %s\n",
				strerror(error));
			return error;
		}
 
		if (!arguments.Add(debuggerPath.Path()))
			return B_NO_MEMORY;
	}
 
	error = find_directory(B_SYSTEM_APPS_DIRECTORY, &debuggerPath);
	if (error != B_OK) {
		debug_printf("debug_server: can't find system-apps directory: %s\n",
			strerror(error));
		return error;
	}
	error = debuggerPath.Append("Debugger");
	if (error != B_OK) {
		debug_printf("debug_server: can't append to system-apps path: %s\n",
			strerror(error));
		return error;
	}
	if (!arguments.Add(debuggerPath.Path()))
		return B_NO_MEMORY;
 
	if (debugInConsoled && !arguments.Add("--cli"))
		return B_NO_MEMORY;
 
	BString debuggerParam;
	debuggerParam.SetToFormat("%" B_PRId32, fTeam);
	if (saveReport) {
		if (!arguments.Add("--save-report"))
			return B_NO_MEMORY;
	}
	if (!arguments.Add("--team") || !arguments.Add(debuggerParam))
		return B_NO_MEMORY;
 
	// start the debugger
	TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): starting  "
		"%s debugger for team %" B_PRId32 "...\n",
			debugInConsoled ? "command line" : "graphical", fTeam));
#endif
 
	for (int32 i = 0; i < arguments.CountStrings(); i++)
		argv[argc++] = arguments.StringAt(i).String();
	argv[argc] = NULL;
 
	thread_id thread = load_image(argc, argv, (const char**)environ);
	if (thread < 0) {
		debug_printf("debug_server: Failed to start debugger: %s\n",
			strerror(thread));
		return thread;
	}
	resume_thread(thread);
 
	TRACE(("debug_server: TeamDebugHandler::_EnterDebugger(): debugger started "
		"for team %" B_PRId32 ": thread: %" B_PRId32 "\n", fTeam, thread));
 
	return thread;
}
 
 
void
TeamDebugHandler::_KillTeam()
{
	KillTeam(fTeam, fTeamInfo.args);
}
 
 
status_t
TeamDebugHandler::_WriteCoreFile()
{
	// get a usable path for the core file
	BPath directoryPath;
	status_t error = find_directory(B_DESKTOP_DIRECTORY, &directoryPath);
	if (error != B_OK) {
		debug_printf("debug_server: Couldn't get desktop directory: %s\n",
			strerror(error));
		return error;
	}
 
	const char* executableName = strrchr(fExecutablePath, '/');
	if (executableName == NULL)
		executableName = fExecutablePath;
	else
		executableName++;
 
	BString fileBaseName("core-");
	fileBaseName << executableName << '-' << fTeam;
	BPath filePath;
 
	for (int32 index = 0;; index++) {
		BString fileName(fileBaseName);
		if (index > 0)
			fileName << '-' << index;
 
		error = filePath.SetTo(directoryPath.Path(), fileName.String());
		if (error != B_OK) {
			debug_printf("debug_server: Couldn't get core file path for team %"
				B_PRId32 ": %s\n", fTeam, strerror(error));
			return error;
		}
 
		struct stat st;
		if (lstat(filePath.Path(), &st) != 0) {
			if (errno == B_ENTRY_NOT_FOUND)
				break;
		}
 
		if (index > 1000) {
			debug_printf("debug_server: Couldn't get usable core file path for "
				"team %" B_PRId32 "\n", fTeam);
			return B_ERROR;
		}
	}
 
	debug_nub_write_core_file message;
	message.reply_port = fDebugContext.reply_port;
	strlcpy(message.path, filePath.Path(), sizeof(message.path));
 
	debug_nub_write_core_file_reply reply;
 
	error = send_debug_message(&fDebugContext, B_DEBUG_WRITE_CORE_FILE,
			&message, sizeof(message), &reply, sizeof(reply));
	if (error == B_OK)
		error = reply.error;
	if (error != B_OK) {
		debug_printf("debug_server: Failed to write core file for team %"
			B_PRId32 ": %s\n", fTeam, strerror(error));
	}
 
	return error;
}
 
 
int32
TeamDebugHandler::_HandleMessage(DebugMessage *message)
{
	// This method is called only for the first message the debugger gets for
	// a team. That means only a few messages are actually possible, while
	// others wouldn't trigger the debugger in the first place. So we deal with
	// all of them the same way, by popping up an alert.
	TRACE(("debug_server: TeamDebugHandler::_HandleMessage(): team %" B_PRId32
		", code: %" B_PRId32 "\n", fTeam, (int32)message->Code()));
 
	thread_id thread = message->Data().origin.thread;
 
	// get some user-readable message
	char buffer[512];
	switch (message->Code()) {
		case B_DEBUGGER_MESSAGE_TEAM_DELETED:
			// This shouldn't happen.
			debug_printf("debug_server: Got a spurious "
				"B_DEBUGGER_MESSAGE_TEAM_DELETED message for team %" B_PRId32
				"\n", fTeam);
			return true;
 
		case B_DEBUGGER_MESSAGE_EXCEPTION_OCCURRED:
			get_debug_exception_string(
				message->Data().exception_occurred.exception, buffer,
				sizeof(buffer));
			break;
 
		case B_DEBUGGER_MESSAGE_DEBUGGER_CALL:
		{
			// get the debugger() message
			void *messageAddress = message->Data().debugger_call.message;
			char messageBuffer[128];
			status_t error = B_OK;
			ssize_t bytesRead = debug_read_string(&fDebugContext,
				messageAddress, messageBuffer, sizeof(messageBuffer));
			if (bytesRead < 0)
				error = bytesRead;
 
			if (error == B_OK) {
				sprintf(buffer, "Debugger call: `%s'", messageBuffer);
			} else {
				snprintf(buffer, sizeof(buffer), "Debugger call: %p "
					"(Failed to read message: %s)", messageAddress,
					strerror(error));
			}
			break;
		}
 
		default:
			get_debug_message_string(message->Code(), buffer, sizeof(buffer));
			break;
	}
 
	debug_printf("debug_server: Thread %" B_PRId32 " entered the debugger: %s\n",
		thread, buffer);
 
	_PrintStackTrace(thread);
 
	int32 debugAction = kActionPromptUser;
	bool explicitActionFound = false;
	if (action_for_team(fExecutablePath, debugAction, explicitActionFound)
			!= B_OK) {
		debugAction = kActionPromptUser;
		explicitActionFound = false;
	}
 
	// ask the user whether to debug or kill the team
	if (_IsGUIServer()) {
		// App server, input server, or registrar. We always debug those.
		// if not specifically overridden.
		if (!explicitActionFound)
			debugAction = kActionDebugTeam;
	} else if (debugAction == kActionPromptUser && USE_GUI
		&& _AreGUIServersAlive() && _InitGUI() == B_OK) {
		// normal app -- tell the user
		_NotifyAppServer(fTeam);
		_NotifyRegistrar(fTeam, true, false);
 
		DebugWindow *alert = new DebugWindow(fTeamInfo.args);
 
		// TODO: It would be nice if the alert would go away automatically
		// if someone else kills our teams.
		debugAction = alert->Go();
		if (debugAction < 0) {
			// Happens when closed by escape key
			debugAction = kActionKillTeam;
		}
		_NotifyRegistrar(fTeam, false, debugAction != kActionKillTeam);
	}
 
	return debugAction;
}
 
 
void
TeamDebugHandler::_LookupSymbolAddress(
	debug_symbol_lookup_context *lookupContext, const void *address,
	char *buffer, int32 bufferSize)
{
	// lookup the symbol
	void *baseAddress;
	char symbolName[1024];
	char imageName[B_PATH_NAME_LENGTH];
	bool exactMatch;
	bool lookupSucceeded = false;
	if (lookupContext) {
		status_t error = debug_lookup_symbol_address(lookupContext, address,
			&baseAddress, symbolName, sizeof(symbolName), imageName,
			sizeof(imageName), &exactMatch);
		lookupSucceeded = (error == B_OK);
	}
 
	if (lookupSucceeded) {
		// we were able to look something up
		if (strlen(symbolName) > 0) {
			// we even got a symbol
			snprintf(buffer, bufferSize, "%s + %#lx%s", symbolName,
				(addr_t)address - (addr_t)baseAddress,
				(exactMatch ? "" : " (closest symbol)"));
 
		} else {
			// no symbol: image relative address
			snprintf(buffer, bufferSize, "(%s + %#lx)", imageName,
				(addr_t)address - (addr_t)baseAddress);
		}
 
	} else {
		// lookup failed: find area containing the IP
		bool useAreaInfo = false;
		area_info info;
		ssize_t cookie = 0;
		while (get_next_area_info(fTeam, &cookie, &info) == B_OK) {
			if ((addr_t)info.address <= (addr_t)address
				&& (addr_t)info.address + info.size > (addr_t)address) {
				useAreaInfo = true;
				break;
			}
		}
 
		if (useAreaInfo) {
			snprintf(buffer, bufferSize, "(%s + %#lx)", info.name,
				(addr_t)address - (addr_t)info.address);
		} else if (bufferSize > 0)
			buffer[0] = '\0';
	}
}
 
 
void
TeamDebugHandler::_PrintStackTrace(thread_id thread)
{
	// print a stacktrace
	void *ip = NULL;
	void *stackFrameAddress = NULL;
	status_t error = debug_get_instruction_pointer(&fDebugContext, thread, &ip,
		&stackFrameAddress);
 
	if (error == B_OK) {
		// create a symbol lookup context
		debug_symbol_lookup_context *lookupContext = NULL;
		error = debug_create_symbol_lookup_context(fTeam, -1, &lookupContext);
		if (error != B_OK) {
			debug_printf("debug_server: Failed to create symbol lookup "
				"context: %s\n", strerror(error));
		}
 
		// lookup the IP
		char symbolBuffer[2048];
		_LookupSymbolAddress(lookupContext, ip, symbolBuffer,
			sizeof(symbolBuffer) - 1);
 
		debug_printf("stack trace, current PC %p  %s:\n", ip, symbolBuffer);
 
		for (int32 i = 0; i < 50; i++) {
			debug_stack_frame_info stackFrameInfo;
 
			error = debug_get_stack_frame(&fDebugContext, stackFrameAddress,
				&stackFrameInfo);
			if (error < B_OK || stackFrameInfo.parent_frame == NULL)
				break;
 
			// lookup the return address
			_LookupSymbolAddress(lookupContext, stackFrameInfo.return_address,
				symbolBuffer, sizeof(symbolBuffer) - 1);
 
			debug_printf("  (%p)  %p  %s\n", stackFrameInfo.frame,
				stackFrameInfo.return_address, symbolBuffer);
 
			stackFrameAddress = stackFrameInfo.parent_frame;
		}
 
		// delete the symbol lookup context
		if (lookupContext)
			debug_delete_symbol_lookup_context(lookupContext);
	}
}
 
 
void
TeamDebugHandler::_NotifyAppServer(team_id team)
{
	// This will remove any kWindowScreenFeels of the application, so that
	// the debugger alert is visible on screen
	BRoster::Private roster;
	roster.ApplicationCrashed(team);
}
 
 
void
TeamDebugHandler::_NotifyRegistrar(team_id team, bool openAlert,
	bool stopShutdown)
{
	BMessage notify(BPrivate::B_REG_TEAM_DEBUGGER_ALERT);
	notify.AddInt32("team", team);
	notify.AddBool("open", openAlert);
	notify.AddBool("stop shutdown", stopShutdown);
 
	BRoster::Private roster;
	BMessage reply;
	roster.SendTo(&notify, &reply, false);
}
 
 
status_t
TeamDebugHandler::_InitGUI()
{
	DebugServer *app = dynamic_cast<DebugServer*>(be_app);
	BAutolock _(app);
	return app->InitGUIContext();
}
 
 
status_t
TeamDebugHandler::_HandlerThreadEntry(void *data)
{
	return ((TeamDebugHandler*)data)->_HandlerThread();
}
 
 
status_t
TeamDebugHandler::_HandlerThread()
{
	TRACE(("debug_server: TeamDebugHandler::_HandlerThread(): team %" B_PRId32
		"\n", fTeam));
 
	// get initial message
	TRACE(("debug_server: TeamDebugHandler::_HandlerThread(): team %" B_PRId32
		": getting message...\n", fTeam));
 
	DebugMessage *message;
	status_t error = _PopMessage(message);
	int32 debugAction = kActionKillTeam;
	if (error == B_OK) {
		// handle the message
		debugAction = _HandleMessage(message);
		delete message;
	} else {
		debug_printf("TeamDebugHandler::_HandlerThread(): Failed to pop "
			"initial message: %s", strerror(error));
	}
 
	// kill the team or hand it over to the debugger
	thread_id debuggerThread = -1;
	if (debugAction == kActionKillTeam) {
		// The team shall be killed. Since that is also the handling in case
		// an error occurs while handing over the team to the debugger, we do
		// nothing here.
	} else if (debugAction == kActionWriteCoreFile) {
		_WriteCoreFile();
		debugAction = kActionKillTeam;
	} else if ((debuggerThread = _EnterDebugger(
			debugAction == kActionSaveReportTeam)) >= 0) {
		// wait for the "handed over" or a "team deleted" message
		bool terminate = false;
		do {
			error = _PopMessage(message);
			if (error != B_OK) {
				debug_printf("TeamDebugHandler::_HandlerThread(): Failed to "
					"pop message: %s", strerror(error));
				debugAction = kActionKillTeam;
				break;
			}
 
			if (message->Code() == B_DEBUGGER_MESSAGE_HANDED_OVER) {
				// The team has successfully been handed over to the debugger.
				// Nothing to do.
				terminate = true;
			} else if (message->Code() == B_DEBUGGER_MESSAGE_TEAM_DELETED) {
				// The team died. Nothing to do.
				terminate = true;
			} else {
				// Some message we can ignore. The debugger will take care of
				// it.
 
				// check whether the debugger thread still lives
				thread_info threadInfo;
				if (get_thread_info(debuggerThread, &threadInfo) != B_OK) {
					// the debugger is gone
					debug_printf("debug_server: The debugger for team %"
						B_PRId32 " seems to be gone.", fTeam);
 
					debugAction = kActionKillTeam;
					terminate = true;
				}
			}
 
			delete message;
		} while (!terminate);
	} else
		debugAction = kActionKillTeam;
 
	if (debugAction == kActionKillTeam) {
		// kill the team
		_KillTeam();
	}
 
	// remove this handler from the roster and delete it
	TeamDebugHandlerRoster::Default()->RemoveHandler(fTeam);
 
	delete this;
 
	return B_OK;
}
 
 
bool
TeamDebugHandler::_ExecutableNameEquals(const char *name) const
{
	return strcmp(_LastPathComponent(fExecutablePath), name) == 0;
}
 
 
bool
TeamDebugHandler::_IsAppServer() const
{
	return _ExecutableNameEquals("app_server");
}
 
 
bool
TeamDebugHandler::_IsInputServer() const
{
	return _ExecutableNameEquals("input_server");
}
 
 
bool
TeamDebugHandler::_IsRegistrar() const
{
	return _ExecutableNameEquals("registrar");
}
 
 
bool
TeamDebugHandler::_IsGUIServer() const
{
	// app or input server
	return _IsAppServer() || _IsInputServer() || _IsRegistrar();
}
 
 
const char *
TeamDebugHandler::_LastPathComponent(const char *path)
{
	const char *lastSlash = strrchr(path, '/');
	return lastSlash ? lastSlash + 1 : path;
}
 
 
team_id
TeamDebugHandler::_FindTeam(const char *name)
{
	// Iterate through all teams and check their executable name.
	int32 cookie = 0;
	team_info teamInfo;
	while (get_next_team_info(&cookie, &teamInfo) == B_OK) {
		entry_ref ref;
		if (BPrivate::get_app_ref(teamInfo.team, &ref) == B_OK) {
			if (strcmp(ref.name, name) == 0)
				return teamInfo.team;
		}
	}
 
	return B_ENTRY_NOT_FOUND;
}
 
 
bool
TeamDebugHandler::_AreGUIServersAlive()
{
	return _FindTeam("app_server") >= 0 && _FindTeam("input_server") >= 0
		&& _FindTeam("registrar");
}
 
 
// #pragma mark -
 
 
DebugServer::DebugServer(status_t &error)
	:
	BServer(kSignature, false, &error),
	fListenerPort(-1),
	fListener(-1),
	fTerminating(false)
{
}
 
 
status_t
DebugServer::Init()
{
	// create listener port
	fListenerPort = create_port(10, "kernel listener");
	if (fListenerPort < 0)
		return fListenerPort;
 
	// spawn the listener thread
	fListener = spawn_thread(_ListenerEntry, "kernel listener",
		B_NORMAL_PRIORITY, this);
	if (fListener < 0)
		return fListener;
 
	// register as default debugger
	// TODO: could set default flags
	status_t error = install_default_debugger(fListenerPort);
	if (error != B_OK)
		return error;
 
	// resume the listener
	resume_thread(fListener);
 
	return B_OK;
}
 
 
bool
DebugServer::QuitRequested()
{
	// Never give up, never surrender. ;-)
	return false;
}
 
 
status_t
DebugServer::_ListenerEntry(void *data)
{
	return ((DebugServer*)data)->_Listener();
}
 
 
status_t
DebugServer::_Listener()
{
	while (!fTerminating) {
		// receive the next debug message
		DebugMessage *message = new DebugMessage;
		int32 code;
		ssize_t bytesRead;
		do {
			bytesRead = read_port(fListenerPort, &code, &message->Data(),
				sizeof(debug_debugger_message_data));
		} while (bytesRead == B_INTERRUPTED);
 
		if (bytesRead < 0) {
			debug_printf("debug_server: Failed to read from listener port: "
				"%s. Terminating!\n", strerror(bytesRead));
			exit(1);
		}
TRACE(("debug_server: Got debug message: team: %" B_PRId32 ", code: %" B_PRId32
	"\n", message->Data().origin.team, code));
 
		message->SetCode((debug_debugger_message)code);
 
		// dispatch the message
		TeamDebugHandlerRoster::Default()->DispatchMessage(message);
	}
 
	return B_OK;
}
 
 
// #pragma mark -
 
 
int
main()
{
	status_t error;
 
	// for the time being let the debug server print to the syslog
	int console = open("/dev/dprintf", O_RDONLY);
	if (console < 0) {
		debug_printf("debug_server: Failed to open console: %s\n",
			strerror(errno));
	}
	dup2(console, STDOUT_FILENO);
	dup2(console, STDERR_FILENO);
	close(console);
 
	// create the team debug handler roster
	if (!TeamDebugHandlerRoster::CreateDefault()) {
		debug_printf("debug_server: Failed to create team debug handler "
			"roster.\n");
		exit(1);
	}
 
	// create application
	DebugServer server(error);
	if (error != B_OK) {
		debug_printf("debug_server: Failed to create BApplication: %s\n",
			strerror(error));
		exit(1);
	}
 
	// init application
	error = server.Init();
	if (error != B_OK) {
		debug_printf("debug_server: Failed to init application: %s\n",
			strerror(error));
		exit(1);
	}
 
	server.Run();
 
	return 0;
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fTeamInfo.

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.

V614 Uninitialized variable 'error' used.