I wanted to help out #mark in a question where he is asking for an API to dump many objects from a .NET crash dump file.
So I wrote the following code using mdbgeng, but unfortunately it fails with a NotImplementedException when trying to enumerate the objects in memory.
using System;
using System.Runtime.InteropServices;
using Microsoft.Samples.Debugging.CorDebug;
using Microsoft.Samples.Debugging.CorDebug.Utility;
using Microsoft.Samples.Debugging.MdbgEngine;
using Microsoft.Samples.Debugging.Native;
namespace DumpHeapFromDotNet
{
class Program
{
static void Main(string[] args)
{
var libraryProvider = new LibraryProvider();
var dumpReader = new DumpReader(args[0]);
var dataTarget = new DumpDataTarget(dumpReader);
foreach (var module in dumpReader.EnumerateModules())
{
var clrDebugging = new CLRDebugging();
Version actualVersion;
ClrDebuggingProcessFlags flags;
CorProcess proc;
var hr = (HResult) clrDebugging.TryOpenVirtualProcess(module.BaseAddress, dataTarget, libraryProvider,
new Version(4, 6, int.MaxValue, int.MaxValue), out actualVersion, out flags, out proc);
if (hr < 0)
{
switch (hr)
{
case HResult.CORDBG_E_NOT_CLR:
Console.WriteLine(module.FullName + " is not a .NET module");
break;
case HResult.CORDBG_E_LIBRARY_PROVIDER_ERROR:
Console.WriteLine(module.FullName + " could not provide library");
break;
case HResult.CORDBG_E_UNSUPPORTED_DEBUGGING_MODEL:
case HResult.CORDBG_E_UNSUPPORTED_FORWARD_COMPAT:
break;
default:
Marshal.ThrowExceptionForHR((int)hr);
break;
}
}
else
{
var objects = proc.Objects; // NotImplementedException
foreach (CorObjectValue o in objects)
{
// TODO: Write details of object to file here
}
}
}
Console.ReadLine();
}
}
}
The dump I was using is a .NET 4.6.1076.0 dump with full memory (you can pass a file name as an argument):
0:000> lm vm clr
[...]
ProductVersion: 4.6.1076.0
FileVersion: 4.6.1076.0 built by: NETFXREL3STAGE
0:000> .dumpdebug
----- User Mini Dump Analysis
MINIDUMP_HEADER:
Version A793 (61B1)
NumberOfStreams 11
Flags 1806
0002 MiniDumpWithFullMemory
0004 MiniDumpWithHandleData
0800 MiniDumpWithFullMemoryInfo
1000 MiniDumpWithThreadInfo
I doubt it has something to do with missing mscordacwks or similar, since I just created the dump on the same machine with the same .NET framework as I used for this sample.
Is it really not implemented yet, or am I doing something else wrong?
I'm currently messing with MDBG and I have tried to check the described behavior on real application, not on the dump. I received exatly the same not implemented exception. Looking for the documentation on MSDN I've found the confirmation, that this method is not implemented.
Related
I have a CLR Empty Project (.NET Framework 4.6.2) I've noticed that the Embed Interop Types is not present in the References Properties, I couldn't find a workaround, is there a way to turn it on? It looks like it's only available on C#.
I come up with the following workaround:
Add the particular assembly you want to embed into your project, right click on it, select Properties then set Item Type to Compiled Managed Resource and put the following snippet at the first lines inside the main:
[System::STAThread]
int main()
{
System::AppDomain::CurrentDomain->AssemblyResolve += gcnew System::ResolveEventHandler(&loadEmbeddedAssembly);
}
And at somewhere there:
System::Reflection::Assembly^ loadEmbeddedAssembly(System::Object^ sender, System::ResolveEventArgs^ args) {
System::Reflection::AssemblyName^ assemblyName = gcnew System::Reflection::AssemblyName(args->Name);
System::String^ resourceName = assemblyName->Name + ".dll";
System::IO::Stream^ stream = System::Reflection::Assembly::GetExecutingAssembly()->GetManifestResourceStream(resourceName);
array<System::Byte>^ assemblyData = gcnew array<System::Byte>((unsigned long)stream->Length);
try {
stream->Read(assemblyData, 0, assemblyData->Length);
}
finally {
if (stream != nullptr) delete stream;
}
return System::Reflection::Assembly::Load(assemblyData);
}
I have a NET 5.0 console application, from which I am trying to compile and execute external code BUT also be able to update the code, unload the previously created appdomain and re-compile everything.
This is my entire static class that handles code compilation and assembly loading
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using Microsoft.CodeAnalysis.Emit;
using System.Runtime.Loader;
namespace Scripting
{
public static class ScriptCompiler
{
public static Dictionary<string, AppDomain> _appDomainDict = new();
public static object CompileScript(string scriptpath)
{
var tree = SyntaxFactory.ParseSyntaxTree(File.ReadAllText(scriptpath));
//Adding basic references
List<PortableExecutableReference> refs = new List<PortableExecutableReference>();
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
// A single, immutable invocation to the compiler
// to produce a library
string hash_name = scriptpath.GetHashCode();
if (_appDomainDict.ContainsKey(hash_name))
{
AppDomain.Unload(_appDomainDict[hash_name]);
_appDomainDict.Remove(hash_name);
}
AppDomain new_domain = AppDomain.CreateDomain(hash_name);
_appDomainDict[hash_name] = new_domain;
var compilation = CSharpCompilation.Create(hash_name)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe:true))
.AddReferences(refs.ToArray())
.AddSyntaxTrees(tree);
MemoryStream ms = new MemoryStream();
EmitResult compilationResult = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
if (compilationResult.Success)
{
// Load the assembly
Assembly asm = new_domain.Load(ms.ToArray());
object main_ob = asm.CreateInstance("SomeClass");
ms.Close();
return main_ob;
}
else
{
foreach (Diagnostic codeIssue in compilationResult.Diagnostics)
{
string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}," +
$" Location: { codeIssue.Location.GetLineSpan()}," +
$" Severity: { codeIssue.Severity}";
Callbacks.Logger.Log(typeof(NbScriptCompiler), issue, LogVerbosityLevel.WARNING);
}
return null;
}
}
}
}
Its all good when I am trying load the assembly in the current domain and execute from the instantiated object. The problem with this case is that since I wanna do frequent updates to the code, even if I make sure that the assembly names are different. I'll end up loading a ton of unused assemblies to the current domain.
This is why I've been trying to create a new domain and load the assembly there. But for some reason I get a platform not supported exception. Is this not possible to do in NET 5? Are there any workarounds or am I doing something wrong here.
Ok, it turns out that AppDomain support for NET Core + is very limited and in particular there seems to be only one appdomain
On .NET Core, the AppDomain implementation is limited by design and
does not provide isolation, unloading, or security boundaries. For
.NET Core, there is exactly one AppDomain. Isolation and unloading are
provided through AssemblyLoadContext. Security boundaries should be
provided by process boundaries and appropriate remoting techniques.
Source: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-6.0
And indeed, when trying to use AssemblyLoadContext and create object instances through these contexts everything worked like a charm!
One last note is that if the created context is not marked as collectible, its not possible to unload it. But this can be very easily set during AssemblyLoadContext construction.
My sample app looks as follows.
class Program
{
static List<XmlNode> memList = new List<XmlNode>();
static void Main(string[] args)
{
Console.WriteLine("Press any key to start");
Console.ReadKey();
CauseHighCPU();
}
static public void CauseHighCPU()
{
string str = string.Empty;
for (int i = 0; i < 100000; i++)
{
str += " Hello World";
}
}
}
I expect string concatenation to cause high cpu. When I profile the application using PerfView, this is very loud and clear.
I am trying to do similar analysis using Visual Studio 2017 Diagnostics Hub. Below is what its CPU usage tab shows.
Its Call-tree view not showing any call to Concat, although, there are some External Code here
This makes me think that it may related to something missing in my configration. As you can see here, Enable Just My Code is unchecked.
Also not sure if its related but here is symbols settings.
Any thouhts what could be wrong that is causing VS not showing root cause of high-cpu usage.
You should not look in the options of Debugging but in the options of Performance Tools and then disable "Just my code":
I have created a simple console app and execute it from PerfView via Run Command -> PerfMonTest.exe
I get the log file and see the process of the app. It is expensive as expected (99% CPU ), but when I want to drill down into the expensive methods they are not shown in the list of expensive methods.
Is there something I can do to make them visible?
Here is the view when I selected the process. I would expect CallExpensive and CallCheap in the list:
Selecting the Main Methods doesnt give me the chace to drill further into the called methods
Here is the app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PerfMonTest
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i <= 2000; i++)
{
CallExpensive(1000);
CallCheap(1000);
CallCheap(400);
}
}
public static void CallExpensive(int expense)
{
for (int i = 0; i <= expense; i++)
{
DateTime checkTime = DateTime.Now;
string val = "10" + i.ToString();
}
}
public static void CallCheap(int expense)
{
for (int i = 0; i <= expense; i++)
{
int j = 2;
}
}
}
}
From the screenshots it looks like, you didn't load symbols. If you do, you'll see that most of the time is spent in DateTime.Now.
If you click on Main in the By Name view, you'll go to the Callers view, which will tell you which methods called Main. If you want to drill into what methods Main is calling, you need to go to the Callees view. If you do that, you'll see the break down of what Main calls.
However, in this particular case the logic of CallExpensive and CallCheap is so simple, that the methods will be inlined (in release mode). Because the methods are inlined, they don't appear as part of the calls made from Main as the code has been folded into Main itself.
You can verify that the methods are inlined by attaching a debugger after the methods have run and look at the method descriptors for the type. Here's the output I got:
0:004> !dumpmt -md 004737c0
EEClass: 00471278
Module: 00472e94
Name: ConsoleApplication1.Program
mdToken: 02000002
File: C:\temp\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe
BaseSize: 0xc
ComponentSize: 0x0
Slots in VTable: 8
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDe JIT Name
72064a00 71d66728 PreJIT System.Object.ToString()
72058830 71d66730 PreJIT System.Object.Equals(System.Object)
72058400 71d66750 PreJIT System.Object.GetHashCode()
72051790 71d66764 PreJIT System.Object.Finalize()
0047c01d 004737b8 NONE ConsoleApplication1.Program..ctor()
004d0050 00473794 JIT ConsoleApplication1.Program.Main(System.String[])
0047c015 004737a0 NONE ConsoleApplication1.Program.CallExpensive(Int32)
0047c019 004737ac NONE ConsoleApplication1.Program.CallCheap(Int32)
The fact that CallExpensive and CallCheap have NONE listed in the JIT column indicates that they were inlined (or not called at all, but that's not the case here).
i want to use ILspy debug a dll,as pic:
but it only can show two process:
but in vs2010,i can attach more process:
how to show w3wp.exe in ILspy? who can help me?
Running ILSpy as an administrator solved this problem for me.
From the ILSpy source code (ICSharpCode.ILSpy.Debugger.UI.AttachToProcessWindow):
Process currentProcess = Process.GetCurrentProcess();
foreach (Process process in Process.GetProcesses()) {
try {
if (process.HasExited) continue;
// Prevent attaching to our own process.
if (currentProcess.Id != process.Id) {
bool managed = false;
try {
var modules = process.Modules.Cast<ProcessModule>().Where(
m => m.ModuleName.StartsWith("mscor", StringComparison.OrdinalIgnoreCase));
managed = modules.Count() > 0;
} catch { }
if (managed) {
list.Add(new RunningProcess {
ProcessId = process.Id,
ProcessName = Path.GetFileName(process.MainModule.FileName),
FileName = process.MainModule.FileName,
WindowTitle = process.MainWindowTitle,
Managed = "Managed",
Process = process
});
}
}
} catch (Win32Exception) {
// Do nothing.
}
}
Seems relatively straight forward...
It is preview software, so perhaps there is a flaw in this algorithm for determining if a process uses managed code.
You might be able to move pass this issue just by downloading the source code and changing
bool managed = false;
to
bool managed = true;
and recompiling.
I don't have the full version of IIS7 installed so I can't attempt to recreate your issue, but I doubt I would have the same problem anyways because my visual studio development server shows up fine in ILSpy while yours does not. Perhaps there is something different about your environment that messes with the above algorithm.
32-bit vs 64-bit might also play some role