上篇文章:ARM Linux 系统稳定性分析入门及渐进 3 – 栈溢出
下篇文章:ARM Linux 系统稳定性分析入门及渐进 5 – kernel hung task 工作原理
1.1 RT-Thread 的系统栈
如果是 GCC编译器,系统栈底的值一般会在系统链接脚本 link.lds
中设置, 如下配置系统栈的大小为 1K
;
如在 rtthread 代码 swm320-lq100/drivers/linker_scripts/link.lds 中有如下定义:
_system_stack_size = 0x200;
在系统启动阶段一般都是先通过汇编代码进行部分处理,比如 data 段中的数据copy, 及 bss 段的清零,然后一般会跳转到C代码,在使用C代码之前需要设置好栈的地址。
我们知道 C
代码实现的函数通过反汇编可以看到:在函数之间的调用处会保存一些关键信息,比如 PC/LR/SP/FP,及相关参数到栈中,所以在跳转到 C 代码之前一定要配置好栈的地址。
系统栈地址的配置一般都是在汇编阶段给 SP指针赋值,在 RT-Thread
中 Cortex-M7 平台的 BSP目录下一般会有个名叫 startup.s 的文件,在该文件中的会对 SP 进行赋值:
Reset_Handler:
ldr sp, =_estack
_estack
即为栈底(栈底是高地址)的位置,该值会在连接脚本中赋值, 如下:
.stack :
{
. = ALIGN(4);
__sstack = .;
. = . + system_stack_size;
. = ALIGN(4)
_estack = .;
}> RAM
在 RT-Thread 中需要知道系统栈(中断栈)与进程栈不是一回事
1.2 RT-Thread 进程栈
1.1 节内容中对于栈的描述是在 scheduler 启动之前的情况,在 scheduler 启动后(中断的打开也是在调度器启动的第一个线程前打开的),也即 在多线程系统中,每个线程都是独立的、互不干扰的,所以要为每个线程都分配独立的栈空间。这个栈空间可以是:
- 一个预先定义好的全局数组,定义线程栈的示例代码如下:
ALIGN(RT_ALIGN_SIZE)
rt_uint8_t thread1_stack[512];
rt_uint8_t thread2_stack[1024];
- 也可以是动态分配的一段内存空间:
thread->stack_addr = (void *)RT_KERNEL_MALLOC(stack_size);
static rt_err_t _thread_init(struct rt_thread *thread, ...)
{
...
/* stack init */
thread->stack_addr = stack_start;
thread->stack_size = stack_size;
/* init thread stack */
rt_memset(thread->stack_addr, '#', thread->stack_size);
...
从上面可以看到 task 初始化的时候会将栈空间填入'#',
这个是为了后面检查栈是否溢出的一种方式。
1.2.1 线程栈溢出检查
OS 在调度时 (rt_scheudle
) 主动检查栈是否溢出:
- 线程栈底部预留内存中的 pattern 是不是有被修改;
SP
是否超出线程栈范围
栈增长方向不同,略有区别,原理一样,ARM 架构通常使用向下增长方式。
#ifdef RT_USING_OVERFLOW_CHECK
static void _rt_scheduler_stack_check(struct rt_thread *thread)
{
RT_ASSERT(thread != RT_NULL);
#ifdef ARCH_CPU_STACK_GROWS_UPWARD
if (*((rt_uint8_t *)((rt_ubase_t)thread->stack_addr + thread->stack_size - 1)) != '#' ||
#else
if (*((rt_uint8_t *)thread->stack_addr) != '#' ||
#endif /* ARCH_CPU_STACK_GROWS_UPWARD */
(rt_ubase_t)thread->sp <= (rt_ubase_t)thread->stack_addr ||
(rt_ubase_t)thread->sp >
(rt_ubase_t)thread->stack_addr + (rt_ubase_t)thread->stack_size)
{
rt_ubase_t level;
rt_kprintf("thread:%s stack overflow\n", thread->name);
level = rt_hw_interrupt_disable();
while (level);
}
... ...
}
#endif /* RT_USING_OVERFLOW_CHECK */
stack frame | addr |
---|---|
栈底: thread->stack_addr | low |
stack size | |
栈顶:thread->stack_addr + thread->stack_size | high |
上篇文章:ARM Linux 系统稳定性分析入门及渐进 3 – 栈溢出
下篇文章:ARM Linux 系统稳定性分析入门及渐进 5 – kernel hung task 工作原理