有过程Sub1、Sub2、Sub3、.....,均要调用另外一个S_Sub。
我的问题是如何在S_Sub中确定是那会函数调用了它?当然可以通过在S_Sub中确定返回地址的方法判断调用宿主,也可以用形参实参传递标志的方法或设公用标志变量,但我不想用这些方法。
各位还有方法吗?

解决方案 »

  1.   

    procedure S_Sub(Sub1: Boolean = False; Sub2: Boolean = False; Sub3: Boolean = False);
    begin
      if Sub1 then ShowMessage('被Sub1调用!')
      ...
    end;procedure Sub1;
    begin
      S_Sub(True)
    end;...
      

  2.   

    分析callstack
    procedure S_Sub();
    begin
      try
        raise Exception.Create('故意的异常');
      except
        //这这里查询callstack delphi并未提供看堆栈的功能,但是你到网上找找,有一些牛人写的工具是很好用的
      end;
    end;
      

  3.   

    比如madExcept或
    这个
    http://www.dimusware.com/products/excmagic/index.html
      

  4.   

    如果S_Sub中没用到edi,还可以这样:procedure S_Sub;
    var
      f: word;
    begin
      asm
        mov f, di
      end;  if f=1 then ShowMessage('被Sub1调用!')
    end;procedure Sub1;
    begin
      asm
        push di
        mov di,1
        call S_Sub
        pop di
      end;
    end;
      

  5.   

    to 9楼,
        我想找个更好的方法,不用形实参和标志变量。这样不需在接口或程序中做任何设置。这一点对DLL中的S_Sub很有用。
        实际上利用堆栈是一个好方法,在所有的调试工具中都是通过这种方法实现调用关系的显示。只是堆栈返回地址不能表示是那个主过程Sub?调用了S_Sub。
        我的问题真的无解吗?期待ing......
      

  6.   


    //调用者的方法地址
      mov    eax,   [ebp+$04] 依据调用者的方法地址,查询对应的名称等信息。所以要求程序需要带有调试信息、开启栈帧(Stack frame)。
    第三方库如MadExcept、JCLDebug,应该都支持以上需求。
      

  7.   

    说得更严谨点,在开启栈帧的前提下,[ebp+$04]实际上是调用完S_Sub之后,返回到哪儿去继续执行的地址(当然还在调用者的代码范围之内)。
      

  8.   

    to 21楼,对于形参个数不同的Sub_S也是这个吗?好象返回地址在栈中的位置与Sub_S的参数有关,而且不同调用约定的DLL也是不一样的,有入栈先后顺序之分。
      

  9.   

    no!
    调用者(比如Sub1)内部是这样的:
    ...//相关参数设置
    call S_Sub//调用S_Sub
    ...//调用完之后的代码其中call指令就是把“调用完之后的代码”的首地址入栈,再jmp到S_Sub。即相当于:
    push xxxx
    jmp S_Sub然后S_Sub里头(开启栈帧的前提下):
    push ebp
    mov ebp,esp
    ...//其他代码
    mov eax,[ebp+4]//此时如果有这句,就取得本函数结束后返回到哪儿继续执行的地址
    ...//其他代码
    pop ebp//恢复ebp
    ret//相当于pop ip,就是把xxxx弄出栈并赋给ip,即跳到xxxx执行
      

  10.   

    纠正:
    ret相当于pop eip
      

  11.   

    崩溃的时候在弹出的对话框按相应按钮进入调试,按Alt+7键查看Call Stack里面从上到下列出的对应从里层到外层的函数调用历史。双击某一行可将光标定位到此次调用的源代码或汇编指令处。
      

  12.   

    楼主可以参考这里的详细阐述:
    http://liyiwen.iteye.com/blog/345525
      

  13.   

    http://liyiwen.iteye.com/blog/345525
      

  14.   

    狗尾续貂一下。(^_^)
    #include <stdio.h>
    void whocallme();
    void fun1() {
        whocallme();
    }
    void fun2() {
        whocallme();
    }
    void fun3() {
        fun1();
    }
    void whocallme() {
        int *_esp,i;
        __asm {
            mov eax,esp
            mov _esp,eax
        }
        printf("\nfun1,fun2,fun3,_esp=%08x,%08x,%08x,%08x\n",(int)fun1,(int)fun2,(int)fun3,(int)_esp);
        for (i=0;i<100;i++) if (_esp[i]==(int)_esp) break;
        if (i<100) {
            printf("ret addr=%08x\n",_esp[i+2]);
            if ((int)fun1<=_esp[i+2] && _esp[i+2]<(int)fun2) printf("fun1 callme\n");
            if ((int)fun2<=_esp[i+2] && _esp[i+2]<(int)fun3) printf("fun2 callme\n");
        }
    }
    void main() {
        fun2();
        fun1();
    }
    //C:\tmp\tmp\Debug>tmp
    //
    //fun1,fun2,fun3,_esp=00401000,00401020,00401040,0012fe84
    //ret addr=0040102e
    //fun2 callme
    //
    //fun1,fun2,fun3,_esp=00401000,00401020,00401040,0012fe84
    //ret addr=0040100e
    //fun1 callme
    //
    //C:\tmp\tmp\Debug>cd ..\release
    //
    //C:\tmp\tmp\Release>tmp
    //
    //fun1,fun2,fun3,_esp=00401000,0040100a,00401014,0012ff5c
    //ret addr=00401012
    //fun2 callme
    //
    //fun1,fun2,fun3,_esp=00401000,0040100a,00401014,0012ff5c
    //ret addr=00401008
    //fun1 callme
    //
      

  15.   

    我否定楼上所有的做法,我觉得越简单越好,直接使用一个静态变量,在调用S_Sub之前设置静态变量的值,在S_Sub中判断静态变量的值就知道了。如果不需要并发那这个方法是最佳的。
      

  16.   

    to 50楼,对于模块设计来说,当然越简单也就越可靠越好。可是在很多情况下简单并不能解决问题。比如插件模块用你说的方法就不行。因为Sub1、Sub2不是你自己设计的。这就是为什么在主楼说明中将这种方法排除在外的原因。
      

  17.   

    delphi的事件都有一个sender参数,就是事件触发时的被操作者(按钮、列表。)
    可以参考借鉴一下,只是接口都需要修改了
      

  18.   

    to 54楼Sender是控件的触发事件产生的,其中确实有主调函数的相关信息。可是Sub1调用Sub_S是最原始的过程调用,已知的可利用信息只有在堆栈中找。
      

  19.   

    43楼,这段代码我在VC++ 2008 下执行,无论开不开Debug都得不到预期的结果。主要是以下两句        if ((int)fun1<=_esp[i+2] && _esp[i+2]<(int)fun2) printf("fun1 callme\n");
            if ((int)fun2<=_esp[i+2] && _esp[i+2]<(int)fun3) printf("fun2 callme\n");这两句if的条件判断始终为false,所以后面的printf语句始终不能执行到。
    我想43楼在看完前面提到的那篇blog没有很好的理解其中的意思,
    因为
    (int)fun1
    指的是代码段地址,

    _esp[i+2]
    中存储的是栈地址。
    同时,在那篇博客中的代码已经明确表示:只能通过返回地址获得主调函数的栈地址,但是不加debug信息,很难通过栈数据反过来获得代码段数据,而且你也无法仅仅只通过地址而获得函数的名称,因为名称信息在编译的时候已经被翻译成段地址了。我不知道在开debug时,运行时信息是怎么样的,但一定会丰富,不过可能不同编译器编译出的RTL会不一样,也许没有一个通用的解决方案。总的来说,我觉得这个问题其实是由于楼主程序结构设计不合理造成的。如果你一定要在被调函数中知道主调函数是谁,其实就是需要一个外部状态来决定函数的执行行为。
      

  20.   

    为什么都是些很奇怪的方法呢?程序员的任务不是把问题复杂化而是解决问题,忽然看到居然某个大神用汇编,已经被惊倒了 ---- 会汇编很了不起,今天给你换个其他平台玩玩:修改程序已经是比较糟糕的主意了,不就要看这个函数被那些函数调用了,用得着这么麻烦:是不是这个效果?我想知道_getdrive被谁调用了。使用现成工具IDA就可以了
      

  21.   

    补充一下,如果需要生成如上符号表请使用Debug模式,针对Release (商业代码)IDA也可以生成调用、被调用关系。但没有符号表;IDA不仅仅可以帮助分析C/C++,其他一些语言也可以,包括程序流程图绘制 ---- 当然是针对目标代码;
      

  22.   

    to tomsoft
       后期的分析可以借助很多工具来达到这样的效果,但是设计者如何将这些工具动态连接到自己的程序中?好象不可能,也不现实。
       在自己的DLL中能用你的方法确定调用者?有现成的代码吗?期待中......
      

  23.   

    IDA这样的工具不需要一定在后期分析时采用,工具在于灵活应用;IDA可以对已经运行的DLL或其他常驻系统的程序进行分析;IDA最大的有别于程序设定断定的好处是不需要对程序进行任何修改,也不需要连接什么特定的库 ---- 个人观点是尽量少对程序进行修改:现在很多程序都会使用第三方库;尤其是诸如int 3这样和系统相关的更要谨慎使用
      

  24.   

    能用IDA的人不多,特别是你不能因为你的DLL使用了IDA而要求用户用IDA
      

  25.   

    经大家参与讨论。本贴所列问题的解决之路只能是通过栈分析方法。当然也可采用传参和设置标识(前提是Sub1、sub2....和Subs必需采用同样的设计规则,统一规划方可)。
    谢谢各位的帮助!