root/src/kits/debugger/debug_managers/BreakpointManager.cpp
/*
 * Copyright 2009-2012, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "BreakpointManager.h"

#include <stdio.h>

#include <new>

#include <AutoLocker.h>

#include "DebuggerInterface.h"
#include "Function.h"
#include "SpecificImageDebugInfo.h"
#include "Statement.h"
#include "Team.h"
#include "Tracing.h"


BreakpointManager::BreakpointManager(Team* team,
        DebuggerInterface* debuggerInterface)
        :
        fLock("breakpoint manager"),
        fTeam(team),
        fDebuggerInterface(debuggerInterface)
{
        fDebuggerInterface->AcquireReference();
}


BreakpointManager::~BreakpointManager()
{
        fDebuggerInterface->ReleaseReference();
}


status_t
BreakpointManager::Init()
{
        return fLock.InitCheck();
}


status_t
BreakpointManager::InstallUserBreakpoint(UserBreakpoint* userBreakpoint,
        bool enabled)
{
        TRACE_CONTROL("BreakpointManager::InstallUserBreakpoint(%p, %d)\n",
                userBreakpoint, enabled);

        AutoLocker<BLocker> installLocker(fLock);
        AutoLocker<Team> teamLocker(fTeam);

        bool oldEnabled = userBreakpoint->IsEnabled();
        if (userBreakpoint->IsValid() && enabled == oldEnabled) {
                TRACE_CONTROL("  user breakpoint already valid and with same enabled "
                        "state\n");
                return B_OK;
        }

        // get/create the breakpoints for all instances
        TRACE_CONTROL("  creating breakpoints for breakpoint instances\n");

        status_t error = B_OK;
        for (int32 i = 0;
                UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {

                TRACE_CONTROL("    breakpoint instance %p\n", instance);

                if (instance->GetBreakpoint() != NULL) {
                        TRACE_CONTROL("    -> already has breakpoint\n");
                        continue;
                }

                target_addr_t address = instance->Address();
                Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
                if (breakpoint == NULL) {
                        TRACE_CONTROL("    -> no breakpoint at that address yet\n");

                        Image* image = fTeam->ImageByAddress(address);
                        if (image == NULL) {
                                TRACE_CONTROL("    -> no image at that address\n");
                                error = B_BAD_ADDRESS;
                                break;
                        }

                        breakpoint = new(std::nothrow) Breakpoint(image, address);
                        if (breakpoint == NULL || !fTeam->AddBreakpoint(breakpoint)) {
                                delete breakpoint;
                                error = B_NO_MEMORY;
                                break;
                        }
                }

                TRACE_CONTROL("    -> adding instance to breakpoint %p\n", breakpoint);

                breakpoint->AddUserBreakpoint(instance);
                instance->SetBreakpoint(breakpoint);
        }

        // If everything looks good so far mark the user breakpoint according to
        // its new state.
        if (error == B_OK)
                userBreakpoint->SetEnabled(enabled);

        // notify user breakpoint listeners
        if (error == B_OK)
                fTeam->NotifyUserBreakpointChanged(userBreakpoint);

        teamLocker.Unlock();

        // install/uninstall the breakpoints as needed
        TRACE_CONTROL("  updating breakpoints\n");

        if (error == B_OK) {
                for (int32 i = 0;
                        UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i);
                        i++) {
                        TRACE_CONTROL("    breakpoint instance %p\n", instance);

                        error = _UpdateBreakpointInstallation(instance->GetBreakpoint());
                        if (error != B_OK)
                                break;
                }
        }

        if (error == B_OK) {
                TRACE_CONTROL("  success, marking user breakpoint valid\n");

                // everything went fine -- mark the user breakpoint valid
                if (!userBreakpoint->IsValid()) {
                        teamLocker.Lock();
                        userBreakpoint->SetValid(true);
                        userBreakpoint->AcquireReference();
                        fTeam->AddUserBreakpoint(userBreakpoint);
                        fTeam->NotifyUserBreakpointChanged(userBreakpoint);
                                // notify again -- the breakpoint hadn't been added before
                        teamLocker.Unlock();
                }
        } else {
                // something went wrong -- revert the situation
                TRACE_CONTROL("  error, reverting\n");

                teamLocker.Lock();
                userBreakpoint->SetEnabled(oldEnabled);
                teamLocker.Unlock();

                if (!oldEnabled || !userBreakpoint->IsValid()) {
                        for (int32 i = 0;  UserBreakpointInstance* instance
                                        = userBreakpoint->InstanceAt(i);
                                i++) {
                                Breakpoint* breakpoint = instance->GetBreakpoint();
                                if (breakpoint == NULL)
                                        continue;

                                if (!userBreakpoint->IsValid()) {
                                        instance->SetBreakpoint(NULL);
                                        breakpoint->RemoveUserBreakpoint(instance);
                                }

                                _UpdateBreakpointInstallation(breakpoint);

                                teamLocker.Lock();

                                if (breakpoint->IsUnused())
                                        fTeam->RemoveBreakpoint(breakpoint);
                                teamLocker.Unlock();
                        }

                        teamLocker.Lock();
                        fTeam->NotifyUserBreakpointChanged(userBreakpoint);
                        teamLocker.Unlock();
                }
        }

        installLocker.Unlock();

        return error;
}


void
BreakpointManager::UninstallUserBreakpoint(UserBreakpoint* userBreakpoint)
{
        AutoLocker<BLocker> installLocker(fLock);
        AutoLocker<Team> teamLocker(fTeam);

        if (!userBreakpoint->IsValid())
                return;

        fTeam->RemoveUserBreakpoint(userBreakpoint);

        userBreakpoint->SetValid(false);
        userBreakpoint->SetEnabled(false);

        teamLocker.Unlock();

        // uninstall the breakpoints as needed
        for (int32 i = 0;
                UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {
                if (Breakpoint* breakpoint = instance->GetBreakpoint())
                        _UpdateBreakpointInstallation(breakpoint);
        }

        teamLocker.Lock();

        // detach the breakpoints from the user breakpoint instances
        for (int32 i = 0;
                UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i); i++) {
                if (Breakpoint* breakpoint = instance->GetBreakpoint()) {
                        instance->SetBreakpoint(NULL);
                        breakpoint->RemoveUserBreakpoint(instance);

                        if (breakpoint->IsUnused())
                                fTeam->RemoveBreakpoint(breakpoint);
                }
        }

        fTeam->NotifyUserBreakpointChanged(userBreakpoint);

        teamLocker.Unlock();
        installLocker.Unlock();

        // release the reference from InstallUserBreakpoint()
        userBreakpoint->ReleaseReference();
}


status_t
BreakpointManager::InstallTemporaryBreakpoint(target_addr_t address,
        BreakpointClient* client)
{
        AutoLocker<BLocker> installLocker(fLock);
        AutoLocker<Team> teamLocker(fTeam);

        // create a breakpoint, if it doesn't exist yet
        Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
        if (breakpoint == NULL) {
                Image* image = fTeam->ImageByAddress(address);
                if (image == NULL)
                        return B_BAD_ADDRESS;

                breakpoint = new(std::nothrow) Breakpoint(image, address);
                if (breakpoint == NULL)
                        return B_NO_MEMORY;

                if (!fTeam->AddBreakpoint(breakpoint))
                        return B_NO_MEMORY;
        }

        BReference<Breakpoint> breakpointReference(breakpoint);

        // add the client
        status_t error;
        if (breakpoint->AddClient(client)) {
                if (breakpoint->IsInstalled())
                        return B_OK;

                // install
                teamLocker.Unlock();

                error = fDebuggerInterface->InstallBreakpoint(address);
                if (error == B_OK) {
                        breakpoint->SetInstalled(true);
                        return B_OK;
                }

                teamLocker.Lock();

                breakpoint->RemoveClient(client);
        } else
                error = B_NO_MEMORY;

        // clean up on error
        if (breakpoint->IsUnused())
                fTeam->RemoveBreakpoint(breakpoint);

        return error;
}


void
BreakpointManager::UninstallTemporaryBreakpoint(target_addr_t address,
        BreakpointClient* client)
{
        AutoLocker<BLocker> installLocker(fLock);
        AutoLocker<Team> teamLocker(fTeam);

        Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
        if (breakpoint == NULL)
                return;

        // remove the client
        breakpoint->RemoveClient(client);

        // check whether the breakpoint needs to be uninstalled
        bool uninstall = !breakpoint->ShouldBeInstalled()
                && breakpoint->IsInstalled();

        // if unused remove it
        BReference<Breakpoint> breakpointReference(breakpoint);
        if (breakpoint->IsUnused())
                fTeam->RemoveBreakpoint(breakpoint);

        teamLocker.Unlock();

        if (uninstall) {
                fDebuggerInterface->UninstallBreakpoint(address);
                breakpoint->SetInstalled(false);
        }
}


void
BreakpointManager::UpdateImageBreakpoints(Image* image)
{
        _UpdateImageBreakpoints(image, false);
}


void
BreakpointManager::RemoveImageBreakpoints(Image* image)
{
        _UpdateImageBreakpoints(image, true);
}


void
BreakpointManager::_UpdateImageBreakpoints(Image* image, bool removeOnly)
{
        AutoLocker<BLocker> installLocker(fLock);
        AutoLocker<Team> teamLocker(fTeam);

        // remove obsolete user breakpoint instances
        BObjectList<Breakpoint> breakpointsToUpdate;
        for (UserBreakpointList::ConstIterator it
                        = fTeam->UserBreakpoints().GetIterator();
                UserBreakpoint* userBreakpoint = it.Next();) {
                int32 instanceCount = userBreakpoint->CountInstances();
                for (int32 i = instanceCount - 1; i >= 0; i--) {
                        UserBreakpointInstance* instance = userBreakpoint->InstanceAt(i);
                        Breakpoint* breakpoint = instance->GetBreakpoint();
                        if (breakpoint == NULL || breakpoint->GetImage() != image)
                                continue;

                        userBreakpoint->RemoveInstanceAt(i);
                        breakpoint->RemoveUserBreakpoint(instance);

                        if (!breakpointsToUpdate.AddItem(breakpoint)) {
                                _UpdateBreakpointInstallation(breakpoint);
                                if (breakpoint->IsUnused())
                                        fTeam->RemoveBreakpoint(breakpoint);
                        }

                        delete instance;
                }
        }

        // update breakpoints
        teamLocker.Unlock();
        for (int32 i = 0; Breakpoint* breakpoint = breakpointsToUpdate.ItemAt(i);
                        i++) {
                _UpdateBreakpointInstallation(breakpoint);
        }

        teamLocker.Lock();
        for (int32 i = 0; Breakpoint* breakpoint = breakpointsToUpdate.ItemAt(i);
                        i++) {
                if (breakpoint->IsUnused())
                        fTeam->RemoveBreakpoint(breakpoint);
        }

        // add breakpoint instances for function instances in the image (if we have
        // an image debug info)
        BObjectList<UserBreakpointInstance> newInstances;
        ImageDebugInfo* imageDebugInfo = image->GetImageDebugInfo();
        if (imageDebugInfo == NULL)
                return;

        for (UserBreakpointList::ConstIterator it
                        = fTeam->UserBreakpoints().GetIterator();
                UserBreakpoint* userBreakpoint = it.Next();) {
                // get the function
                Function* function = fTeam->FunctionByID(
                        userBreakpoint->Location().GetFunctionID());
                if (function == NULL)
                        continue;

                const SourceLocation& sourceLocation
                        = userBreakpoint->Location().GetSourceLocation();
                target_addr_t relativeAddress
                        = userBreakpoint->Location().RelativeAddress();

                // iterate through the function instances
                for (FunctionInstanceList::ConstIterator it
                                = function->Instances().GetIterator();
                        FunctionInstance* functionInstance = it.Next();) {
                        if (functionInstance->GetImageDebugInfo() != imageDebugInfo)
                                continue;

                        // get the breakpoint address for the instance
                        target_addr_t instanceAddress = 0;
                        if (functionInstance->SourceFile() != NULL) {
                                // We have a source file, so get the address for the source
                                // location.
                                Statement* statement = NULL;
                                FunctionDebugInfo* functionDebugInfo
                                        = functionInstance->GetFunctionDebugInfo();
                                functionDebugInfo->GetSpecificImageDebugInfo()
                                        ->GetStatementAtSourceLocation(functionDebugInfo,
                                                sourceLocation, statement);
                                if (statement != NULL) {
                                        instanceAddress = statement->CoveringAddressRange().Start();
                                                // TODO: What about BreakpointAllowed()?
                                        statement->ReleaseReference();
                                        // TODO: Make sure we do hit the function in question!
                                }
                        }

                        if (instanceAddress == 0) {
                                // No source file (or we failed getting the statement), so try
                                // to use the same relative address.
                                if (relativeAddress > functionInstance->Size())
                                        continue;
                                instanceAddress = functionInstance->Address() + relativeAddress;
                                        // TODO: Make sure it does at least hit an instruction!
                        }

                        // create the user breakpoint instance
                        UserBreakpointInstance* instance = new(std::nothrow)
                                UserBreakpointInstance(userBreakpoint, instanceAddress);
                        if (instance == NULL || !newInstances.AddItem(instance)) {
                                delete instance;
                                continue;
                        }

                        if (!userBreakpoint->AddInstance(instance)) {
                                newInstances.RemoveItemAt(newInstances.CountItems() - 1);
                                delete instance;
                        }

                        // get/create the breakpoint for the address
                        target_addr_t address = instance->Address();
                        Breakpoint* breakpoint = fTeam->BreakpointAtAddress(address);
                        if (breakpoint == NULL) {
                                breakpoint = new(std::nothrow) Breakpoint(image, address);
                                if (breakpoint == NULL || !fTeam->AddBreakpoint(breakpoint)) {
                                        delete breakpoint;
                                        break;
                                }
                        }

                        breakpoint->AddUserBreakpoint(instance);
                        instance->SetBreakpoint(breakpoint);
                }
        }

        // install the breakpoints for the new user breakpoint instances
        teamLocker.Unlock();
        for (int32 i = 0; UserBreakpointInstance* instance = newInstances.ItemAt(i);
                        i++) {
                Breakpoint* breakpoint = instance->GetBreakpoint();
                if (breakpoint == NULL
                        || _UpdateBreakpointInstallation(breakpoint) != B_OK) {
                        // something went wrong -- remove the instance
                        teamLocker.Lock();

                        instance->GetUserBreakpoint()->RemoveInstance(instance);
                        if (breakpoint != NULL) {
                                breakpoint->AddUserBreakpoint(instance);
                                if (breakpoint->IsUnused())
                                        fTeam->RemoveBreakpoint(breakpoint);
                        }

                        teamLocker.Unlock();
                }
        }
}


status_t
BreakpointManager::_UpdateBreakpointInstallation(Breakpoint* breakpoint)
{
        bool shouldBeInstalled = breakpoint->ShouldBeInstalled();

        TRACE_CONTROL("BreakpointManager::_UpdateBreakpointInstallation(%p): "
                "should be installed: %d, is installed: %d\n", breakpoint,
                shouldBeInstalled, breakpoint->IsInstalled());

        if (shouldBeInstalled == breakpoint->IsInstalled())
                return B_OK;

        if (shouldBeInstalled) {
                // install
                status_t error = B_OK;
                // if we're not actually connected to a team, silently
                // allow setting the breakpoint so it's saved to settings
                // for when we do connect/have the team in the debugger.
                if (fDebuggerInterface->Connected())
                        fDebuggerInterface->InstallBreakpoint(breakpoint->Address());

                if (error != B_OK)
                        return error;

                TRACE_CONTROL("BREAKPOINT at %#" B_PRIx64 " installed: %s\n",
                        breakpoint->Address(), strerror(error));

                breakpoint->SetInstalled(true);
        } else {
                // uninstall
                fDebuggerInterface->UninstallBreakpoint(breakpoint->Address());

                TRACE_CONTROL("BREAKPOINT at %#" B_PRIx64 " uninstalled\n",
                        breakpoint->Address());

                breakpoint->SetInstalled(false);
        }

        return B_OK;
}