root/src/servers/debug/DebugServer.cpp
/*
 * 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_MESSAGE_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> %s + %#lx%s", imageName, 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(&fDebugContext, -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;
}