/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.Breakpoints.Code;
using dnSpy.Contracts.Debugger.Code;
using dnSpy.Contracts.Debugger.DotNet.Code;
using dnSpy.Contracts.Debugger.Engine;
using dnSpy.Contracts.Metadata;
using dnSpy.Debugger.DotNet.Metadata;
using dnSpy.Debugger.DotNet.Mono.Impl.Evaluation;
using dnSpy.Debugger.DotNet.Mono.Properties;
using Mono.Debugger.Soft;
namespace dnSpy.Debugger.DotNet.Mono.Impl {
sealed partial class DbgEngineImpl {
sealed class BoundBreakpointData : IDisposable {
public BreakpointEventRequest? Breakpoint { get; set; }
public ModuleId Module { get; }
public DbgEngineBoundCodeBreakpoint? EngineBoundCodeBreakpoint { get; set; }
public DbgEngineImpl Engine { get; }
public BoundBreakpointData(DbgEngineImpl engine, ModuleId module) {
Engine = engine ?? throw new ArgumentNullException(nameof(engine));
Module = module;
}
public void Dispose() => Engine.RemoveBreakpoint(this);
}
bool SendCodeBreakpointHitMessage_MonoDebug(BreakpointEventRequest breakpoint, DbgThread? thread) {
debuggerThread.VerifyAccess();
if (breakpoint.Tag is BoundBreakpointData bpData) {
if (bpData is not null)
SendMessage(new DbgMessageBreakpoint(bpData.EngineBoundCodeBreakpoint!.BoundCodeBreakpoint, thread, GetMessageFlags()));
else
SendMessage(new DbgMessageBreak(thread, GetMessageFlags()));
return true;
}
else if (breakpoint.Tag is Func callback)
return callback(thread);
else {
Debug.Fail("Breakpoint with invalid Tag data");
return false;
}
}
void RemoveBreakpoint(BoundBreakpointData bpData) {
if (bpData.Breakpoint is null)
return;
lock (lockObj) {
pendingBreakpointsToRemove.Add(bpData.Breakpoint);
if (pendingBreakpointsToRemove.Count == 1)
MonoDebugThread(() => RemoveBreakpoints_MonoDebug());
}
}
readonly List pendingBreakpointsToRemove = new List();
void RemoveBreakpoints_MonoDebug() {
debuggerThread.VerifyAccess();
BreakpointEventRequest[] breakpointsToRemove;
lock (lockObj) {
breakpointsToRemove = pendingBreakpointsToRemove.Count == 0 ? Array.Empty() : pendingBreakpointsToRemove.ToArray();
pendingBreakpointsToRemove.Clear();
}
if (breakpointsToRemove.Length == 0)
return;
try {
using (TempBreak()) {
foreach (var bp in breakpointsToRemove)
bp.Disable();
}
}
catch (VMDisconnectedException) {
}
catch (Exception ex) {
Debug.Fail(ex.Message);
dbgManager.ShowError(ex.Message);
}
}
static Dictionary> CreateDotNetCodeLocationDictionary(DbgCodeLocation[] locations) {
var dict = new Dictionary>();
foreach (var location in locations) {
// The BP could've gotten closed. It's more likely to happen if the debugged process is
// completely paused when it loses keyboard focus. Mono.Debugger.Soft will block until
// it gets a reply back from the now paused process.
if (location.IsClosed)
continue;
if (location is DbgDotNetCodeLocation loc) {
if (!dict.TryGetValue(loc.Module, out var list))
dict.Add(loc.Module, list = new List());
list.Add(loc);
}
}
return dict;
}
public override void AddBreakpoints(DbgModule[] modules, DbgCodeLocation[] locations, bool includeNonModuleBreakpoints) =>
MonoDebugThread(() => AddBreakpointsCore(modules, locations, includeNonModuleBreakpoints));
void AddBreakpointsCore(DbgModule[] modules, DbgCodeLocation[] locations, bool includeNonModuleBreakpoints) {
debuggerThread.VerifyAccess();
try {
var dict = CreateDotNetCodeLocationDictionary(locations);
if (dict.Count == 0)
return;
foreach (var module in modules) {
if (!TryGetModuleData(module, out var data))
continue;
if (dict.TryGetValue(data.ModuleId, out var moduleLocations))
EnableBreakpoints(data.MonoModule, module, moduleLocations);
}
}
catch (Exception ex) {
Debug.Fail(ex.Message);
dbgManager.ShowError(ex.Message);
}
}
void EnableBreakpoints(ModuleMirror monoModule, DbgModule module, List moduleLocations) {
debuggerThread.VerifyAccess();
if (moduleLocations.Count == 0)
return;
var createdBreakpoints = new DbgBoundCodeBreakpointInfo[moduleLocations.Count];
var reflectionModule = module.GetReflectionModule();
var state = module.GetOrCreateData();
for (int i = 0; i < createdBreakpoints.Length; i++) {
var location = moduleLocations[i];
const ulong address = DbgObjectFactory.BoundBreakpointNoAddress;
DbgEngineBoundCodeBreakpointMessage msg;
var method = reflectionModule?.ResolveMethod((int)location.Token, DmdResolveOptions.None);
if (method is null)
msg = DbgEngineBoundCodeBreakpointMessage.CreateFunctionNotFound(GetFunctionName(location.Module, location.Token));
else {
msg = state.IsTypeLoaded(method.DeclaringType!.MetadataToken) ?
DbgEngineBoundCodeBreakpointMessage.CreateCustomWarning(dnSpy_Debugger_DotNet_Mono_Resources.CanNotSetABreakpointWhenProcessIsPaused) :
DbgEngineBoundCodeBreakpointMessage.CreateNoError();
}
var bpData = new BoundBreakpointData(this, location.Module);
createdBreakpoints[i] = new DbgBoundCodeBreakpointInfo(location, module, address, msg, bpData);
}
var boundBreakpoints = objectFactory!.Create(createdBreakpoints.ToArray());
foreach (var ebp in boundBreakpoints) {
if (!ebp.BoundCodeBreakpoint.TryGetData(out BoundBreakpointData? bpData)) {
Debug.Assert(ebp.BoundCodeBreakpoint.IsClosed);
continue;
}
bpData.EngineBoundCodeBreakpoint = ebp;
if (bpData.Breakpoint is not null)
bpData.Breakpoint.Tag = bpData;
}
for (int i = 0; i < boundBreakpoints.Length; i++) {
var boundBp = boundBreakpoints[i];
var location = (DbgDotNetCodeLocation)boundBp.BoundCodeBreakpoint.Breakpoint.Location;
var method = reflectionModule?.ResolveMethod((int)location.Token, DmdResolveOptions.None);
if (method is null)
continue;
state.AddBreakpoint(method.DeclaringType!.MetadataToken, boundBp, () => EnableBreakpointCore(module, method, boundBp, location));
}
}
void EnableBreakpointCore(DbgModule module, DmdMethodBase method, DbgEngineBoundCodeBreakpoint ebp, DbgDotNetCodeLocation location) {
debuggerThread.VerifyAccess();
if (ebp.BoundCodeBreakpoint.IsClosed)
return;
using (TempBreak()) {
var info = CreateBreakpoint(method.Module, location.Module, location.Token, location.Offset);
if (!ebp.BoundCodeBreakpoint.TryGetData(out BoundBreakpointData? bpData)) {
Debug.Assert(ebp.BoundCodeBreakpoint.IsClosed);
return;
}
Debug2.Assert(bpData.Breakpoint is null);
bpData.Breakpoint = info.bp;
if (bpData.Breakpoint is not null)
bpData.Breakpoint.Tag = bpData;
ebp.UpdateMessage(info.error);
}
}
sealed class TypeLoadBreakpointState {
readonly HashSet loadedTypes = new HashSet();
readonly Dictionary> pendingBreakpoints = new Dictionary>();
readonly struct PendingBreakpoint {
public DbgEngineBoundCodeBreakpoint BoundBreakpoint { get; }
public Action OnTypeLoaded { get; }
public PendingBreakpoint(DbgEngineBoundCodeBreakpoint boundBreakpoint, Action onTypeLoaded) {
BoundBreakpoint = boundBreakpoint;
OnTypeLoaded = onTypeLoaded;
}
}
public bool IsTypeLoaded(int metadataToken) => (metadataToken & 0x00FFFFFF) != 0 && loadedTypes.Contains(metadataToken);
public void OnTypeLoaded(TypeMirror monoType) {
int typeToken = monoType.MetadataToken;
if ((typeToken & 0x00FFFFFF) == 0)
return;
// This can fail if it's a generic instantiated type, eg. List if List has already been loaded
bool b = loadedTypes.Add(typeToken);
if (!b)
return;
if (pendingBreakpoints.TryGetValue(typeToken, out var list)) {
pendingBreakpoints.Remove(typeToken);
foreach (var info in list)
NotifyLoaded(info);
}
}
public void AddBreakpoint(int typeToken, DbgEngineBoundCodeBreakpoint boundBreakpoint, Action onTypeLoaded) {
var pendingBreakpoint = new PendingBreakpoint(boundBreakpoint, onTypeLoaded);
if (loadedTypes.Contains(typeToken))
NotifyLoaded(pendingBreakpoint);
else {
if (!pendingBreakpoints.TryGetValue(typeToken, out var list))
pendingBreakpoints.Add(typeToken, list = new List());
list.Add(pendingBreakpoint);
}
}
void NotifyLoaded(in PendingBreakpoint pendingBreakpoint) {
if (!pendingBreakpoint.BoundBreakpoint.BoundCodeBreakpoint.IsClosed)
pendingBreakpoint.OnTypeLoaded();
}
}
void InitializeBreakpoints(TypeMirror monoType) {
debuggerThread.VerifyAccess();
Debug2.Assert(monoType is not null);
if (monoType is null)
return;
var module = TryGetModuleCore_NoCreate(monoType.Module);
if (module is null)
return;
var state = module.GetOrCreateData();
state.OnTypeLoaded(monoType);
}
(BreakpointEventRequest? bp, DbgEngineBoundCodeBreakpointMessage error) CreateBreakpoint(DmdModule module, ModuleId moduleId, uint token, uint offset) {
DmdMethodBase? method;
MethodMirror monoMethod;
try {
method = module.ResolveMethod((int)token);
if (method is null)
return (null, DbgEngineBoundCodeBreakpointMessage.CreateFunctionNotFound(GetFunctionName(moduleId, token)));
monoMethod = MethodCache.GetMethod(method, null);
}
catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) {
return (null, DbgEngineBoundCodeBreakpointMessage.CreateFunctionNotFound(GetFunctionName(moduleId, token)));
}
try {
var bp = vm!.CreateBreakpointRequest(monoMethod, offset);
bp.Enable();
return (bp, DbgEngineBoundCodeBreakpointMessage.CreateNoError());
}
catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) {
// ArgumentException is thrown if we get error NO_SEQ_POINT_AT_IL_OFFSET
return (null, DbgEngineBoundCodeBreakpointMessage.CreateCouldNotCreateBreakpoint());
}
}
static string GetFunctionName(ModuleId module, uint token) => $"0x{token:X8} ({module.ModuleName})";
Dictionary> CreateBoundBreakpointsDictionary(DbgBoundCodeBreakpoint[] boundBreakpoints) {
var dict = new Dictionary>();
foreach (var bound in boundBreakpoints) {
if (!bound.TryGetData(out BoundBreakpointData? bpData) || bpData.Engine != this)
continue;
if (!dict.TryGetValue(bpData.Module, out var list))
dict.Add(bpData.Module, list = new List());
list.Add(bpData);
}
return dict;
}
public override void RemoveBreakpoints(DbgModule[] modules, DbgBoundCodeBreakpoint[] boundBreakpoints, bool includeNonModuleBreakpoints) {
var dict = CreateBoundBreakpointsDictionary(boundBreakpoints);
var bpsToRemove = new List();
foreach (var module in modules) {
if (!TryGetModuleData(module, out var data))
continue;
if (!dict.TryGetValue(data.ModuleId, out var bpDataList))
continue;
bpsToRemove.AddRange(bpDataList);
}
if (bpsToRemove.Count > 0)
bpsToRemove[0].EngineBoundCodeBreakpoint!.Remove(bpsToRemove.Select(a => a.EngineBoundCodeBreakpoint!).ToArray());
}
// Assumes the method's declaring type has already been loaded so we can set a BP
internal BreakpointEventRequest CreateBreakpointForStepper(DbgModule module, uint token, uint offset, Func callback) {
debuggerThread.VerifyAccess();
var reflectionModule = module.GetReflectionModule() ?? throw new InvalidOperationException();
if (!TryGetModuleData(module, out var data))
throw new InvalidOperationException();
var info = CreateBreakpoint(reflectionModule, data.ModuleId, token, offset);
if (info.bp is null)
throw new InvalidOperationException();
info.bp.Tag = callback;
return info.bp;
}
internal void RemoveBreakpointForStepper(BreakpointEventRequest breakpoint) {
debuggerThread.VerifyAccess();
using (TempBreak())
breakpoint.Disable();
}
}
}