在《UNIX操作系统设计》第11章的第6题写道:
Write a program that attaches shared memory too close to the end of its stack, and let the stack grow into the shared memory region. When does it incur a memory fault?
出于对结果的兴趣写了如下程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
char *get_esp() {
char *esp;
__asm__("movl %%esp, %%eax\n" :"=a"(esp):);
return esp;
}
void re(int dummy) {
re(dummy);
}
int main(int argc, char *argv[]) {
size_t size = 4096;
int mask = ~(0xfff);
char *wanted_addr;
char *esp;
char *addr;
esp = get_esp();
wanted_addr = ((int)esp & mask) - 0x1000;
printf("esp = 0x%x\nwanted_addr = 0x%x\n", esp, wanted_addr);
addr = (char *)mmap(wanted_addr, size, PROT_WRITE| PROT_READ, \
MAP_PRIVATE| MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("addr = 0x%x\n", addr);
re(1);
}
总体来说该程序就是利用mmap分配一个与esp所在页框相邻且地址更低的页框,然后调用一个递归函数消耗掉所有的堆栈空间,看进程会在什么时候崩溃。编译后在gdb下运行结果如下:
(gdb) r Starting program: /home/xudifsd/a.out esp = 0xbffff0c4 wanted_addr = 0xbfffe000 addr = 0xb7fd9000 Program received signal SIGSEGV, Segmentation fault. 0x08048497 in re () (gdb) p $esp $1 = (void *) 0xbf800000
可以看出程序在esp为0xbf800000处收到SIGSEGV信号,这和mmap得到的地址0xb7fd9000相差太多所以不可能是因为写到mmap分配的页面造成的错误,然后猜测可能是写到动态库,但是lld结果如下:
linux-gate.so.1 => (0xb7725000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7563000) /lib/ld-linux.so.2 (0xb7726000)
比mmap分配的地址还要低,所以也不可能是写到了动态库的代码部分产生的错误。然后计算了下0xbffff000 – 0xbf800000差不多等于8M才发现这是由于linux默认的堆栈大小为8M造成的(用ulimit -s查看),于是给代码加了段函数:
void set_limit() {
struct rlimit rl;
rl.rlim_cur = 4294967295;
rl.rlim_max = 4294967295;
if (setrlimit(RLIMIT_STACK, &rl) == -1) {
perror("set_limit");
exit(1);
}
}
再运行发现程序在0xb8001000收到SIGBUS信号退出。虽然比前一次有进步但是也没有写到mmap分配的那个页框,计算0xbffff000 – 0xb8001000得到差不多127M的空间,但是完全不清楚为什么进程会在这收到信号。
之后直接在shell中运行ulimit -s 4194304再启动程序会发现程序直接被SIGKILL杀死,但是在gdb中也没法找出最后一刻的esp值,因此我以为是写到了只读的共享库代码页造成的,所以将re函数改写成为:
void re(int dummy) {
printf("esp == 0x%x\n", get_esp());
re(dummy);
}
这样修改后运行程序会造成系统崩溃,所以如果想实验请先保存所有资料。
运行时会发现程序的esp值能够低于前面共享库的地址,也就是说程序会将共享库的空间也占据,即使它是只读的代码段。我的系统内存加swap都才1.5G(表鄙视,现在市面上都买不到DDR2的内存条了,所以我也无能为力。。),所以程序运行到0xaa000000左右系统就已经无响应了,所以如果有内存大的读者愿意试可以将结果告诉下我。
看来最初的问题是没办法回答了,因为那书基于的是System V系统,而现在linux系统变化了很多,就连这么多实验也没办法找出答案,如果有读者知道还希望能告诉我。
问题
即使是实验做完了,这些问题也解决不了,看来还要等以后会更多东西时才能给出答案吧。
- 为什么mmap分配这么低的一个空间?内核代码中有限制?
- 执行setrlimit调用和在shell下使用ulimit有什么区别,为什么效果差这么多?
- 为什么第二次会收到SIGBUS信号而第三次没有在相同地址收到信号,而是收到SIGKILL信号?
- 为什么修改了re函数后程序能够覆盖只读的代码段,难道我的代码错了,或者说push指令可以不管页面的只读属性?
- 为什么会收到SIGKILL信号?