【问题标题】:IMetaDataEmit::DefineUserString returns HRESULT: -2147024882 (0x8007000E E_OUTOFMEMORY)IMetaDataEmit::DefineUserString 返回 HRESULT: -2​​147024882 (0x8007000E E_OUTOFMEMORY)
【发布时间】:2021-04-09 03:21:22
【问题描述】:

我在玩unmanaged Profiling interfaces for the CLR

使用 netcoreapp3.1 或 net5.0 控制台应用程序运行时,在 ICorProfilerCallback::JITCompilationStartedICorProfilerCallback::ModuleLoadFinished 中,对 IMetaDataEmit::DefineUserString 的任何调用以在控制台应用程序模块中存储字符串文字并返回 mdString 令牌,返回 -2147024882 (0x8007000E E_OUTOFMEMORY) 的 HRESULT。无论将什么值传递给IMetaDataEmit::DefineUserString,调用都会返回相同的 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

在运行应用程序时设置相关的 Core 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&lt;u16&gt;

IMetaDataEmit 是从存储的 ICorProfilerInfo 中检索出来的

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 的某个地方遗漏了一些东西。从ICorProfilerInfoIMetaDataImportIMetaDataImport2 检索数据,获取和修改 IL 函数体(更改现有指令)也是如此。我的一个想法是IMetaDataEmit 是否可能需要可变,但我认为不必如此,因为元数据的更改发生在 FFI 边界的 C++ 运行时侧。

编辑

我组装了一个简单的 C++ 分析器,它在 ICorProfilerCallback::ModuleLoadFinished 中调用 IMetaDataEmit::DefineUserString,并且在示例 .NET 应用程序上按预期工作,因此这表明问题出在某个地方的 Rust 代码中。

查看运行时代码,我认为RegMeta::DefineUserString is the implementation of DefineUserString 并跟踪代码路径,我认为E_OUTOFMEMORY 来自StgBlobPool::AddBlob

【问题讨论】:

  • 如何定义你的 rust IMetaDataEmit?
  • @SimonMourier 我添加了一些额外的细节。我最初犹豫是否要添加 Rust 实现细节,因为其中有很多,但看起来问题更可能出在 Rust 的某个地方
  • 你能显示完整的 IMetaDataEmit 吗?这里只有一种方法。
  • OH LORDY...@SimonMourier,你应该得到全部的赏金,因为你坚持要看到完整的 IMetaDataEmit 实现。它让我更详细地了解它,我发现我错过了一个功能。如果您愿意发布一个答案,告诉我仔细检查 IMetaDataEmit 函数布局,我很乐意将其标记为已接受,谢谢!

标签: c# .net-core rust clr clr-profiling-api


【解决方案1】:

问题(显然)是由于 IMetaDataEmit 接口的错误定义造成的。

无论目标语言是什么,COM 接口定义必须与原始二进制布局完全匹配:所有方法的顺序相同(不要相信 MSDN 对此的视觉顺序),从派生接口方法开始(IUnknown 等),以及每种方法的精确二进制兼容签名。

【讨论】:

    猜你喜欢
    • 2014-11-23
    • 1970-01-01
    • 2021-10-18
    • 2014-09-08
    • 2020-11-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    相关资源
    最近更新 更多