Xudifsd Rational life

关于linux的堆栈实验

2012-08-08
xudifsd

在《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系统变化了很多,就连这么多实验也没办法找出答案,如果有读者知道还希望能告诉我。

问题

即使是实验做完了,这些问题也解决不了,看来还要等以后会更多东西时才能给出答案吧。

  1. 为什么mmap分配这么低的一个空间?内核代码中有限制?
  2. 执行setrlimit调用和在shell下使用ulimit有什么区别,为什么效果差这么多?
  3. 为什么第二次会收到SIGBUS信号而第三次没有在相同地址收到信号,而是收到SIGKILL信号?
  4. 为什么修改了re函数后程序能够覆盖只读的代码段,难道我的代码错了,或者说push指令可以不管页面的只读属性?
  5. 为什么会收到SIGKILL信号?

Similar Posts

上一篇 相遇和陪伴

Comments