200 lines
5.6 KiB
C#
200 lines
5.6 KiB
C#
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using dnlib.DotNet.MD;
|
|
using dnlib.PE;
|
|
using dnSpy.Contracts.Debugger;
|
|
using dnSpy.Contracts.Debugger.DotNet.Metadata.Internal;
|
|
|
|
namespace dnSpy.Debugger.DotNet.Metadata.Internal {
|
|
sealed class DbgRawMetadataImpl : DbgRawMetadata {
|
|
public override bool IsFileLayout {
|
|
get {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
return isFileLayout;
|
|
}
|
|
}
|
|
|
|
public override IntPtr Address {
|
|
get {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
return address;
|
|
}
|
|
}
|
|
|
|
public override int Size {
|
|
get {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
return size;
|
|
}
|
|
}
|
|
|
|
public override IntPtr MetadataAddress {
|
|
get {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
return metadataAddress;
|
|
}
|
|
}
|
|
|
|
public override int MetadataSize {
|
|
get {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
return metadataSize;
|
|
}
|
|
}
|
|
|
|
readonly bool isFileLayout;
|
|
readonly IntPtr address;
|
|
readonly int size;
|
|
readonly IntPtr metadataAddress;
|
|
readonly int metadataSize;
|
|
readonly object lockObj;
|
|
GCHandle moduleBytesHandle;
|
|
readonly DbgProcess? process;
|
|
readonly ulong moduleAddress;
|
|
volatile int referenceCounter;
|
|
volatile bool disposed;
|
|
volatile int freedAddress;
|
|
|
|
public DbgRawMetadataImpl(byte[] moduleBytes, bool isFileLayout) {
|
|
lockObj = new object();
|
|
referenceCounter = 1;
|
|
this.isFileLayout = isFileLayout;
|
|
size = moduleBytes.Length;
|
|
moduleBytesHandle = GCHandle.Alloc(moduleBytes, GCHandleType.Pinned);
|
|
address = moduleBytesHandle.AddrOfPinnedObject();
|
|
(metadataAddress, metadataSize) = GetMetadataInfo();
|
|
}
|
|
|
|
public unsafe DbgRawMetadataImpl(DbgProcess process, bool isFileLayout, ulong moduleAddress, int moduleSize) {
|
|
lockObj = new object();
|
|
referenceCounter = 1;
|
|
this.isFileLayout = isFileLayout;
|
|
size = moduleSize;
|
|
this.process = process;
|
|
this.moduleAddress = moduleAddress;
|
|
|
|
try {
|
|
// Prevent allocation on the LOH. We'll also be able to free the memory as soon as it's not needed.
|
|
address = NativeMethods.VirtualAlloc(IntPtr.Zero, new IntPtr(moduleSize), NativeMethods.MEM_COMMIT, NativeMethods.PAGE_READWRITE);
|
|
if (address == IntPtr.Zero)
|
|
throw new OutOfMemoryException();
|
|
process.ReadMemory(moduleAddress, address.ToPointer(), size);
|
|
(metadataAddress, metadataSize) = GetMetadataInfo();
|
|
}
|
|
catch {
|
|
Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
unsafe (IntPtr metadataAddress, int metadataSize) GetMetadataInfo() {
|
|
try {
|
|
var peImage = new PEImage(address, (uint)size, isFileLayout ? ImageLayout.File : ImageLayout.Memory, true);
|
|
var dotNetDir = peImage.ImageNTHeaders.OptionalHeader.DataDirectories[14];
|
|
if (dotNetDir.VirtualAddress != 0 && dotNetDir.Size >= 0x48) {
|
|
var cor20Reader = peImage.CreateReader(dotNetDir.VirtualAddress, 0x48);
|
|
var cor20 = new ImageCor20Header(ref cor20Reader, true);
|
|
var mdStart = (long)peImage.ToFileOffset(cor20.Metadata.VirtualAddress);
|
|
var mdAddr = new IntPtr((byte*)address + mdStart);
|
|
var mdSize = (int)cor20.Metadata.Size;
|
|
return (mdAddr, mdSize);
|
|
}
|
|
}
|
|
catch (Exception ex) when (ex is IOException || ex is BadImageFormatException) {
|
|
Debug.Fail("Couldn't read .NET metadata");
|
|
}
|
|
return (IntPtr.Zero, 0);
|
|
}
|
|
|
|
~DbgRawMetadataImpl() {
|
|
Debug.Assert(Environment.HasShutdownStarted, nameof(DbgRawMetadataImpl) + " dtor called!");
|
|
Dispose();
|
|
}
|
|
|
|
public unsafe override void UpdateMemory() {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
process?.ReadMemory(moduleAddress, address.ToPointer(), size);
|
|
}
|
|
|
|
internal DbgRawMetadata? TryAddRef() {
|
|
lock (lockObj) {
|
|
if (disposed)
|
|
return null;
|
|
referenceCounter++;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public override DbgRawMetadata AddRef() {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
lock (lockObj)
|
|
referenceCounter++;
|
|
return this;
|
|
}
|
|
|
|
public override void Release() {
|
|
if (disposed)
|
|
throw new ObjectDisposedException(nameof(DbgRawMetadataImpl));
|
|
bool dispose;
|
|
lock (lockObj)
|
|
dispose = --referenceCounter == 0;
|
|
if (dispose)
|
|
Dispose();
|
|
}
|
|
|
|
void Dispose() {
|
|
lock (lockObj) {
|
|
if (disposed)
|
|
return;
|
|
disposed = true;
|
|
}
|
|
ForceDispose();
|
|
}
|
|
|
|
internal void ForceDispose() {
|
|
GC.SuppressFinalize(this);
|
|
if (process is not null && address != IntPtr.Zero && Interlocked.Exchange(ref freedAddress, 1) == 0) {
|
|
bool b = NativeMethods.VirtualFree(address, IntPtr.Zero, NativeMethods.MEM_RELEASE);
|
|
Debug.Assert(b);
|
|
}
|
|
if (process is null) {
|
|
try {
|
|
if (moduleBytesHandle.IsAllocated)
|
|
moduleBytesHandle.Free();
|
|
}
|
|
catch (InvalidOperationException) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|