当前位置:早雪网网络学院编程文档C/C++ → Unix编程/应用问答中文版 ---2.堆栈相关问题

Unix编程/应用问答中文版 ---2.堆栈相关问题

减小字体 增大字体 作者:不详  来源:supcode.com收集整理  发布时间:2005-7-22 19:40:33
两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再 
使用这块内存。因此,0xbfbfe000才是真正的栈底。 

tt在OpenBSD 2.8上测试结果,栈底是0xdfbfe000,注意和NetBSD 1.5相差很大。 

A: tt <warning3@nsfocus.com> 

-------------------------------------------------------------------------- 
/* 
* gcc -Wall -O3 -o gstack gstack.c 

* A simple example to get the current stack bottom address 
* warning3 <warning3@nsfocus.com> 
* 2001-06-01 

* Modified by scz <scz@nsfocus.com> 
* 2001-06-02 
*/ 

#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <unistd.h> 
#include <setjmp.h> 

typedef void Sigfunc ( int );  /* for signal handlers */ 

       Sigfunc * signal           ( int signo, Sigfunc * func ); 
static Sigfunc * Signal           ( int signo, Sigfunc * func ); 
static char    * get_stack_bottom ( void ); 
static void      segfault         ( int signo ); 

static sigjmp_buf             jmpbuf; 
static volatile sig_atomic_t  canjump = 0; 
static Sigfunc               *seg_handler; 
static Sigfunc               *bus_handler;  /* for xxxBSD */ 

Sigfunc * signal ( int signo, Sigfunc * func ) 

    struct sigaction act, oact; 

    act.sa_handler = func; 
    sigemptyset( &act.sa_mask ); 
    act.sa_flags   = 0; 
    if ( sigaction( signo, &act, &oact ) < 0 ) 
    { 
        return( SIG_ERR ); 
    } 
    return( oact.sa_handler ); 
}  /* end of signal */ 

static Sigfunc * Signal ( int signo, Sigfunc * func )  /* for our signal() funct 
ion */ 

    Sigfunc * sigfunc; 

    if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR ) 
    { 
        exit( EXIT_FAILURE ); 
    } 
    return( sigfunc ); 
}  /* end of Signal */ 

static char * get_stack_bottom ( void ) 

    volatile char *c;  /* for autovar, must be volatile */ 

    seg_handler = Signal( SIGSEGV, segfault ); 
    bus_handler = Signal( SIGBUS, segfault ); 
    c           = ( char * )&c; 

    if ( sigsetjmp( jmpbuf, 1 ) != 0 ) 
    { 
        Signal( SIGSEGV, seg_handler ); 
        Signal( SIGBUS, bus_handler ); 
        return( ( char * )c ); 
    } 
    canjump = 1;  /* now sigsetjump() is OK */ 
    while ( 1 ) 
    { 
        *c = *c; 
        c++; 
    } 
    return( NULL ); 
}  /* end of get_stack_bottom */ 

static void segfault ( int signo ) 

    if ( canjump == 0 ) 
    { 
        return;  /* unexpected signal, ignore */ 
    } 
    canjump = 0; 
    siglongjmp( jmpbuf, signo );  /* jump back to main, don't return */ 
}  /* end of segfault */ 

int main ( int argc, char * argv[] ) 

    fprintf( stderr, "Current stack bottom is at 0x%p\n", get_stack_bottom() ); 
    return( EXIT_SUCCESS ); 
}  /* end of main */ 
-------------------------------------------------------------------------- 

D: scz <scz@nsfocus.com> 2001-06-03 00:38 

W. Richard Stevens在<<Advanced Programming in the UNIX Environment>>中详细 
介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。 

这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV 
信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。 

tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD, 
NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV 
信号。 

非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号 
句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此 
时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句 
柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了 
保存恢复,有些系统没有做。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函 
数。下面来自SPARC/Solaris 7的setjmp(3C) 

-------------------------------------------------------------------------- 
#include <setjmp.h> 

int  setjmp     ( jmp_buf env ); 
int  sigsetjmp  ( sigjmp_buf env, int savemask ); 
void longjmp    ( jmp_buf env, int val ); 
void siglongjmp ( sigjmp_buf env, int val ); 
-------------------------------------------------------------------------- 

如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回 
来的时候从env中恢复信号屏蔽字。 

数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有 
虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是 
与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。 

在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也 
保持不变。 

无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom() 
中声明c为volatile变量。 

注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、 
SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指 
令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一 
次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用 
长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果 
在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。 

D: scz <scz@nsfocus.com> 2001-06-03 00:40 

在x86/Linux系统中用如下命令可以确定栈区所在 

# cat /proc/1/maps  <-- 观察1号进程init 
... ... 
bfffe000-c0000000 rwxp fffff000 00:00 0 


在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在 

# /usr/proc/bin/pmap 1  <-- 观察1号进程init 
... ... 
FFBEC000     16K read/write/exec     [ stack ] 


16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000 

与前面tt介绍的 

SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 ) 

相符合。 

此外,在SPARC/Solaris 7下,可以这样验证之 

# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit" 
[7015]  |0x0000100546f8|0x000000000008|OBJT |GLOB |0    |ABS    |_userlimit 
[8051]  |0x000010054700|0x000000000008|OBJT |GLOB |0    |ABS    |_userlimit32 
# echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem 
physmem 3b72 
_userlimit: 
_userlimit:     ffffffff80000000 
# skd64 0x000010054700 8 
byteArray [ 8 bytes ] ----> 
0000000000000000  00 00 00 00 FF BF 00 00 
#                             ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户 
                                          空间上限 

如果编译64-bit应用

上一页  [1] [2] [3] [4]  下一页

[数据载入中...] [返回上一页] [打 印]