Reputation: 113
I was playing around with this new feature and decided to use dbghelp.dll
's SymGetModuleInfo64
as an example. I wrote the custom marshaller and I am able to run it (below is the generated code), but it throws an AccessViolationException when the result of ConvertToManaged
is assigned back to the ref. Does the ref parameter need special handling? Currently I am just instantiating a IMAGEHELP_MODULE64
and setting its SizeOfStruct
before passing it to SymGetModuleInfo64
.
public unsafe partial class Test
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.10.26716")]
[System.Runtime.CompilerServices.SkipLocalsInitAttribute]
private static partial bool SymGetModuleInfo64(nint hProcess, long ModuleBase64, ref global::Test.Test.IMAGEHELP_MODULE64 imgHelpModule)
{
int __lastError;
global::Test.Test.IMAGEHELP_MODULE64_Marshaller.Unmanaged __imgHelpModule_native;
bool __retVal;
int __retVal_native;
// Marshal - Convert managed data to native data.
__imgHelpModule_native = global::Test.Test.IMAGEHELP_MODULE64_Marshaller.ConvertToUnmanaged(imgHelpModule);
{
System.Runtime.InteropServices.Marshal.SetLastSystemError(0);
__retVal_native = __PInvoke(hProcess, ModuleBase64, &__imgHelpModule_native);
__lastError = System.Runtime.InteropServices.Marshal.GetLastSystemError();
}
// Unmarshal - Convert native data to managed data.
__retVal = __retVal_native != 0;
-----> imgHelpModule = global::Test.Test.IMAGEHELP_MODULE64_Marshaller.ConvertToManaged(__imgHelpModule_native);
System.Runtime.InteropServices.Marshal.SetLastPInvokeError(__lastError);
return __retVal;
// Local P/Invoke
[System.Runtime.InteropServices.DllImportAttribute("dbghelp.dll", EntryPoint = "SymGetModuleInfo64", ExactSpelling = true)]
static extern unsafe int __PInvoke(nint hProcess, long ModuleBase64, global::Test.Test.IMAGEHELP_MODULE64_Marshaller.Unmanaged* imgHelpModule);
}
}
I tried allocating memory for the parameter, to ensure I own it, but no matter what I do I still get the AccessViolationException
.
Here's what the Test
class that generated the code above looks like (NOTE: I was only interested in the LoadedPdbName
so I didn't implement marshalling for the entire struct, but that shouldn't matter, the PInvoke call succeeds):
public unsafe partial class Test
{
[StructLayout(LayoutKind.Sequential)]
[NativeMarshalling(typeof(IMAGEHELP_MODULE64_Marshaller))]
private struct IMAGEHELP_MODULE64
{
public int SizeOfStruct;
public long BaseOfImage;
public int ImageSize;
public int TimeDateStamp;
public int CheckSum;
public int NumSyms;
public SymType SymType;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string ModuleName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string ImageName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string LoadedImageName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string LoadedPdbName;
public int CVSig;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 780)]
public string CVData;
public int PdbSig;
public GUID PdbSig70;
public int PdbAge;
public bool PdbUnmatched;
public bool DbgUnmatched;
public bool LineNumbers;
public bool GlobalSymbols;
public bool TypeInfo;
public bool SourceIndexed;
public bool Publics;
public int MachineType;
public int Reserved;
}
[StructLayout(LayoutKind.Sequential)]
private struct GUID
{
public int Data1;
public ushort Data2;
public ushort Data3;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] data4;
}
[Flags]
private enum SymType : uint
{
SymNone,
SymCoff,
SymCv,
SymPdb,
SymExport,
SymDeferred,
SymSym,
SymDia,
SymVirtual,
}
[CustomMarshaller(typeof(IMAGEHELP_MODULE64), MarshalMode.Default, typeof(IMAGEHELP_MODULE64_Marshaller))]
private static unsafe class IMAGEHELP_MODULE64_Marshaller
{
[StructLayout(LayoutKind.Explicit)]
public ref struct Unmanaged
{
[FieldOffset(0)] public int SizeOfStruct;
[FieldOffset(580)] public fixed byte LoadedPdbName[256];
}
public static Unmanaged ConvertToUnmanaged(IMAGEHELP_MODULE64 managed)
{
return new Unmanaged
{
SizeOfStruct = managed.SizeOfStruct
};
}
public static IMAGEHELP_MODULE64 ConvertToManaged(Unmanaged unmanaged)
{
return new IMAGEHELP_MODULE64
{
LoadedPdbName = AnsiStringMarshaller.ConvertToManaged(unmanaged.LoadedPdbName)
};
}
}
[LibraryImport("dbghelp.dll", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SymGetModuleInfo64(nint hProcess, long ModuleBase64,
ref IMAGEHELP_MODULE64 imgHelpModule);
}
And here's how it's being used:
var moduleInfo = new IMAGEHELP_MODULE64();
moduleInfo.SizeOfStruct = Marshal.SizeOf(moduleInfo);
if (!SymGetModuleInfo64(_processPtr, _baseOfDll, ref moduleInfo))
throw new Win32Exception(Marshal.GetLastWin32Error());
Upvotes: 1
Views: 42
Reputation: 72194
I assumed that
Unmanaged
doesn't need to be fully fleshed out since it carries the full length of the struct underSizeOfStruct
when going in.
You can't just ignore the rest of the data in the struct. You are telling the native function that 1680 (?) bytes are available to be written, but there are actually far less. So C# didn't allocate enough space, meaning you are getting a buffer overrun, wiping out the rest of your stack and probably your return pointer and parameters along with it. Then when you try to assign the ref
value, the ref
location has been overwritten with garbage or zeroes, so you get an AVE.
At the very least, add a reserved field at the end, or set the struct size explicitly:
[StructLayout(LayoutKind.Explicit, Size = 1680)]
public ref struct Unmanaged
{
[FieldOffset(0)] public int SizeOfStruct;
[FieldOffset(580)] public fixed byte LoadedPdbName[256];
}
The best thing would be to just redefine the whole struct again with all the correct buffers, along with associated reading and copying of the values you need.
[StructLayout(LayoutKind.Sequential)]
private struct IMAGEHELP_MODULE64
{
public int SizeOfStruct;
public long BaseOfImage;
public int ImageSize;
public int TimeDateStamp;
public int CheckSum;
public int NumSyms;
public SymType SymType;
public fixed byte ModuleName[32];
public fixed byte ImageName[256];
public fixed byte LoadedImageName[256];
public fixed byte LoadedPdbName[256];
public int CVSig;
public fixed byte CVData[MAX_PATH * 3];
public int PdbSig;
public GUID PdbSig70;
public int PdbAge;
public bool PdbUnmatched;
public bool DbgUnmatched;
public bool LineNumbers;
public bool GlobalSymbols;
public bool TypeInfo;
public bool SourceIndexed;
public bool Publics;
public int MachineType;
public int Reserved;
}
Upvotes: 1