打印

救火车的火灾预警----小心keil的堆栈隐患。

[复制链接]
楼主: 救火车
手机看帖
扫描二维码
随时随地手机跟帖
41
口才也不错

使用特权

评论回复
42
51avr| | 2010-7-12 11:30 | 只看该作者
1。 统计
自己定义数组,初始化为0,并将SP指向该数组。在实际工作环境中运行一天后,通过串口将该数组的内容全部输出。观察连续非0的数组长度,然后留出20%左右的余量。

2。 分析map文件,确定堆栈大小。注意中断函数是否会在堆栈中保存所有寄存器。

使用特权

评论回复
43
冷漠| | 2010-7-12 11:32 | 只看该作者
比起小丁差得远。需提防:
万一冷漠的讲义讲的内容,与所有高手想的不沾边,但是完整回答了救火车版主的精确计算
?STACK 的问题,攻击冷漠者届时怎么办?

万事给自己留后路。所长当年……

使用特权

评论回复
44
xlsbz| | 2010-7-12 17:14 | 只看该作者
43# 冷漠

冷漠是80后么》、?? 不可能吧

使用特权

评论回复
45
highgear| | 2010-7-12 22:41 | 只看该作者
性格可以张扬,但技术不能张扬。技术必须严谨周密,必须准确, 不能想当然,毫无技术根据的随便张扬,这会误导很多初学者。

40搂的论断还是不够严谨。递归是函数调用自身的过程,一定是可重入的, 但可重入的函数却不一定是递归。指定(限制)递归的次数,可能会导致递归无法达到终止条件而失败。递归和可重入不是一个概念,不可混淆。

使用特权

评论回复
46
救火车|  楼主 | 2010-7-13 09:16 | 只看该作者
本帖最后由 救火车 于 2010-7-13 10:29 编辑

递归是极特殊的情况。这里暂不讨论这种情况。在我们这里,单片机编程是禁用递归方法的。就是因为栈的深度不确定,容易溢出。

现在我只能用手工计算每个函数占用栈深度的方法计算。
一般函数占用两个字节堆栈空间,中断函数占用三个字节空间。
需要用栈传送参数的函数(例如指针参数)还会多占用栈空间。
自认为这种方法很蠢。

实在不行就只能按42楼的方法。把程序开一天,看内存情况了。

使用特权

评论回复
47
highgear| | 2010-7-13 21:15 | 只看该作者
让编译器精确的计算栈深, 难度非常大, 因为编译器没有智能到通过代码分析可以预知运行时的各种可能性。

手工计算虽然可以精确的计算栈深, 但容易出错而且繁琐。42搂的方法简便, 但不一定能得到精确的最大栈深。即便知道最大栈深, 留出一定的裕量还是必要的。

使用特权

评论回复
48
冷漠| | 2010-7-14 10:18 | 只看该作者
本帖最后由 冷漠 于 2010-7-14 11:20 编辑
现在我只能用手工计算每个函数占用栈深度的方法计算。
一般函数占用两个字节堆栈空间,中断函数占用三个字节空间。
需要用栈传送参数的函数(例如指针参数)还会多占用栈空间。
自认为这种方法很蠢。


回46楼救火车版主:您说的方法是完全汇编语言的方法,到了C编译器是非常智能化的软件了,离操作系统思想不远:内含堆栈管理,内存管理。——当然对裸奔程序来说这有点吹牛。

内容太多,冷漠懒于打字,先提示一点:一个(没有二级嵌套中断的)一级中断,其堆栈的最大长度(堆栈深度)应该是13字节!

这就是编译器算法总结,做编译器的人都是顶级聪明的人,他有一套规则来限制堆栈任意使用、增长。

先别抓冷漠“这句话有严重的语病……其它的问题我就不多说了……  ”XXX一贯用来遮掩自己,以贬低他人开始,……
最好先集中问题的讨论,推翻冷漠这个结论。谁若能证明一级堆栈的深度能大于17字节,冷漠拜他为大师。万一不能,就别在这捣乱。

举个例子:
    救火车版主说的函数占用堆栈空间的问题:
1、多个非重入函数的参数传递和自动变量是被编译器分配在全局静态变量空间共享覆盖区、而不是堆栈区的(通过堆栈传递参数是纯粹的汇编方法。)因此,整个程序的所有非重入函数所占用的data(C51为例)字节空间是那个使用参数和自动变量最多的函数占有量,其它函数不过是在共享这个区域而已。共享覆盖区被编译器(C51为例)分配在全局变量区的下面。可以看做是后台程序的堆栈(参数及动态变量保护区)。——可以精确计算了吧。这个规则没有疑问吧?关键:
2、由1、可知,前台中断需要保护的变量,和后台非重入函数所占用的“后台堆栈”,没有联系,它仅需要保护自己ISR所用到的寄存器罢了,具体保护哪些寄存器,当然已经由C编译器完成了,无需用户考虑的事。更关键:
3、前台中断ISR所使用的堆栈深度不大于13字节。这已经可以算是中断堆栈深度精确计算了吧。

为什么不大于13字节?谁能证明一下,举个例子推翻冷漠……最好能指出所举资料、书籍来源出处。

别光知道空喊赛肺活量。“很复杂,编译器绝对做不到那么智能……”C编译器规则是ANSI   C标准化委员会制定的,——他们不是一群饭桶。全世界所有做编译器的一致遵循的(可别说冷漠“不对,XX编译器就和ANSI  C有差别……。贬低他人,抬高自己?),所长硬说C51规则做不到精确定位,有严重的错误,只有自己才是规则,这种人冷漠也没什么办法。
只是:冷漠只信书
万一冷漠证明了,C51编译器有足够的智能精确计算堆栈深度,所长只好拿出空喊的本事来。我们马上就可以见识一下……仅以C51为例,……其它大多数编译器一样。哈哈,那是操作系统教材上讲的,我以为所长还精通OS教本?把CORE .C这种纯软的OS模块说成是独属 Intel 酷睿双核、四核,这种联想智能,不用赛肺活量,都让冷漠不得不佩服。——冷漠马上知道,所长没看过任何操作系统教材,在这充数呢。

所长要评论研究生高峰的错误,先看清自己是谁才好。——恐怕先要修好博士学位才行。



使用特权

评论回复
49
英雄无敌六| | 2010-7-14 13:19 | 只看该作者
#include <reg52.h>

sbit P10 = P1^0;
sbit P11 = P1^1;
sbit P12 = P1^2;
sbit P13 = P1^3;
sbit P14 = P1^4;

unsigned char sp1;
unsigned char sp2;

void a()
{
    P10 = 0;
        sp2 = SP;
}

void b()
{
    P11 = 0;
        a();
}

void c()
{
    P11 = 0;
        b();
}

void d()
{
    P12 = 0;
        c();
}

void e()
{
    P13 = 0;
        d();
}

void f()
{
    P14 = 0;
        e();
}

void g()
{
    P14 = 0;
        f();
}


void h()
{
    P14 = 0;
        g();
}

void i()
{
    P14 = 0;
        h();
}

void j()
{
    P14 = 0;
        i();
}

void k()
{
    P14 = 0;
        j();
}


main()
{
        sp1 = SP;
        k();
    while(1)
        {
        }
}

把优化关掉,sp1=0x09, sp2=0x1f,堆栈大于17个字节,这个算不算堆栈深度?

使用特权

评论回复
50
冷漠| | 2010-7-14 16:37 | 只看该作者
本帖最后由 冷漠 于 2010-7-14 18:25 编辑
内容太多,冷漠懒于打字,先提示一点:一个(没有二级嵌套中断的)一级中断,其堆栈的最大长度(堆栈深度)应该是13字节


先拜49楼为大师。建议仔细读一下救火车版主的主题是什么?

使用特权

评论回复
51
救火车|  楼主 | 2010-7-14 17:14 | 只看该作者
49楼的例子共调用了11层。如果编译器不作优化的话,应占用22字节堆栈。必须设置优化等级是0时才是这样。
我已经实际验证了,正是22字节。

在默认情况下,编译器对程序进行了优化。所以看不到22字节。

另外大家注意两点。
1、我在这里开贴是为了讨论问题。如果大家意见有分歧,还要互相多包涵。
2、救火车和大家都一样,在这个问题上,只是小学生。期待大侠的出现。

使用特权

评论回复
52
冷漠| | 2010-7-14 18:06 | 只看该作者

呵呵,49楼说的是后台堆栈,其堆栈深度最大是256 - 8 字节。

本帖最后由 冷漠 于 2010-7-14 18:40 编辑

1、49楼所举例子是后台函数,是10个后台函数的私有堆栈之和!

2、私有堆栈(每个后台函数的私有STACK,和C编译器中的?STACK是两回事)被编译器分配在RAM低端,从全局静态变量区(包括共享覆盖区)后面开始,即初始SP所指向区域,直到?STACK所指向为结束。

?STACK所指向的是前台堆栈,被编译器默认自动分配所有段(包括所有私有STACK段)的最后面(RAM高端)——栈顶部分!关键的是从这里开始,直到栈顶,才存在溢出危险。——它是一个独立ISR函数(不是多个后台函数)的堆栈。冷漠说的是13 / 17 字节,而且不包括硬件自动压入的2字节PC!——49楼10个后台函数堆栈之和实际全是硬件自动压入的PC之和。——11个函数共22字节!实际不属于函数私有STACK,(除非是汇编程序),C程序中硬件自动压入的PC在私有STACK指向下面就完成了,用户程序根本看不见的。好像称为系统控制栈内容。

3、一高(端)一低(端),一小一大,一前(台)一后(台)。好多概念。

使用特权

评论回复
53
冷漠| | 2010-7-14 19:13 | 只看该作者

问题是不是通过检查 SP_max 值就能求解?

本帖最后由 冷漠 于 2010-7-14 19:30 编辑

正在画C51编译器内存分配规则图,和前台堆栈分配基本规则,——为什么不大于13字节的原理。突然有一个问题不明白,见以下贴图:
KEIL C51模拟调试结果不值得相信?还是救火车版主忽略了没检查sp_max这一项? 0x1F- 09= 22,不用自己算呀;keil  调试器已经清楚记录了程序运行轨道和SP最大值,——如果没有外部输入数据的话。如果用仿真器,更不需要通电检测一天什么的了,——输入外部数据,一次完整运行就知道堆栈最大深度了。是这道理?

使用特权

评论回复
54
戒指320| | 2010-7-14 21:36 | 只看该作者
做个记录

使用特权

评论回复
55
highgear| | 2010-7-14 21:40 | 只看该作者
用脚后跟都能想到, 目前的技术不会有automation. keol 的一些答案, 仔细的看看 keil 的网站,答案都有:

QUESTION
How can I determine the maximum stack size of my program?

ANSWER
There is no automatic way the tools can tell you the maximum stack depth or stack utilization. Because of asynchronous events and interrupts, the automation is too difficult.

However, you can use the µVision Simulator which provides the sp_max value in the register window. When you execute all features of your application, this gives you the maximum sp value that has been reached by your application. Make sure that your assumptions about the available stack are correct. Some 8051 variants have just 128 bytes on-chip RAM and therefore the stack space ends at 0x7F.

You should have more bytes available for executed interrupts. As a guideline, there should be enough space to execute the most complex interrupt of your application.

If you cannot use the µVision Simulator, use the following method, which works in any debugging tool. You may use the Monitor, the ISD51 In-system debugger, or any in-circuit emulator. The results should be similar.

Locate the beginning of the stack. If you use the C51 compiler, look for the ?STACK symbol. that is listed in the linker map file (*.M51 or *.MAP)
Fill the stack area with a known value or string. Something like "STACK---", since it's 8 characters long, displays nicely in a memory dump.
Run your program and make sure you execute every line of code (or as much as makes sense). Make sure you generate interrupts so that they are included in the profile. If your emulator has a code coverage feature, use it to make sure that every path is taken. You can do this with dScope's code coverage feature.
After your program runs a while, stop it and look at the stack area. You should see garbage for the part of the stack that has been used and the "STACK---" strings in the remainder of the stack. Count the number of complete strings, multiply by 8 (since "STACK---" is 8 bytes long), and you have the number of bytes of remaining stack space.

http://www.keil.com/support/docs/192.htm

使用特权

评论回复
56
highgear| | 2010-7-14 21:46 | 只看该作者
运行时如何检查stack溢出

http://www.keil.com/support/docs/2095.htm

使用特权

评论回复
57
highgear| | 2010-7-14 22:00 | 只看该作者
keil 的技术支持很不错, 很多的问题有现成答案。

keil 的论坛:http://www.keil.com/forum/threads.asp?MsgIndex=0
英文好而且对keil 痴迷的可以去看看。

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
xlsbz + 1
58
冷漠| | 2010-7-14 22:19 | 只看该作者
本帖最后由 冷漠 于 2010-7-14 22:26 编辑

唉,所长总是跟在冷漠后面用脚后跟查互联网。到底什么时候能够做一回先发制人?显示一下大师级水平,别总跟菜鸟冷漠学舌。我烦。

等了您一星期:给咱们菜鸟讲讲为什么中断堆栈的深度不会大于13字节?随便你查什么互联网;如若这个问题再跟在冷漠后面“用脚后跟想”,是不是就太没劲啦。毕竟是老前辈了。老毛病总改不了?回答问题就老老实实回答问题,先贬低他人,“用脚后跟都能想得到……不是我总跟在冷漠后面装明白,是因为我没有用脚后跟去想……”;想得到你怎么一星期都没动静?
再让你一把:给咱们菜鸟讲讲课,为什么中断堆栈的深度不会大于13字节?(以C51为例)这回不知道又会有什么理由啦。“没用指甲盖去想?……”
keil 网站没讲到的你就想不到?这回千万别让冷漠先讲清楚,在跟在冷漠后面装明白了。也不能总让冷漠先发制人吧。

说不出来就别捣乱,你回答你的奇思妙想,别总追着冷漠学。冷漠对你没兴趣。

使用特权

评论回复
59
xuyiyi| | 2010-7-15 07:12 | 只看该作者
以前用keil C51时一直用手工计算推栈深度,再放两字节余量,运行几小时,观察最后两字节数据是否修改,用以验证手工计算推栈深度的正确性。
冷漠大师对keil C51推栈深度的描述,让俺在理论上又上了一个台阶。
谢谢冷漠大师的大作。

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
xlsbz + 1
60
hotpower| | 2010-7-15 07:15 | 只看该作者
是的,冷漠最近不知从门哪位大师门下,进步飞速是人类无法超越的~~~

敬礼

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则