Windbg之探索托管exe加载及函数JIT的过程

类似的文章网上已经汗牛充栋了,但作为个人的一个学习经历,我觉得还是有必要记录一下为好。

使用到的工具: Peview   windbg

目标程序 ILText.exe

目标程序源码:

using System;
using System.Collections.Generic;
using System.Text;

namespace ILTest
{
    public class HelloWorld
    {
        private static long n1;
        private static long n2 = DateTime.Now.Ticks;
        private static long n3 = DateTime.Now.Ticks;
        private readonly static string ConstValue = "testStatic";

       static HelloWorld()
        {
            n1 = DateTime.Now.Ticks;
        }

        private static void staticMethod(int n)
        {
            Console.WriteLine(n);
        }

        protected string Method1()
        {
            return null;
        }

        private string Method2()
        {
            int n;
            string str;

            str = DateTime.Now.ToString();
            n = DateTime.Now.Year;

            return null;
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Hello, world.");

            HelloWorld obj = new HelloWorld();
            obj.Method2();

            HelloWorld.staticMethod(10);

            Console.ReadLine();
        }
    }
}

 

首先我们使用peview打开一个exe

 

 

image

我们可以看到该程序的入口点的偏移地址是299e.

然后我们使用windbg,选打开可执行文件,选上我们的目标exe文件。

敲入以下几个命令:

~0s

.load sos

ld iltest

ld mscoree

.load/ld mscorwks

ld kernel32

lm

image

可以看到该exe 的首地址是00400000,加上偏移地址等于是0040299e,ok,我们查看下该地址的汇编码:

u 0040299e

image

可以看到里边是一个简单的跳转指令,跳转到00402000, 用dds (Display Words and Symbols)查看一下该地址存了什么:

dds 00402000

image

至此,已经已经非常清楚了。任何托管的EXE程序中的入口点都是一条JMP指令直接跳转到MSCOREE.DLL的_CorExeMain函数执行。

 

接下来,我们继续探索JIT的过程。

Don Box 在《.NET本质论 第1卷:公共语言运行库》的第六章中提到方法表在JIT 之前,保存的都是 call mscorwks.dll!PreStubWorker 调用,直到第一次使用时,才会对目标 IL 代码进行 JIT 编译,并调用之。因此我们第一步可以在此函数上设置断点(bu mscorwks!PreStubWorker),看看系统是如何调用此函数的。

bu mscorwks!PreStubWorker

image

然后输入g命令,接着用!clrstack命令查看当前托管代码的堆栈,直到运行到Method2方法被调用。

或者我们可以采用另外一种方法,直接给托管方法Method2设断点。image

然后我们可以查看HellowWorld的实例对应的methodtable,来查看method2在JIT之前存储的是什么。

!dso

image

!do 013a3834

image

!dumpmt -md 00a63098

image 

可以看出,method2还没有被JIT过,我们看看此时它对应出的执行代码是什么。注意标记处的指令,第一条指令的地址来源于上图。

image

至此,可以看出method2还没有被JIT的情况下,会被引导至mscorwks,然后执行JIT.

我们继续输入g,让程序继续运行。

image

然后此时再查看methodtable

!dumpmt -md 00a63098

image

可以看出来method2已经被JIT了,另外跟上一次执行同样的命令比较,发现此次method2对应的entry已经变成了00da0180.

用dumpmd看看该方法

image

也一样可以看出方法已经被JIT,m_CodeOrIL此时存放的就是该方法的汇编码。我们看下对应的汇编代码是什么吧:

!u 00da0180  

image

至此,我们应该已经看出来了,method2在JIT前后的入口点地址发生了变化,这就符合我们在很多文章所看到的,一个方法是以IL形式存放到托管模块中的,当第一次访问后,该方法会被JIT,生成相应的汇编码,之后的再次调用的话,就会直接方法JIT过的汇编地址。

那么,该地址既然有变化,是否意味着那么多调用 该方法的地方,相应的地址(method2的entry)也要变化呢?

我们可以看看本段代码中的Main方法对应的汇编码:

从上边的执行结果中我们可以查到main方法对应的入口地址是00da0100。

!u 00da0100  

image

可以看出,method2对应的入口地址应该存放在0A630DCh中,我们看看该内存区域的东东:

image

咦,是00da0180,这个不正是我们上边已经查到的method2对应的entry么? 它是通过运行时计算得来的,因此,我们可以猜测,在JIT之前,0A630DCh存放的一定是00a6c01d。因此,也就不存在说调用方改变相应地址这回事了。

posted @ 2009-07-26 22:36  彷徨......  阅读(620)  评论(0编辑  收藏  举报