/* 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.ComponentModel.Composition; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; using dnlib.DotNet; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.Breakpoints.Code; using dnSpy.Contracts.Debugger.CallStack; using dnSpy.Contracts.Debugger.Code; using dnSpy.Contracts.Debugger.Disassembly; using dnSpy.Contracts.Debugger.DotNet.Code; using dnSpy.Contracts.Debugger.DotNet.Disassembly; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.DotNet.Metadata; using dnSpy.Contracts.Decompiler; using dnSpy.Contracts.Disassembly; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.Disassembly { [ExportDbgRuntimeNativeCodeProvider(null, PredefinedDbgRuntimeKindGuids.DotNet)] sealed class DbgRuntimeNativeCodeProviderImpl : DbgRuntimeNativeCodeProvider { readonly Lazy dbgMetadataService; readonly Lazy dbgModuleIdProviderService; readonly IDecompilerService decompilerService; readonly IDecompiler? ilDecompiler; [ImportingConstructor] DbgRuntimeNativeCodeProviderImpl(Lazy dbgMetadataService, Lazy dbgModuleIdProviderService, IDecompilerService decompilerService) { this.dbgMetadataService = dbgMetadataService; this.dbgModuleIdProviderService = dbgModuleIdProviderService; this.decompilerService = decompilerService; ilDecompiler = decompilerService.AllDecompilers.FirstOrDefault(a => a.GenericGuid == DecompilerConstants.LANGUAGE_IL); } sealed class DotNetSymbolResolver : ISymbolResolver { readonly IDbgDotNetRuntime runtime; public DotNetSymbolResolver(IDbgDotNetRuntime runtime) => this.runtime = runtime; public void Resolve(ulong[] addresses, SymbolResolverResult[] result) { if (!runtime.Dispatcher.TryInvoke(() => ResolveCore(addresses, result))) { // process has exited } } void ResolveCore(ulong[] addresses, SymbolResolverResult[] result) { Debug.Assert(addresses.Length == result.Length); runtime.Dispatcher.VerifyAccess(); for (int i = 0; i < addresses.Length; i++) { ulong address = addresses[i]; if (runtime.TryGetSymbol(address, out var symResult)) result[i] = symResult; } } } static bool HasSequencePoints(in DbgDotNetNativeCode nativeCode) { foreach (var block in nativeCode.Blocks) { if (block.ILOffset >= 0) return true; } return false; } bool CreateResult(IDbgDotNetRuntime runtime, DbgModule? methodModule, uint methodToken, string? header, DbgNativeCodeOptions options, DbgDotNetNativeCode nativeCode, out GetNativeCodeResult result) { if (methodToken == uint.MaxValue) methodToken = 0; var newBlocks = new NativeCodeBlock[nativeCode.Blocks.Length]; for (int i = 0; i < newBlocks.Length; i++) { ref var blocks = ref nativeCode.Blocks[i]; newBlocks[i] = new NativeCodeBlock(blocks.Kind, blocks.Address, blocks.Code, null); } IDecompiler? decompiler = decompilerService.Decompiler; if (decompiler is not null && decompiler.GenericGuid == DecompilerConstants.LANGUAGE_IL) decompiler = null; bool canShowILCode = (options & DbgNativeCodeOptions.ShowILCode) != 0 && ilDecompiler is not null; bool canShowCode = (options & DbgNativeCodeOptions.ShowCode) != 0 && decompiler is not null; NativeVariableInfo[]? nativeVariableInfo = null; if (methodModule is not null && methodToken != 0 && (canShowILCode || canShowCode) && HasSequencePoints(nativeCode)) { var module = dbgMetadataService.Value.TryGetMetadata(methodModule, DbgLoadModuleOptions.AutoLoaded); var method = module?.ResolveToken(methodToken) as MethodDef; if (method is not null) { var cancellationToken = CancellationToken.None; ILSourceStatementProvider ilCodeProvider = default; SourceStatementProvider codeProvider = default; List? ilOffsets = null; if (canShowILCode) { Debug2.Assert(ilDecompiler is not null); var provider = new DecompiledCodeProvider(ilDecompiler, method, cancellationToken); if (provider.TryDecompile()) ilCodeProvider = provider.CreateILCodeProvider(); } if (canShowCode) { var provider = new DecompiledCodeProvider(decompiler, method, cancellationToken); if (provider.TryDecompile()) { codeProvider = provider.CreateCodeProvider(); nativeVariableInfo = provider.CreateNativeVariableInfo(); } } var commentBuilder = new StringBuilder(); var nativeBlocks = nativeCode.Blocks; for (int i = 0; i < newBlocks.Length; i++) { int ilOffset = nativeBlocks[i].ILOffset; if (ilOffset < 0) continue; var block = newBlocks[i]; var info = codeProvider.GetStatement(ilOffset); AddStatement(commentBuilder, info.line, info.span, showStmt: true); if (!ilCodeProvider.IsDefault) { if (ilOffsets is null) ilOffsets = GetILOffsets(nativeBlocks); int endILOffset = GetNextILOffset(ilOffsets, ilOffset); if (endILOffset < 0) endILOffset = ilOffset + 1; info = ilCodeProvider.GetStatement(ilOffset, endILOffset); AddStatement(commentBuilder, info.line, info.span, showStmt: false); } if (commentBuilder.Length != 0) newBlocks[i] = new NativeCodeBlock(block.Kind, block.Address, block.Code, commentBuilder.ToString()); commentBuilder.Length = 0; } } } var newCode = new NativeCode(nativeCode.Kind, nativeCode.Optimization, newBlocks, nativeCode.CodeInfo, nativeVariableInfo, nativeCode.MethodName, nativeCode.ShortMethodName, nativeCode.ModuleName); var symbolResolver = new DotNetSymbolResolver(runtime); result = new GetNativeCodeResult(newCode, symbolResolver, header); return true; } static List GetILOffsets(DbgDotNetNativeCodeBlock[] blocks) { var list = new List(blocks.Length); foreach (var block in blocks) { if (block.ILOffset >= 0) list.Add(block.ILOffset); } list.Sort(); return list; } static int GetNextILOffset(List sortedOffsets, int ilOffset) { int index = sortedOffsets.BinarySearch(ilOffset); if (index < 0) return -1; while (index + 1 < sortedOffsets.Count) { if (sortedOffsets[index + 1] != ilOffset) break; index++; } if (index + 1 == sortedOffsets.Count) return int.MaxValue; return sortedOffsets[index + 1]; } void AddStatement(StringBuilder sb, string lines, TextSpan span, bool showStmt) { if (lines is null) return; Debug.Assert(span.End <= lines.Length); int pos = 0; while (pos < lines.Length) { int nextLineOffset; int eol = lines.IndexOf('\n', pos); if (eol < 0) { eol = lines.Length; nextLineOffset = eol; } else { nextLineOffset = eol + 1; if (eol > 0 && lines[eol - 1] == '\r') eol--; } sb.Append(lines, pos, eol - pos); sb.AppendLine(); // Show statement, but only if it's the first line, and if there are multiple statements on the same line if (showStmt && pos == 0) { int nonSpace = FindNonSpace(lines, pos, eol); int stmtEnd = Math.Min(eol, span.End); if (!(nonSpace >= span.Start && stmtEnd == eol)) { sb.Append(' ', span.Start - pos); sb.Append('^', stmtEnd - span.Start); sb.AppendLine(); } } pos = nextLineOffset; } } static int FindNonSpace(string lines, int pos, int end) { while (pos < end) { if (!char.IsWhiteSpace(lines[pos])) return pos; pos++; } return -1; } static bool TryGetDotNetRuntime(DbgRuntime dbgRuntime, [NotNullWhen(true)] out IDbgDotNetRuntime? runtime) { runtime = null; if (dbgRuntime.Process.State != DbgProcessState.Paused || dbgRuntime.IsClosed) return false; runtime = dbgRuntime.GetDotNetRuntime(); if ((runtime.Features & DbgDotNetRuntimeFeatures.NativeMethodBodies) == 0) { runtime = null; return false; } return true; } public override bool CanGetNativeCode(DbgStackFrame frame) { if (!TryGetDotNetRuntime(frame.Runtime, out var runtime)) return false; // If it's an IL frame (very likely), the body should be available, else we'll fail later in the next method return true; } public override bool TryGetNativeCode(DbgStackFrame frame, DbgNativeCodeOptions options, out GetNativeCodeResult result) { result = default; if (!TryGetDotNetRuntime(frame.Runtime, out var runtime)) return false; if (!runtime.TryGetNativeCode(frame, out var nativeCode)) return false; uint methodToken = frame.FunctionToken; const string? header = null; return CreateResult(runtime, frame.Module, methodToken, header, options, nativeCode, out result); } public override bool CanGetNativeCode(DbgBoundCodeBreakpoint boundBreakpoint) => CanGetNativeCode(boundBreakpoint.Runtime, boundBreakpoint.Breakpoint.Location); public override bool TryGetNativeCode(DbgBoundCodeBreakpoint boundBreakpoint, DbgNativeCodeOptions options, out GetNativeCodeResult result) => TryGetNativeCode(boundBreakpoint.Runtime, boundBreakpoint.Breakpoint.Location, options, out result); public override bool CanGetNativeCode(DbgRuntime dbgRuntime, DbgCodeLocation location) { if (!TryGetDotNetRuntime(dbgRuntime, out var runtime)) return false; return location is IDbgDotNetCodeLocation loc; } public override bool TryGetNativeCode(DbgRuntime dbgRuntime, DbgCodeLocation location, DbgNativeCodeOptions options, out GetNativeCodeResult result) { result = default; if (!TryGetDotNetRuntime(dbgRuntime, out var runtime)) return false; if (!(location is IDbgDotNetCodeLocation loc)) return false; var module = loc.DbgModule ?? dbgModuleIdProviderService.Value.GetModule(loc.Module); if (module is null) return false; var reflectionModule = module.GetReflectionModule(); if (reflectionModule is null) return false; var reflectionMethod = reflectionModule.ResolveMethod((int)loc.Token); if (reflectionMethod is null) return false; if (!runtime.TryGetNativeCode(reflectionMethod, out var nativeCode)) return false; const string? header = null; return CreateResult(runtime, module, loc.Token, header, options, nativeCode, out result); } } }