转自:http://linuxperf.com/?p=116
在Linux系统上,进程运行分为用户态与内核态,进入内核态之后使用的是内核栈,作为基本的安全机制,用户程序不能直接访问内核栈,所以尽管内核栈属于进程的地址空间,但与用户栈是分开的。Linux的内核栈大小是固定的,从2.6.32-520开始缺省大小是16KB,之前的kernel版本缺省大小是8KB。内核栈的大小可以修改,但要通过重新编译内核实现。以下文件定义了它的大小:
arch/x86/include/asm/page_64_types.h
8KB:
#define THREAD_ORDER 1
16KB:
#define THREAD_ORDER 2
由于内核栈的大小是有限的,就会有发生溢出的可能,比如调用嵌套太多、参数太多都会导致内核栈的使用超出设定的大小。内核栈溢出的结果往往是系统崩溃,因为溢出会覆盖掉本不该触碰的数据,首当其冲的就是thread_info
—
它就在内核栈的底部,内核栈是从高地址往低地址生长的,一旦溢出首先就破坏了thread_info,thread_info里存放着指向进程的指针等关键数据,迟早会被访问到,那时系统崩溃就是必然的事。
【小知识】:把thread_info放在内核栈的底部是一个精巧的设计,在高端CPU中比如PowerPC、Itanium往往都保留了一个专门的寄存器来存放当前进程的指针,因为这个指针的使用率极高,然而x86的寄存器太少了,专门分配一个寄存器实在太奢侈,所以Linux巧妙地利用了栈寄存器,把thread_info放在内核栈的底部,这样通过栈寄存器里的指针可以很方便地算出thread_info的地址,而thread_info的第一个字段就是进程的指针。
内核栈溢出导致的系统崩溃有时会被直接报出来,比如你可能会看到:
但更多的情况是不直接报错,而是各种奇怪的panic。在分析vmcore的时候,它们的共同点是thread_info被破坏了。以下是一个实例,注意在task_struct中stack字段直接指向内核栈底部也就是thread_info的位置,我们看到thread_info显然被破坏了:cpu的值大得离谱,而且指向task的指针与task_struct的实际地址不匹配:
作为一种分析故障的手段,可以监控内核栈的大小和深度,方法如下:
|
1
2
|
# mount -t debugfs nodev /sys/kernel/debug
# echo 1 > /proc/sys/kernel/stack_tracer_enabled
|
然后检查下列数值,可以看到迄今为止内核栈使用的峰值和对应的backtrace:
你可以写个脚本定时收集上述数据,有助于找到导致溢出的代码。下面是一个输出结果的实例:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# cat /sys/kernel/debug/tracing/stack_max_size
7272
# cat /sys/kernel/debug/tracing/stack_trace
)
--
0x980
0x400
0x20
0x40
0x90
0x140
0x30
0x60
]
0x340
0x590
0x40
0x500
0x5b0
0x120
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
]
0x40
0x4a0
0x30
]
0x40
0x60
0x90
]
]
0x140
0x1f0
0x60
]
]
]
]
]
]
]
0xa0
0x20
|