/// <summary>
        /// 使用 clr!DebugDebugger::IsDebuggerAttached() 检测是否存在托管调试器。
        /// 注意,此方法不能检测到非托管调试器(如OllyDbg,x64dbg)的存在。
        /// </summary>
        /// <returns></returns>
        public static bool HasManagedDebugger()
        {
            byte[]     opcodes;
            byte *     pCodeStart;
            byte *     pCodeCurrent;
            byte *     pCodeEnd;
            ldasm_data ldasmData;
            bool       is64Bit;

            InitializeManaged();
            if (_isDebuggerAttached())
            {
                // 此时肯定有托管调试器附加
                return(true);
            }
            // 此时不能保证托管调试器未调试当前进程
            if (_pIsDebuggerAttached[0] == 0x33 && _pIsDebuggerAttached[1] == 0xC0 && _pIsDebuggerAttached[2] == 0xC3)
            {
                // 这是dnSpy反反调试的特征
                return(true);
            }
            // 有可能特征变了,进一步校验
            opcodes      = new byte[_isDebuggerAttachedLength];
            pCodeStart   = _pIsDebuggerAttached;
            pCodeCurrent = pCodeStart;
            pCodeEnd     = _pIsDebuggerAttached + _isDebuggerAttachedLength;
            is64Bit      = sizeof(void *) == 8;
            while (true)
            {
                uint length;

                length = Ldasm.ldasm(pCodeCurrent, &ldasmData, is64Bit);
                if ((ldasmData.flags & Ldasm.F_INVALID) != 0)
                {
                    throw new NotSupportedException();
                }
                CopyOpcode(&ldasmData, pCodeCurrent, opcodes, (uint)(pCodeCurrent - pCodeStart));
                pCodeCurrent += length;
                if (pCodeCurrent == pCodeEnd)
                {
                    break;
                }
            }
            // 复制Opcodes
            if (DynamicCrc32.Compute(opcodes) != _isDebuggerAttachedCrc32)
            {
                // 如果CRC32不相等,那说明CLR可能被Patch了
                return(true);
            }
            return(false);
        }
        private static void InitializeManaged()
        {
            void *        clrModuleHandle;
            StringBuilder stringBuilder;

            byte[] clrFile;

            if (_isManagedInitialized)
            {
                return;
            }
            switch (Environment.Version.Major)
            {
            case 2:
                _pIsDebuggerAttached = (byte *)typeof(Debugger).GetMethod("IsDebuggerAttached", BindingFlags.NonPublic | BindingFlags.Static).MethodHandle.GetFunctionPointer();
                // 和.NET 4.x不一样,这个Debugger.IsAttached的get属性调用了IsDebuggerAttached(),而不是直通CLR内部。
                clrModuleHandle = GetModuleHandle("mscorwks.dll");
                break;

            case 4:
                _pIsDebuggerAttached = (byte *)typeof(Debugger).GetMethod("get_IsAttached").MethodHandle.GetFunctionPointer();
                // Debugger.IsAttached的get属性是一个有[MethodImpl(MethodImplOptions.InternalCall)]特性的方法,意思是实现在CLR内部,而且没有任何stub,直接指向CLR内部。
                // 通过x64dbg调试,可以知道Debugger.get_IsAttached()对应clr!DebugDebugger::IsDebuggerAttached()。
                clrModuleHandle = GetModuleHandle("clr.dll");
                break;

            default:
                throw new NotSupportedException();
            }
            _isDebuggerAttached = (IsDebuggerAttachedDelegate)Marshal.GetDelegateForFunctionPointer((IntPtr)_pIsDebuggerAttached, typeof(IsDebuggerAttachedDelegate));
            if (clrModuleHandle == null)
            {
                throw new InvalidOperationException();
            }
            stringBuilder = new StringBuilder((int)MAX_PATH);
            if (!GetModuleFileName(clrModuleHandle, stringBuilder, MAX_PATH))
            {
                throw new InvalidOperationException();
            }
            clrFile = File.ReadAllBytes(stringBuilder.ToString());
            // 读取CLR模块文件内容
            fixed(byte *pPEImage = clrFile)
            {
                PEInfo     peInfo;
                uint       isDebuggerAttachedRva;
                uint       isDebuggerAttachedFoa;
                byte *     pCodeStart;
                byte *     pCodeCurrent;
                ldasm_data ldasmData;
                bool       is64Bit;

                byte[] opcodes;

                peInfo = new PEInfo(pPEImage);
                isDebuggerAttachedRva = (uint)(_pIsDebuggerAttached - (byte *)clrModuleHandle);
                isDebuggerAttachedFoa = peInfo.ToFOA(isDebuggerAttachedRva);
                pCodeStart            = pPEImage + isDebuggerAttachedFoa;
                pCodeCurrent          = pCodeStart;
                is64Bit = sizeof(void *) == 8;
                opcodes = new byte[0x200];
                // 分配远大于实际函数大小的内存
                while (true)
                {
                    uint length;

                    length = Ldasm.ldasm(pCodeCurrent, &ldasmData, is64Bit);
                    if ((ldasmData.flags & Ldasm.F_INVALID) != 0)
                    {
                        throw new NotSupportedException();
                    }
                    CopyOpcode(&ldasmData, pCodeCurrent, opcodes, (uint)(pCodeCurrent - pCodeStart));
                    if (*pCodeCurrent == 0xC3)
                    {
                        // 找到了第一个ret指令
                        pCodeCurrent += length;
                        break;
                    }
                    pCodeCurrent += length;
                }
                // 复制Opcode直到出现第一个ret
                _isDebuggerAttachedLength = (uint)(pCodeCurrent - pCodeStart);

                fixed(byte *pOpcodes = opcodes)
                _isDebuggerAttachedCrc32 = DynamicCrc32.Compute(pOpcodes, _isDebuggerAttachedLength);
            }

            _isManagedInitialized = true;
        }