IMetaDataEmit::DefineUserString returns HRESULT:-2147024882 (0x8007000E E_OUTOFMEMORY)
IMetaDataEmit::DefineUserString returns HRESULT: -2147024882 (0x8007000E E_OUTOFMEMORY)
我正在玩 unmanaged Profiling interfaces for the CLR。
当 运行 一个 netcoreapp3.1 或 net5.0 控制台应用程序时,在 ICorProfilerCallback::JITCompilationStarted
or in ICorProfilerCallback::ModuleLoadFinished
, any call to IMetaDataEmit::DefineUserString
中将字符串文字存储在控制台应用程序模块中 return 一个 mdString
令牌,return 是 -2147024882 (0x8007000E E_OUTOFMEMORY) 的 HRESULT。无论传递给 IMetaDataEmit::DefineUserString
.
的值是什么,调用 return 都会得到相同的 HRESULT
.NET 应用程序超级简单
using System;
namespace dotnetapp
{
class Program
{
static void Main(string[] args)
{
WriteEnvironmentVariable("CORECLR_ENABLE_PROFILING");
WriteEnvironmentVariable("CORECLR_PROFILER");
WriteEnvironmentVariable("CORECLR_PROFILER_PATH");
Console.WriteLine("Hello World!");
}
static void WriteEnvironmentVariable(string name)
{
var value = Environment.GetEnvironmentVariable(name);
Console.WriteLine($"{name} = {value}");
}
}
}
并构建 运行 与
dotnet build -c Debug dotnetapp.csproj
dotnet bin/Debug/net5.0/dotnetapp.dll
在 运行 应用程序
时设置相关的核心 CLR 分析环境变量
CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={PROFILER CLSID}
CORECLR_PROFILER_PATH=clr_profiler.dll
探查器是使用 com-rs 用 Rust 编写的,对 IMetaDataEmit::DefineUserString
的调用定义为
impl IMetaDataEmit {
pub fn define_user_string(&self, str: &str) -> Result<mdString, HRESULT> {
let mut md_string = mdStringNil;
let mut wide_string = U16CString::from_str(str).unwrap();
let len = wide_string.len() as ULONG;
let ptr = wide_string.as_ptr();
let hr = unsafe { self.DefineUserString(ptr, len, &mut md_string) };
if FAILED(hr) {
log::error!("define user string '{}' failed. HRESULT: {} {:X}", str, hr, hr);
return Err(hr);
}
log::trace!("md_string token {}", md_string);
Ok(md_string)
}
}
其中不安全调用是对 comr-rs 生成的函数
com::interfaces! {
#[uuid("BA3FEE4C-ECB9-4E41-83B7-183FA41CD859")]
pub unsafe interface IMetaDataEmit: IUnknown {
// functions ordered by IMetaDataEmit layout
fn DefineUserString(&self,
szString: LPCWSTR,
cchString: ULONG,
pstk: *mut mdString,
) -> HRESULT;
}
}
我正在使用 U16CString
在探查器的其他地方创建一个 *const u16
指针,以将 LPCWSTR
传递给接口函数,例如 IMetaDataImport::EnumMethodsWithName
,所以我不认为这是一个问题,但我想我会提到它。
失败调用的日志是
TRACE [imetadata_emit] wide_string UCString { inner: [71, 111, 111, 100, 98, 121, 101, 32, 87, 111, 114, 108, 100, 33, 0] }, len 14
ERROR [imetadata_emit] define user string 'Goodbye World!' failed. HRESULT: -2147024882 8007000E
其中 UCString.inner
是指针传递给 IMetaDataEmit::DefineUserString
的 Vec<u16>
。
IMetaDataEmit
是从存储的 ICorProfilerInfo
中检索的,在使用 ICorProfilerInfo::GetModuleMetaData
初始化时传递给探查器,使用 CorOpenFlags
ofRead
和 ofWrite
impl ICorProfilerInfo {
pub fn get_module_metadata<I: Interface>(
&self,
module_id: ModuleID,
open_flags: CorOpenFlags,
) -> Result<I, HRESULT> {
let mut unknown = None;
let hr = unsafe {
self.GetModuleMetaData(module_id, open_flags.bits(), &I::IID as REFIID, &mut unknown as *mut _ as *mut *mut IUnknown)
};
if FAILED(hr) {
log::error!("error fetching metadata for module_id {}, HRESULT: {:X}", module_id, hr);
return Err(hr);
}
Ok(unknown.unwrap())
}
}
其中 GetModuleMetaData
是在使用 com::interfaces!
宏
生成的 ICorProfilerInfo
上定义的
com::interfaces! {
#[uuid("28B5557D-3F3F-48b4-90B2-5F9EEA2F6C48")]
pub unsafe interface ICorProfilerInfo: IUnknown {
// functions ordered by ICorProfilerInfo layout
fn GetModuleMetaData(&self,
moduleId: ModuleID,
dwOpenFlags: DWORD,
riid: REFIID,
ppOut: *mut *mut IUnknown,
) -> HRESULT;
}
}
我好像在 Rust 的某个地方遗漏了一些东西。从 ICorProfilerInfo
、IMetaDataImport
、IMetaDataImport2
检索数据,获取和修改 IL 函数体(更改现有指令)也是如此。我的一个想法是 IMetaDataEmit
是否需要可变,但我认为不需要这样,因为元数据的更改发生在 FFI 边界的 C++ 运行 时间侧。
编辑
我整理了一个简单的 C++ 探查器,它在 ICorProfilerCallback::ModuleLoadFinished
中调用 IMetaDataEmit::DefineUserString
,并且在示例 .NET 应用程序上按预期工作,因此这表明问题出在 Rust 代码的某个地方。
翻看运行时间码,我觉得RegMeta::DefineUserString
is the implementation of DefineUserString
and tracing the code paths, I think the E_OUTOFMEMORY
is coming from StgBlobPool::AddBlob
。
这个问题(显然)是由于 IMetaDataEmit
接口的定义不正确造成的。
无论目标语言是什么,COM 接口定义必须与原始二进制布局完全匹配:所有方法顺序相同(不要相信 MSDN 视觉顺序),从派生接口方法(IUnknown 等)开始。 ),以及每种方法的精确二进制兼容签名。
我正在玩 unmanaged Profiling interfaces for the CLR。
当 运行 一个 netcoreapp3.1 或 net5.0 控制台应用程序时,在 ICorProfilerCallback::JITCompilationStarted
or in ICorProfilerCallback::ModuleLoadFinished
, any call to IMetaDataEmit::DefineUserString
中将字符串文字存储在控制台应用程序模块中 return 一个 mdString
令牌,return 是 -2147024882 (0x8007000E E_OUTOFMEMORY) 的 HRESULT。无论传递给 IMetaDataEmit::DefineUserString
.
.NET 应用程序超级简单
using System;
namespace dotnetapp
{
class Program
{
static void Main(string[] args)
{
WriteEnvironmentVariable("CORECLR_ENABLE_PROFILING");
WriteEnvironmentVariable("CORECLR_PROFILER");
WriteEnvironmentVariable("CORECLR_PROFILER_PATH");
Console.WriteLine("Hello World!");
}
static void WriteEnvironmentVariable(string name)
{
var value = Environment.GetEnvironmentVariable(name);
Console.WriteLine($"{name} = {value}");
}
}
}
并构建 运行 与
dotnet build -c Debug dotnetapp.csproj
dotnet bin/Debug/net5.0/dotnetapp.dll
在 运行 应用程序
时设置相关的核心 CLR 分析环境变量CORECLR_ENABLE_PROFILING=1
CORECLR_PROFILER={PROFILER CLSID}
CORECLR_PROFILER_PATH=clr_profiler.dll
探查器是使用 com-rs 用 Rust 编写的,对 IMetaDataEmit::DefineUserString
的调用定义为
impl IMetaDataEmit {
pub fn define_user_string(&self, str: &str) -> Result<mdString, HRESULT> {
let mut md_string = mdStringNil;
let mut wide_string = U16CString::from_str(str).unwrap();
let len = wide_string.len() as ULONG;
let ptr = wide_string.as_ptr();
let hr = unsafe { self.DefineUserString(ptr, len, &mut md_string) };
if FAILED(hr) {
log::error!("define user string '{}' failed. HRESULT: {} {:X}", str, hr, hr);
return Err(hr);
}
log::trace!("md_string token {}", md_string);
Ok(md_string)
}
}
其中不安全调用是对 comr-rs 生成的函数
com::interfaces! {
#[uuid("BA3FEE4C-ECB9-4E41-83B7-183FA41CD859")]
pub unsafe interface IMetaDataEmit: IUnknown {
// functions ordered by IMetaDataEmit layout
fn DefineUserString(&self,
szString: LPCWSTR,
cchString: ULONG,
pstk: *mut mdString,
) -> HRESULT;
}
}
我正在使用 U16CString
在探查器的其他地方创建一个 *const u16
指针,以将 LPCWSTR
传递给接口函数,例如 IMetaDataImport::EnumMethodsWithName
,所以我不认为这是一个问题,但我想我会提到它。
失败调用的日志是
TRACE [imetadata_emit] wide_string UCString { inner: [71, 111, 111, 100, 98, 121, 101, 32, 87, 111, 114, 108, 100, 33, 0] }, len 14
ERROR [imetadata_emit] define user string 'Goodbye World!' failed. HRESULT: -2147024882 8007000E
其中 UCString.inner
是指针传递给 IMetaDataEmit::DefineUserString
的 Vec<u16>
。
IMetaDataEmit
是从存储的 ICorProfilerInfo
中检索的,在使用 ICorProfilerInfo::GetModuleMetaData
初始化时传递给探查器,使用 CorOpenFlags
ofRead
和 ofWrite
impl ICorProfilerInfo {
pub fn get_module_metadata<I: Interface>(
&self,
module_id: ModuleID,
open_flags: CorOpenFlags,
) -> Result<I, HRESULT> {
let mut unknown = None;
let hr = unsafe {
self.GetModuleMetaData(module_id, open_flags.bits(), &I::IID as REFIID, &mut unknown as *mut _ as *mut *mut IUnknown)
};
if FAILED(hr) {
log::error!("error fetching metadata for module_id {}, HRESULT: {:X}", module_id, hr);
return Err(hr);
}
Ok(unknown.unwrap())
}
}
其中 GetModuleMetaData
是在使用 com::interfaces!
宏
ICorProfilerInfo
上定义的
com::interfaces! {
#[uuid("28B5557D-3F3F-48b4-90B2-5F9EEA2F6C48")]
pub unsafe interface ICorProfilerInfo: IUnknown {
// functions ordered by ICorProfilerInfo layout
fn GetModuleMetaData(&self,
moduleId: ModuleID,
dwOpenFlags: DWORD,
riid: REFIID,
ppOut: *mut *mut IUnknown,
) -> HRESULT;
}
}
我好像在 Rust 的某个地方遗漏了一些东西。从 ICorProfilerInfo
、IMetaDataImport
、IMetaDataImport2
检索数据,获取和修改 IL 函数体(更改现有指令)也是如此。我的一个想法是 IMetaDataEmit
是否需要可变,但我认为不需要这样,因为元数据的更改发生在 FFI 边界的 C++ 运行 时间侧。
编辑
我整理了一个简单的 C++ 探查器,它在 ICorProfilerCallback::ModuleLoadFinished
中调用 IMetaDataEmit::DefineUserString
,并且在示例 .NET 应用程序上按预期工作,因此这表明问题出在 Rust 代码的某个地方。
翻看运行时间码,我觉得RegMeta::DefineUserString
is the implementation of DefineUserString
and tracing the code paths, I think the E_OUTOFMEMORY
is coming from StgBlobPool::AddBlob
。
这个问题(显然)是由于 IMetaDataEmit
接口的定义不正确造成的。
无论目标语言是什么,COM 接口定义必须与原始二进制布局完全匹配:所有方法顺序相同(不要相信 MSDN 视觉顺序),从派生接口方法(IUnknown 等)开始。 ),以及每种方法的精确二进制兼容签名。