.NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点

Cadence Virtuoso实战:3分钟搞定反相器参数化设计(附CDF配置避坑指南) 本文详细介绍了如何在Cadence Virtuoso中利用CDF参数传递功能实现反相器的参数化设计,包括PMOS和NMOS的基础搭建、CDF配置技巧及常见报错解决方案。通过实战案例和避坑指南,帮助工程师快速掌握高效设计方法,提升集成电路设计效率。 阅读详情

准备工作

在经过一番准备之后,现在我们可以开始正式使用WinDbg+SOS来调试托管代码了。如果你没有看过前两篇文章,那么请先阅读这两篇文章以对WinDbg+SOS有一个大致的了解。这两篇文章的链接在这里:

.NET Rotor源码研究4 – 修改Rotor使其发送CLR Notification:http://blog.youkuaiyun.com/ATField/archive/2007/05/21/1618535.aspx

.NET Rotor源码研究3 - 调试Rotor托管代码的利器:WinDbg和SOS:http://blog.youkuaiyun.com/ATField/archive/2007/05/12/1606151.aspx

除此之外,还需要准备一个小程序来进行调试,本文所使用的程序如下:(hello.cs)

 

namespace Hello

{

       class Hello

       {

              public static void Main(string[] args)

              {

                     System.Console.WriteLine("Your name please?");

                     string s = System.Console.ReadLine();

                     Welcome(s);

                     Welcome(s);

              }

 

              public static void Welcome(string name)

              {

                     System.Console.WriteLine("Hello " + name);

              }

 

       }

}

打开命令提示符,进入sscli20目录,键入:

env dbg

进入Rotor的调试环境,如果你还没有Build出Rotor的一个Debug版本,那么请参照本系列的第一篇文章来设置你的环境并Build出一个调试版本的Rotor。文章的链接在这里:

.NET Rotor源码研究1 – Building Rotor:http://blog.youkuaiyun.com/ATField/archive/2006/12/31/1471465.aspx

如果已经Build出来了一个Rotor的x86调试版本,那么可以开始动手编译hello.cs (假定hello.cs位于binaries.x86dbg.rotor目录下):

cd binaries.x86dbg.rotor

csc hello.cs

编译之后,启动调试器。这里我们不能直接调试hello.exe,否则.NET将会执行hello.exe,这里我们需要使用clix.exe来运行hello.exe,这样才可以让Rotor来运行hello.exe::

windbg clix hello.exe

请保证WinDbg已经被安装并且在其路径在Path变量中。

程序的加载

启动调试器,我们停在程序加载的位置,Call Stack如下(如果你没有Windows系统DLL所对应的Symbol,那么你看到的会有所不同,这里因为有Symbol,结果更加准确):

ntdll!DbgBreakPoint

ntdll!LdrpDoDebuggerBreak+0x31

ntdll!LdrpInitializeProcess+0xffc

ntdll!_LdrpInitialize+0xf5

ntdll!LdrInitializeThunk+0x10

在本系列的第二篇文章中曾经提到,用到PAL的程序的main实际是在PAL_startup_main,如果你还没有看到第二篇文章的话,连接在这里:

.NET Rotor源码研究2 - PAL :http://blog.youkuaiyun.com/ATField/archive/2007/01/12/1481538.aspx

在调试器中输入:

 

bp clix!PAL_startup_main

g

第一条语句的作用是设置断点于clix.exe的PAL_startup_main函数,第二条语句命令WinDbg继续执行。执行g之后WinDbg很快在clix的main函数停下来,这里的main实际上就是PAL_startup_main,被#define过:

int __cdecl main(int argc, char **argv)

{

// 省略…

nExitCode = Launch(pModuleName, pActualCmdLine);

}

 

DWORD Launch(WCHAR* pFileName, WCHAR* pCmdLine)

{

    // 省略…

    nExitCode = _CorExeMain2(NULL, 0, pFileName, NULL, pCmdLine);

 

    return nExitCode;

}

这里有不少无关的代码,大部分是分析命令行,直接来到Launch函数调用,Launch函数负责启动ModuleName,也就是hello.exe,启动工作由_CorExeMain2执行。在Windbg中F10和F11仍然可以工作(当然命令行也可以)。一路执行到_CorExeMain2然后F11,会发现来到了sscoree的_CorExeMain2函数,位于sscoree_shims.h之中:

 

SSCOREE_SHIM_RET (

                  __int32,

                  STDMANGLE(_CorExeMain2,20),

                  ( PBYTE   pUnmappedPE,

                    DWORD   cUnmappedPE,

                    LPWSTR  pImageNameIn,

                    LPWSTR  pLoadersFileName,

                    LPWSTR  pCmdLine),

                  ( pUnmappedPE,

                    cUnmappedPE,

                    pImageNameIn,

                    pLoadersFileName,

                    pCmdLine),

                  -1)

这个函数代码很奇怪,只是一些函数调用。仔细观察一下这个头文件,发现这个文件是很有规律的由下面内容组成:

 

SSCOREE_LIB_START (mscorwks)

 

SSCOREE_SHIM_RET (

                  HRESULT,

                  STDMANGLE(MetaDataGetDispenser,12),SSCOREE_LIB_END (mscorwks)

…

…

SSCOREE_LIB_END (mscorwks)

 

SSCOREE_LIB_START (mscorpe)

…

SSCOREE_LIB_END (mscorpe)

 

 

SSCOREE_LIB_START (mscordbi)

…

…

这个提示我们SSCOREE.dll会负责将列表中的函数转发到对应的DLL中的对应函数。实际上,这正是sscoree.dll所起到的作用之一,确定Rotor版本,加载对应版本的Rotor,并调用对应版本的Rotor的相应函数,因此sscoree(在.NET中则是mscoree)又被称为Shim。这个SSCOREE_SHIM_RET只是一个宏定义,如下:

#define SSCOREE_SHIM_BODY(FUNC,RET_COMMAND,SIG_RET,SIG_ARGS,ARGS)       /

do {                                                                    /

    SSCOREE_SHIM_CUSTOM_INIT                                            /

    FARPROC proc_addr = SscoreeShimGetProcAddress (                     /

                        SHIMSYM_ ## FUNC,                               /

                        #FUNC);                                         /

    _ASSERTE (proc_addr);                                               /

    if (proc_addr) {                                                    /

        RET_COMMAND ((SIG_RET (STDMETHODCALLTYPE *)SIG_ARGS)proc_addr)ARGS; /

    }                                                                   /

} while (0)

 

 

#define SSCOREE_SHIM_RET(SIG_RET,FUNC,SIG_ARGS,ARGS,ONERROR)            /

extern "C"                                                              /

SIG_RET STDMETHODCALLTYPE FUNC SIG_ARGS                                 /

{                                                                       /

    SSCOREE_SHIM_BODY (FUNC, return, SIG_RET, SIG_ARGS, ARGS);          /

    return ONERROR;                                                     /

}

 

#define SSCOREE_SHIM_NORET(FUNC,SIG_ARGS,ARGS)                          /

extern "C"                                                              /

void STDMETHODCALLTYPE FUNC SIG_ARGS                                    /

{                                                                       /

    SSCOREE_SHIM_BODY (FUNC, ; ,void, SIG_ARGS, ARGS);                  /

}     

可以看到在sscoree中每个类似_CorExeMain2的函数大致作的事情都很类似,首先调用SscoreeShimGetProcAddress获得在Rotor核心DLL中的地址,然后调用之。

回到调试器,按下F11,直接进入SscoreeShimGetProcAddress函数:

FARPROC

SscoreeShimGetProcAddress (

    ShimmedSym SymIndex,

    LPCSTR     SymName)

{

    FARPROC proc;

 

#ifdef TRACE_LOADS

    printf ("SscoreeShimGetProcAddress: Loading Symbol %d (%s)/n",

            SymIndex, g_Syms[SymIndex].Name);

#endif

 

    _ASSERTE (SYM_INDEX_VALID (SymIndex));

    _ASSERTE (SymName);

    _ASSERTE (g_Syms[SymIndex].Name);

    _ASSERTE (!strcmp (g_Syms[SymIndex].Name, SymName));

 

    proc = g_Syms[SymIndex].Proc;

 

    if (proc == NULL) {

        proc = SetupProc(SymIndex, SymName);

    }

 

    return proc;

}

g_Syms是一个全局的数组,用于保存每个函数的实际地址,如果地址=NULL,说明还没有获得此函数的地址,需要调用SetupProc:

static

FARPROC

SetupProc (

    ShimmedSym SymIndex,

    LPCSTR     SymName)

{

    HMODULE lib_handle;

    FARPROC proc;

 

    ShimmedLib LibIndex = FindSymbolsLib (SymIndex);

    _ASSERTE (LIB_INDEX_VALID (LibIndex));

 

#ifdef TRACE_LOADS

    printf ("SscoreeShimGetProcAddress: Loading library %d (%S)/n",

            LibIndex, g_Libs[LibIndex].Name);

#endif

 

    lib_handle = g_Libs[LibIndex].Handle;

    if (lib_handle == NULL) {

        lib_handle = SetupLib (LibIndex);

        if (lib_handle == NULL)

            return NULL;

    }

    _ASSERTE (lib_handle);

 

    proc = g_Syms[SymIndex].Proc;

    if (proc == NULL) {

        proc = GetProcAddress (lib_handle, SymName);

        if (!proc) {

#ifdef _DEBUG

            fprintf (stderr,

                        "SscoreeShimGetProcAddress: GetProcAddress (/"%s/") failed/n",

                        SymName);

#endif

            return proc;

        }

        g_Syms[SymIndex].Proc = proc;

    }

 

    return proc;

}

FindSymbols负责找到函数和DLL之间的对应关系:

ShimmedLib

FindSymbolsLib (

    ShimmedSym SymIndex)

{

    // some trickery to figure out which library this symbol is in

    _ASSERTE (SYM_INDEX_VALID (SymIndex));

       

#define SSCOREE_LIB_START(LIBNAME)                                      /

    if (SymIndex < SHIMLIB_ ## LIBNAME) {                               /

        return LIB_ ## LIBNAME;                                         /

    }                                                                   /

    if (SymIndex == SHIMLIB_ ## LIBNAME) {                              /

        return LIB_ ## MAX_LIB;                                         /

    }

#include "sscoree_shims.h"

   

    return LIB_MAX_LIB;

}

这个函数的实现非常有意思,直接定义了两个宏然后include了sscoree_shims.h。实际上这是一个很有意思的技巧,sscoree_shims.h中以宏的形式保存了每个函数和每个DLL,这样,通过定义宏的内容,可以对同样的sscoree_shims.h中的内容转换成不同的代码,比如这里就是把这个文件转换成了一系列的if语句,判断函数Index的范围,返回DLL(这里称之为LIB)的Index,避免了重复代码。

再回到SetupProc函数,这次需要注意的SetupProc在调用FindSymbolsLib之后接着调用了SetupLib函数:

 

HMODULE

SetupLib (

    ShimmedLib LibIndex)

{

    HMODULE lib_handle;

    WCHAR FullPath[_MAX_PATH];

 

    if (!PAL_GetPALDirectory (FullPath, _MAX_PATH)) {

        return NULL;

    }

    if (wcslen(FullPath) + wcslen(g_Libs[LibIndex].Name) >= _MAX_PATH) {

        SetLastError(ERROR_FILENAME_EXCED_RANGE);

        return NULL;

    }

    wcsncat(FullPath, g_Libs[LibIndex].Name, _MAX_PATH);

 

    lib_handle = LoadLibrary (FullPath);

    if (lib_handle == NULL) {

#ifdef _DEBUG

        fprintf (stderr,

                    "SscoreeShimGetProcAddress: LoadLibrary (/"%S/") failed/n",

                    FullPath);

        DisplayMessageFromSystem(GetLastError());

#endif

        return lib_handle;

    }

    g_Libs[LibIndex].Handle = lib_handle;

 

#ifdef _DEBUG

    // first time we've hit this library. Run some tests.

    SscoreeVerifyLibrary (LibIndex);

#endif

 

    ROTOR_PAL_CTOR_TEST_RUN(SSCOREE_INT);

 

    return lib_handle;

}这个函数不长,根据PAL所在目录加载对应的DLL从而实现不同版本的Rotor共存的功能,并且返回加载的DLL的Handle。这里我们所需要的DLL是mscorwks.dll,是.NET / Rotor 虚拟机的工作站(WorkStation)版本的核心DLL。

执行到wcsncat语句之后,在调试器中输入:

 

dv FullPath

这条命令作用是显示FullPath局部变量的值,结果为:

      FullPath = wchar_t [260] "D:/usr/src/sscli20/binaries.x86dbg.rotor/mscorwks.dll"

可以看到我们需要运行mscorwks!_CorExeMain。

再度回到SetupProc,这次SetupProc调用GetProcAddress获得对应函数的地址并保存,然后返回。下面的代码就不需要继续执行了。在调试器中输入下面语句:

 

g mscorwks!_CorExeMain2

这条语句让WinDbg执行程序直到遇见mscorwks!_CorExeMain2函数为止:

//*****************************************************************************

// This entry point is called from the native entry piont of the loaded

// executable image.  The command line arguments and other entry point data

// will be gathered here.  The entry point for the user image will be found

// and handled accordingly.

//*****************************************************************************

__int32 STDMETHODCALLTYPE _CorExeMain2( // Executable exit code.

    PBYTE   pUnmappedPE,                // -> memory mapped code

    DWORD   cUnmappedPE,                // Size of memory mapped code

    __in LPWSTR  pImageNameIn,          // -> Executable Name

    __in LPWSTR  pLoadersFileName,      // -> Loaders Name

    __in LPWSTR  pCmdLine)              // -> Command Line

{

 

加载SOS,设置断点

对了,现在我们还需要加载SOS,因为SOS需要mscorwks,因此在这个时候加载SOS正合适。在调试器中输入:

 

.loadby sos mscorwks

这条语句负责将和mscorwks在同一目录下的sos.dll作为WinDbg的Extension加载。如果你没有看到任何提示信息,那么加载成功了。如果提示出错,请检查在binaries.x86dbg.rotor目录下面确实存在SOS.dll,并且WinDbg已经被修改过或者MSVCR80D.dll在路径中,具体可以参考本系列第3篇文章:

.NET Rotor源码研究3 - 调试Rotor托管代码的利器:WinDbg和SOS:http://blog.youkuaiyun.com/ATField/archive/2007/05/12/1606151.aspx

成功加载之后,为了验证之前我们对IsDebuggerPresent的修改确实生效,输入:

!bpmd hello.exe Hello.Hello.Main

g

前一条命令是SOS命令,负责对Hello.Hello.Main函数设置断点,实际上在CLR中设置断点要比一般程序中设置断点要复杂的多,并且需要notification才可以工作,在后面我将会讲到具体的过程。后面的g命令告诉WinDbg继续执行,注意在WinDbg的输出有如下内容:

(11fc.1088): CLR notification exception - code e0444143 (first chance)

CLR notification: module 'sorttbls.nlp' loaded

(11fc.1088): CLR notification exception - code e0444143 (first chance)

(11fc.1088): CLR notification exception - code e0444143 (first chance)

CLR notification: method 'Hello.Hello.Main(System.String[])' code generated

(11fc.1088): CLR notification exception - code e0444143 (first chance)

JITTED hello!Hello.Hello.Main(System.String[])

若干CLR Notification已经发出,最重要的是最后一个notification,通知Hello.Hello.Main已经被JIT编译成功,之后很快WinDbg在Hello.Hello.Main函数停下了,说明断点设置成功。

OK,至此我们在Windbg中完整地跟踪了CLIX的启动过程和SSCOREE的函数转发,并成功加载了SOS。下一篇文章将回归mscorwks!_CorExeMain2函数,继续我们在.NET / Rotor之中的探索过程,请继续关注。

 

作者:      张羿/ATField
Blog:     
http://blog.youkuaiyun.com/atfield
转载请注明出处

 

 




黏菌算法(Slime Mould Algorithm,SMA) 这是一篇关于黏菌算法的总结博客,包含算法思想,算法步骤,求函数最值(Python实现),算法改进等,持续更新ing 阅读详情

相关推荐

施密特触发电路详细原理介绍

我们想获得矩形脉冲,有种方法是可以采用整形的方法获得矩形脉冲,但前提是可以找到频率和幅度都符合要求的电压信号。 我们设定矩形脉冲的几个指标。 施密特电路是脉冲波形变换中一种常采用的电路,主要有以下两个特点: 1.输入电平从低电平上升过程中电路状态转换为对应的输入电平,与高电平下降过程中对应的输入转换电平不同。 2.通过电路内部的正反馈使输出电压波形的边沿变得很陡。 施密特电路的结构和工作原理 我们先从模电的角度来分析此电路的工作原理。 此电路是通过公共发射极电阻耦合的两级正反馈放大器。 v1输入为低电平

fcxgfdjy的博客 1万+

Windbg断点调试.net程序

程序员都知道,在生产环境中,如果没有系统日志,对问题的分析将非常的困难。即使有日志,有时候也会因为日志记录的不全面,而导致问题不能分析清楚。其实,Windbg里面有Live Debug功能,正好可以借鉴应用。 本文介绍使用Windbg在.net程序中设置断点调试的方法。Windbg在Native Code里面下断点是比较方便的,bp加上一个内存地址就可以做到。下面进入正题...

weixin_30654583的博客 296

使用DDR4控制器实现多通道数据读写

这种高带宽对于大量的数据的应用非常重要。对于Controller/PHY Mode 的选择,我们使用Controller + PHY模式,Xilinx UltraScale/UltraScale+等高端FPGA通常包含硬核PHY,选择Controller + PHY模式可直接利用硬核,降低功耗和延迟,并且此模式的开发更易上手。在Basic页面下,首先勾选上AXI4 Interface选项,我们选用的内存接口速度为2133MHz的ddr4芯片,由于双沿触发,频率为1066MHz,那么周期应为938ps。

weixin_38828382的博客 1364

windows用户态调试

windows用户态调试 打开windows的记事本小程序,windbg attach到这个进程,发现notepad不能动了,这是用户态调试的特点: 今天调试模式,应用程序所有线程都处于freeze状态。 观察一下线程状态: 会看到有6个线程。 0:006> ~* 0 Id: 1de4.1f6c Suspend: 1 Teb: 000007ff`fffdc000 Unfrozen Start: notepad!WinMainCRTStartup (00000000`ff683ac...

hb_zxl的专栏 1449

Mask2former自定义数据集实现滑坡语义分割

先介绍一下这次用的数据集:Bijie Landslide Dataset,是武汉大学在Triplesat卫星上裁剪的数据集,包含770张滑坡(image、mask、dem、边界坐标txt)和2003张非滑坡数据(dem、image)。直接在ade20k修改,按照路径和设置将预处理数据放好,该步骤很快就能结束,打开的图片是全黑的,因为0和1在图片上肉眼较难区分。bijie的数据集可以直接使用,接下去的步骤是1、预处理数据。配置文件修改训练类别和训练类型,由于我们分割的是滑坡,所以进行语义分割。

weixin_57812838的博客 2216

COMSOL后处理与可视化

公众号→【COMSOL仿真交流】←更多精彩内容 后处理是采用COMSOL建模的一个重要环节。通过软件自带的可视化工具,可以快速方便的展现仿真结果。 COMSOL为使用者提供了多种后处理工具,包括表面图、切面图、等值线图、流线图、线图、体图等等。可按功能分为数据集(储存数据的地方)、派生值和表单(对数据进行进一步的操作)、绘图组合绘图类型(显示数据结果的地方)、导出数据与生成报告。   如果想用origin、matlab对模拟数据做进一步的处理,导出excel格式的模拟结果,这可以通过“导出

midiio的博客 4027

Windbg+sos调试.net笔记

32位进程需要32位SOS。 32位SOS仅适用于32位WinDbg。 加载扩展 要加载扩展,有2个命令。一个是.loadby,另一个是.load。对于.loadby,请使用相对路径;对于.load,请使用完整路径。 对于.loadby,有5个选项: .loadby sos mscorsvr .loadby sos mscorwks .loadby sos clr .loadby sos coreclr .loadby sos 其中mscorsvr确实很旧(.NET CLR 1,服务器版本),mscor

yusakul的博客 925

基于深度学习的中文方言识别模型训练实战

本文介绍了一个基于深度学习的中文方言识别模型开发流程。项目采用PyTorch框架和Chinese-BERT-wwm预训练模型,构建了包含BERT基础层、方言特征增强层和注意力机制的多方言分类系统。文中详细阐述了数据收集策略,包括从社交媒体、语料库和众包平台获取多源数据,并实现了文本清洗、同义词替换和回译等数据增强技术。模型架构部分展示了如何在BERT基础上添加方言特征提取层和多头注意力机制来提升识别性能。该方案支持普通话、粤语等6种主要方言的识别,目标准确率达90%以上,响应时间小于100ms,适用于智能客

电脑管家 1730

[转]windows程序员进阶系列:《软件调试》之O--- WinDbg使用介

原帖地址:http://blog.csdn.net/ithzhang/article/details/8630429                         windows程序员进阶系列:《软件调试》之O--- WinDbg使用介绍      拥有一个顺手的武器是每一个武林高手梦寐以求的。对于windows程序员来说,WinDbg调试器就是我们的武器。熟练使用调试器能大大提高我们的

comhaqs的专栏 1497

《用 Python 爬取统计局年鉴:从 API 抓取到结构化分析,实战全流程详解》

在数据分析、经济研究、商业决策中,国家统计局发布的年鉴数据具有权威性和参考价值。本篇博客将带你从零实践:了解目标、设计爬虫、抓取年度数据、解析 JSON、保存结构化数据、并用 Pandas 简要分析可视化,且使用现代 Python 最佳实践(如httpx、pydantic、typer 等),提升代码质量与效率。如何抓取国家统计局年鉴数据;利用 API 方式高效获取;使用现代异步 Python 提升性能;将数据结构化输出;结合 CLI、类型校验和简单分析技术提升实用性。

2201_76125261的博客 1085

使用C++实现DNS欺骗攻击

其中,domain_name表示需要欺骗的域名,dns_server_ip表示实际DNS服务器的IP地址,fake_ip表示需要重定向到的IP地址。在DNS查询过程中,当客户端向DNS服务器请求解析某个域名时,DNS服务器会返回该域名对应的IP地址。DNS劫持是一种常见的网络攻击方式,通过篡改DNS响应数据,使得用户访问的网站被重定向到攻击者指定的恶意站点。我们使用C++编写一个简单的DNS欺骗程序,使得当客户端请求解析特定的域名时,我们可以将其重定向到指定的IP地址。使用C++实现DNS欺骗攻击。

TechGlide的博客 411
上一篇: Visual Studio中的Class Designer设计工具需要您的建议!
下一篇: 使用WinDbg对Windows Live Toolbar死锁的简单分析
mengtech
博客等级 码龄19年 44粉丝 3472原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值