为什么同一个程序在不同的运行中堆栈大小不同?
Why is the stack size different in different runs of the same program?
考虑以下程序。它从命令行获取一个参数,然后展开一个递归函数直到该限制。
#include <stdio.h>
#include <stdlib.h>
int rec(int x, int limit) {
if (x == limit) {
return limit;
}
int r = rec(x + 1, limit);
return r - 1;
}
int main(int arc, char* argv[]) {
int result, limit;
limit = atoi(argv[1]);
printf("stack: %p\n", &result);
result = rec(0, limit);
printf("%d\n", result);
}
如果我编译它,我希望它 运行 出栈以获得固定的输入参数限制。发生了其他事情。
dejan@raven:~/test/stack$ gcc stack.c
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7fff42fd58f0
Segmentation fault (core dumped)
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7ffdd2dd8b20
0
在两个不同的运行中,堆栈大小似乎不同。这似乎不是编译器问题,因为同样的事情发生在 clang 上,并且反汇编不涉及任何奇怪的事情。
为什么不同 运行 的筹码量不同?
添加 SIGSEGV 处理程序:
void handler( int sig )
{
char buffer[ 1024 ]
sprintf( buffer, "/path/to/pmap %d", getpid() );
system( buffer );
exit( 0 );
}
int main( int argc, char *argv[] )
{
signal( SIGSEGV, handler );
.
.
.
这样,您的进程将在 SEGV 时发出其地址映射 space,而不是生成核心文件。
请注意,一般来说,这是一种非常危险的做事方式。它不是真正的异步信号安全。但是你没有做任何会导致死锁的事情。
我已将 /proc/self/maps
解析器添加到您的程序中(与@AndrewHenle 建议的方法相同,但我在程序开始时这样做,并且不调用 pmap
):
char* get_stack_bounds() {
FILE* maps = fopen("/proc/self/maps", "r");
static char line[256];
while(!feof(maps)) {
fgets(line, 255, maps);
if(strstr(line, "[stack]")) {
char* space = strchr(line, ' ');
*space = '[=10=]';
fclose(maps);
return line;
}
}
fclose(maps);
return NULL;
}
unsigned long get_stack_right() {
char* bounds = get_stack_bounds();
bounds = strchr(bounds, '-') + 1;
return strtol(bounds, NULL, 16);
}
并在main()
开头转储一些信息:
printf("&result: %p delta: %ld\n", &result,
get_stack_right() - ((unsigned long) &result));
以下是一些结果:
> ./a.out 104747
&result: 0x7fff3347c7f8 delta: 6152
0
> ./a.out 174580
&result: 0x7fffe43c9b38 delta: 5320
0
> ./a.out 174580
&result: 0x7fff26ad2b28 delta: 9432
Segmentation fault (core dumped)
> ./a.out 174580
&result: 0x7fff145aa5a8 delta: 6744
0
> ./a.out 174580
&result: 0x7fff74fff0b8 delta: 12104
Segmentation fault (core dumped)
我认为delta
(result
地址和堆栈基地址之间的差异)和段错误之间的相关性很明显。
您应该注意 main()
不是程序中运行的第一个函数,实际入口点将从 crt1.o(或其他)_start()
,因此初始堆栈大小可以不同。
实际问题是Address space layout randomization 。
这是 fs/binfmt_elf_fdpic.c
对其用法的评论:
/* In some cases (e.g. Hyper-Threading), we want to avoid L1 evictions
* by the processes running on the same package. One thing we can do is
* to shuffle the initial stack for them, so we give the architecture
* an opportunity to do so here.
*/
sp = arch_align_stack(bprm->p);
这里是 arch_align_stack()
在 x86 上的实现:
unsigned long arch_align_stack(unsigned long sp)
{
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
sp -= get_random_int() % 8192;
return sp & ~0xf;
}
考虑以下程序。它从命令行获取一个参数,然后展开一个递归函数直到该限制。
#include <stdio.h>
#include <stdlib.h>
int rec(int x, int limit) {
if (x == limit) {
return limit;
}
int r = rec(x + 1, limit);
return r - 1;
}
int main(int arc, char* argv[]) {
int result, limit;
limit = atoi(argv[1]);
printf("stack: %p\n", &result);
result = rec(0, limit);
printf("%d\n", result);
}
如果我编译它,我希望它 运行 出栈以获得固定的输入参数限制。发生了其他事情。
dejan@raven:~/test/stack$ gcc stack.c
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7fff42fd58f0
Segmentation fault (core dumped)
dejan@raven:~/test/stack$ ./a.out 174580
stack: 0x7ffdd2dd8b20
0
在两个不同的运行中,堆栈大小似乎不同。这似乎不是编译器问题,因为同样的事情发生在 clang 上,并且反汇编不涉及任何奇怪的事情。
为什么不同 运行 的筹码量不同?
添加 SIGSEGV 处理程序:
void handler( int sig )
{
char buffer[ 1024 ]
sprintf( buffer, "/path/to/pmap %d", getpid() );
system( buffer );
exit( 0 );
}
int main( int argc, char *argv[] )
{
signal( SIGSEGV, handler );
.
.
.
这样,您的进程将在 SEGV 时发出其地址映射 space,而不是生成核心文件。
请注意,一般来说,这是一种非常危险的做事方式。它不是真正的异步信号安全。但是你没有做任何会导致死锁的事情。
我已将 /proc/self/maps
解析器添加到您的程序中(与@AndrewHenle 建议的方法相同,但我在程序开始时这样做,并且不调用 pmap
):
char* get_stack_bounds() {
FILE* maps = fopen("/proc/self/maps", "r");
static char line[256];
while(!feof(maps)) {
fgets(line, 255, maps);
if(strstr(line, "[stack]")) {
char* space = strchr(line, ' ');
*space = '[=10=]';
fclose(maps);
return line;
}
}
fclose(maps);
return NULL;
}
unsigned long get_stack_right() {
char* bounds = get_stack_bounds();
bounds = strchr(bounds, '-') + 1;
return strtol(bounds, NULL, 16);
}
并在main()
开头转储一些信息:
printf("&result: %p delta: %ld\n", &result,
get_stack_right() - ((unsigned long) &result));
以下是一些结果:
> ./a.out 104747
&result: 0x7fff3347c7f8 delta: 6152
0
> ./a.out 174580
&result: 0x7fffe43c9b38 delta: 5320
0
> ./a.out 174580
&result: 0x7fff26ad2b28 delta: 9432
Segmentation fault (core dumped)
> ./a.out 174580
&result: 0x7fff145aa5a8 delta: 6744
0
> ./a.out 174580
&result: 0x7fff74fff0b8 delta: 12104
Segmentation fault (core dumped)
我认为delta
(result
地址和堆栈基地址之间的差异)和段错误之间的相关性很明显。
您应该注意 main()
不是程序中运行的第一个函数,实际入口点将从 crt1.o(或其他)_start()
,因此初始堆栈大小可以不同。
实际问题是Address space layout randomization 。
这是 fs/binfmt_elf_fdpic.c
对其用法的评论:
/* In some cases (e.g. Hyper-Threading), we want to avoid L1 evictions
* by the processes running on the same package. One thing we can do is
* to shuffle the initial stack for them, so we give the architecture
* an opportunity to do so here.
*/
sp = arch_align_stack(bprm->p);
这里是 arch_align_stack()
在 x86 上的实现:
unsigned long arch_align_stack(unsigned long sp)
{
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
sp -= get_random_int() % 8192;
return sp & ~0xf;
}