首页
社区
课程
招聘
[讨论]ESP 和 EBP 的关系(以及对at&t和intel 的asm讨论)
2008-11-22 23:57 12726

[讨论]ESP 和 EBP 的关系(以及对at&t和intel 的asm讨论)

2008-11-22 23:57
12726
在OD的堆栈区里
可以将Address显示的方式改成Relative to ESP或
Relative to EBP

请问一下OD为什么要这样设计?
什么时候会想知道堆栈address relative to EBP?

===================================================
此帖本来由 magicfx提问,目前发展成对汇编语言深度讨论的帖子......
欢迎广大的专业人士及爱好者继续讨论.....
仔细读每一个跟帖都会有很大收获。
===================================================

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞7
打赏
分享
最新回复 (28)
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-22 23:59
2
0
函数内都是以 ebp 作参考的呀。

本来,ebp 就是 stack frame base pointer
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 00:50
3
0
INTEL推荐使用EBP作为栈帧的访问基址寄存器。

实际中大多数编译器生成的标准代码也是这么做的。

但是对于某些情况,比如一个数学算法,如果开了编译器的高级优化,它会把EBP也分配给局部变量或临时变量,而只使用ESP访问栈上的局部变量和函数参数。

在特定情况下,仅仅多一个可分配寄存器却可能意味着性能成倍地提高。
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 00:55
4
0
>> 在特定情况下,仅仅多一个可分配寄存器却可能意味着性能成倍地提高。

这句话说得太夸张了吧。

x64 处理器多了8 个 GPRs 之多,性能是否提升了一倍,都很难说
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 01:21
5
0
编译器的寄存器分配策略,对编译代码的质量有着至关重要的影响。

有时候恰好由于少一个寄存器,编译器不得不使用栈帧上的内存,对于某些数学算法,局部变量和临时结果的访问非常频繁。有时仅仅因为一个寄存器不够,代码的效率会成倍地打折。

要知道内存的访问速度和寄存器的访问速度相差至少一个数量级!哪怕多了几条访存指令,程序的效率降低就非常明显。有些直接使用内存操作数的指令,比如add [esp+0x10],eax,实际上要两次总线操作的周期才能完成。类似的指令还有比如inc [esp+8]等等。

作为个人桌面应用,这点性能的差别是完全可以不计的。但对用作科学计算的计算机程序,好的编译器比差的编译器就不止是成倍地提高效率了。

我曾经读过一本书,讲图像处理的算法,作者自己编写一个滤镜算法的程序,执行时间是200ms,经过不断地优化,最后提高到46ms,最后使用intel的编译工具,使用intel的各种多媒体指令集,结果该算法最终的平均执行时间仅为6ms!

一个寄存器看似事小,实则影响非同小可。如果遇到了类似于访存的“颠簸”现象,那么这一个寄存器的代价可能是几个数量级的性能损失了!!!

包括Linus内核在内的GNU世界的程序员,全部倾向于使用AT&T汇编,而不愿意使用INTEL汇编语法。

原因就在于AT&T汇编原本不是为了程序员用来写程序而发明的,AT&T汇编格式本身就是编译器前端生成的、供后端汇编器使用的“中间代码”,像Linux内核那样,在C中嵌入汇编,甚至可以对编译器的寄存器分配策略进行控制,而如果嵌入INTEL格式,则是完全不同的概念了。

虽然我还没有能力使用AT&T汇编格式来写程序,不过目前读懂不太晦涩的代码还是没问题的。见识过了AT&T汇编,才知道GCC有多么强大。。。
雪    币: 231
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qdk 2008-11-23 01:31
6
0
有寄存器重命名+乱序执行。
可用的寄存器就不像想像中的少了。

再加上其他的技术,寄存器不足的缺点就不容易体现出来了。

特定情况当然很难说的
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
北极狐狸 7 2008-11-23 02:14
7
0
说实话从看at&t 和intel的汇编代码的时候没有发现谁更有优势,汇编的别名是组合。汇编代码下面实际就是机器码。这是区别于其他程序语言的原因。谁更有优势可能是其编译器的同,其优化组合的程度不同。没有看过GCC源码。天生对大程序的代码有种恐惧.有时间把GCC的汇编编译部分的源代码拿出来看看。可怜m$却没有给机会我们看masm的源码......

如果有人有兴趣倒是可以拿masm来练练内功。
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 02:52
8
0
狐狸你是没看过AT&T的汇编。看过之后就知道它是什么了。

如果是纯汇编的还好看,如果是C中内嵌的AT&T,就不同了,因为它不与机器码完全对应!!!

这是用AT&T写的纯汇编的程序,会看INTEL汇编的,只要记住它的源与目的操作数相反就能看懂。

_pg_dir:
  17startup_32:
  18        cld
  19        movl $0x10,%eax
  20        mov %ax,%ds
  21        mov %ax,%es
  22        mov %ax,%fs
  23        mov %ax,%gs
  24        lss _stack_start,%esp
  25        call setup_idt
  26        call setup_gdt
  27        movl $0x10,%eax         # reload all the segment registers
  28        mov %ax,%ds             # after changing gdt. CS was already
  。。。

但你要是看下面的C中内嵌的汇编,看起来只有一条call指令,而实际上4个参数需要组合,所以编译器生成的绝不是一条指令!

#define __get_user_x(size, ret, x, ptr)               \
        asm volatile("call __get_user_" #size         \
                     : "=a" (ret),"=d" (x)            \
                     : "0" (ptr))                     \

我现在拜读Linux内核就因为这些AT&T汇编太难理解而几乎读不下去,郁闷。。。
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 03:06
9
0
寄存器重命名和寄存器窗口等技术,很多高端处理器都是有的。

但INTEL桌面计算机处理器是没戏了。

但面向高端的INTEL的ITANIUM处理器,确实让人很叹服它的设计,打破INTEL给人的复杂臃肿低效的印象,它具有足够多的寄存器,并且支持寄存器重命名(旋转寄存器),而且最妙的是,它具有一个“寄存器栈”,在需要的栈空间不多时,可以完全脱离内存栈,其性能提升可想而知(当然寄存器栈的设计还有很多其它的用处)。

我特别注意到它的指令,是叫做“指令束”的,具有三条并行的指令流!

试想如果X86构架没有“向前兼容”的包袱,我想当今桌面计算机的性能也绝对不是现在这个水平。。。
雪    币: 231
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qdk 2008-11-23 03:59
10
0
#define __get_user_x(size, ret, x, ptr)               \
        asm volatile("call __get_user_" #size         \    //宏展开,
                                                                                //__get_user_x,x=1,2,4
                     : "=a" (ret),"=d" (x)            \              //输出约束,ret由eax输出
                                             //x由edx输出
                     : "0" (ptr))                                        //输入约束,ptr约束和ret约束一样

x86应该也有寄存器重命名啊,龙芯都有了
雪    币: 122
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Campaign 2008-11-23 04:21
11
0
书呆彭的回答很专业,不错,谢谢!
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
北极狐狸 7 2008-11-23 18:07
12
0
呵呵,我已经看不懂了....
雪    币: 251
活跃值: (25)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
newjueqi 7 2008-11-23 18:45
13
0
看了讨论,获益良多
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 21:14
14
0
你回答得很不专业。若想说明你的观点,应该摆数据说事实。

1、拿一个程序在32位平台和64位平台的表现来说明。在 x64 平台下,要知道,x64 的 ABI 规定,有 7 个通用寄存器可以用了作为函数的参数传递,比之 x86 一般使用栈来传递参数要优越多了吧。况且多出了几个寄存器可用。那么是否,一个程序采用64位编程真的比32位要快上一倍或以上吗? 反正,我好像是没看到64位比32位快上一个数量级的结论。

2、你说的关于图象方面的例子,不知道你是看本书籍,我曾经看过一本很经典的图像编程的书籍,忘了叫什么名,很厚有1000多页,里面有很好的例子,有专门讲优化的部分。是 id 公司的人写的。 但是所说的性能大幅提升,主要是算法方面的改进,然而才到汇编级的改进。汇编级的改进,性能提升幅度并不是很大。 至于说到使用SSEx 系列指令,令当别论,且那不是通用编程。

3、你拿 unix / linux 世界的汇编说事,那更是说明你不了解。unix/linux 世界采用 gnu 语法,那是由于 AT&T 与 unix 与生俱来的惯例。并不是因为采用 gnu 语法会比 intel 语法性能上要优越。
   反而是恰恰相反:gnu 语法是照顾了通用性而牺性性能,linux 能运行在多种平台上,汇编语言这种中间代码就是要为了能运行在多种平台上而产生。
   采用 gnu 的语法不可能会比 intel 语法要快,难道 intel 编译器产生的代码会比 gcc 产生的代码要慢??
  事实上,gcc 的嵌入语法,是给了程序员自由度挺少,由编译器自己调度分配策略。这是一种通用性方面的考虑,并不是性能方面的考虑。
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 21:17
15
0
gcc 强大是因为它的通用性,不是它的性能
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 21:20
16
0
寄存器栈是一个被人唾弃的结构,x86 的 浮点运算部分就是采用寄存器栈的。那是高端机器不会采用的
雪    币: 2368
活跃值: (81)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
北极狐狸 7 2008-11-23 21:25
17
0
越辨越明。
对于技术的的辩论或互相纠正其表达上的错误。
只要不要过于激进都是值得称道的...
mik期待你的大作........嘿嘿.
雪    币: 231
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
qdk 2008-11-23 21:27
18
0
很专业,顶一下。

看来我还得加把劲。
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 21:38
19
0
此寄存器栈非彼寄存器栈,不了解请不要乱说。

况且,80x87的栈式寄存器结构并不错,错在INTEL想在80x86的基础上不断地增加新功能,新指令集,导致它的指令过于复杂。

80x37的寄存器组不仅可以以栈式访问,也可以访问指定的寄存器,LOAD/STORE结构是非常经典的处理器构建,但是它构建于80x86之上,结果使它成为了失败的设计。

安腾处理器的设计在高端处理器中都可以说非常优雅了。它超越了一般高端机的寄存器重命名或寄存器分组的概念,在此基础上实现了一个寄存器的栈,对于系统软件来说,它可以不使用存储器栈而完成系统服务,仅在任务切换时,需要将寄存器栈与存储器栈进行同步。这种设计是非常好的设计,而不是失败的设计。

像MIPS之类的机器,由于它的寄存器不够多,只有32个,因此无法支持一个实用的栈,但对于安腾处理器,有多达127(128)个能用寄存器(外加128个浮点寄存器),它可以支持的寄存器栈的最大深度是96,足够一般的嵌套过程调用使用(栈主要用来保存层次调用路径,参数是通过能用寄存器传递的)
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 21:44
20
0
安腾处理器,我确实不了解,这方面我保持沉默
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-23 22:46
21
0
关于GNU,我了解不多,所以说得不对,多谢指教。

对于AT&T汇编,我只是能读懂而已,没有更多的了解,我只知道在GCC前端生成的汇编代码就是这个样子的。我知道我不懂,说得不对,所以不怕别人指出来。

最开始我说的一个寄存器可能造成性能相差很多,本来指得是很特殊的情况,并不说一般情况。

我只是为了说明寄存器对程序效率影响很大,可能说得大了,以为我在钻牛角。

本来我想找例子来说明一下我的意思,免得大家误会,找了半天,没找到。

我记得以前上课时,老师讲过一个“颠簸”,或“摇摆”的例子,不幸的是上课时的PPT找不到了,我实在想不起这个例子的具体情况了,所以在谷歌上找了一个类似的例子(唐志敏的一篇报告中找到的):
for ( k=0;k<10;k++)
{
  for (j=0;j<1000;j++)
    执行运算过程B;
}
运算过程B的大小:如果B过大,CPU内部寄存器的压力就会很大,如果寄存器的数量不足以保存B中出现的所有数据,可能会出现颠簸的现象,刚刚从寄存器中换出的数据也许就是下一个需要的数据,还得重新读入寄存器,这对效率显然是有影响的。

所以CPU用一层”透明“的CACHE,而以此来大幅提高性能,虽然CACHE也可能发生颠簸,但CACHE总归比寄存器的容量大得多,颠簸的机率也低得多。

另外,对于程序执行的效率,其实对于面向个人用户的软件开发者,基本上可以不予考虑,对个人而言,一段程序执行1ms和100ms的差别并不多,我点一下鼠标后能立即看到结果,就行了。

真正对效率斤斤计较的场合是高端应用。我们实验室6台Spark的工作站,全天24小时CPU几乎总是100,要测试一个东西,写好测试脚本,提交后至少也得十多分钟。很多师兄都是晚上下班时提交,第二天上班时看看好没好。

谢谢mik的关注。你指出的错误我会去好好查一查的。
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-23 23:02
22
0
前面提到,性能大幅度提升,主要是要靠算法及数据结构的改进。单单靠多一个寄存器提升的性能是有限的。当然象安腾这样多出寄存器当一个数量级以上,另当别论
既使将vc2008最高级别的优化打开,也不会考虑从ebp下手
你提到的颠玻情况,应该好好考虑数据结构方面,充分利用cpu 内部的cache
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-24 00:07
23
0
我确实碰到过一个数学库中的函数,使用除ESP外所有寄存器进行运算的。而且这个库应该是VC6编译的。

我刚才自己特意“编”了一段测试代码,发现它确实把EBP用作寄存器变量了。请看下面的程序。

27:
28:   int main(void)
29:   {
00401170   push        ebx
00401171   push        ebp
00401172   push        esi
00401173   push        edi
00401174   call        StartMeassure (00401150)
30:       StartMeassure();
31:
32:
33:
34:       register    int a,b,c,d,e,f,g,i;
35:
36:       a = 0;
00401179   xor         eax,eax
37:       b = 1;
0040117B   mov         ecx,1
38:       c = 2;
00401180   mov         edx,2
39:       d = 3;
00401185   mov         edi,3
40:       e = 4;
0040118A   mov         esi,4
41:       f = 5;
0040118F   mov         ebx,5
00401194   mov         ebp,5F5E100h
42:       //g = 6;
43:
44:       for ( i = 0; i < 100000000; ++i )
45:       {
46:           a = 2*a + 1;
00401199   lea         eax,[eax+eax+1]
47:           b += a;
0040119D   add         ecx,eax
48:           c += b;
0040119F   add         edx,ecx
49:           d += c;
004011A1   add         edi,edx
50:           e += d;
004011A3   add         esi,edi
51:           f += e;
004011A5   add         ebx,esi
004011A7   dec         ebp
004011A8   jne         main+29h (00401199)
52:           //g+= f;
53:       }
54:
55:       ::EndMeassure();
004011AA   call        EndMeassure (00401160)
56:
57:       result = e;
004011AF   mov         dword ptr [result (0042a2f8)],esi
58:
59:       PrintTime();
004011B5   call        PrintTime (00401080)
004011BA   pop         edi
004011BB   pop         esi
004011BC   pop         ebp
60:       return  0;
004011BD   xor         eax,eax
004011BF   pop         ebx
61:   }
雪    币: 2108
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
书呆彭 6 2008-11-24 00:08
24
0
对了,我用的是VC6,编译优化全开。
雪    币: 723
活跃值: (81)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
mik 4 2008-11-24 09:41
25
0
这种情形下,那当然多一个寄存器快了
游客
登录 | 注册 方可回帖
返回