⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 unix编程应用问答scz.html

📁 书名为: Unix编程/应用问答中文版V0.03
💻 HTML
📖 第 1 页 / 共 5 页
字号:
    jmp_buf        env;
    int            i;
    int *          iptr;

    FLUSHWIN();

    setjmp( env );
    iptr = ( int * )env;

    sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];

    for ( i = 0; i < SKIP_FRAMES && sp; i++ )
    {
        if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) )
        {
            fprintf( stderr, "***[stack pointer corrupt]\n" );
            return;
        }
        sp = ( struct frame * )sp->fr_savfp;
    }

    i = 100;  /* looping check */

    while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i
 )
    {
         print_address( ( void * )sp->fr_savpc );
         sp = ( struct frame * )sp->fr_savfp;
    }
}  /* end of print_stack */

/* ......................................................................... */

void backtrace( void )
{
    fprintf( stderr, "***backtrace...\n" );
    print_stack();
    fprintf( stderr, "***backtrace ends\n" );
}

/* ......................................................................... */

2.4 如何编程获取栈底地址

Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序
   获取这个栈底地址。

A: tt <warning3@nsfocus.com> 2001-06-02 19:40

假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址

x86/Linux         栈底是0xc0000000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 2.6 栈底是0xf0000000( 栈底往低地址的4个字节总是零 )
x86/FreeBSD       栈底是0xbfc00000( 栈底往低地址的4个字节总是零 )
x86/NetBSD 1.5    栈底是0xbfbfe000
x86/OpenBSD 2.8   栈底是0xdfbfe000

D: jonah

对于NetBSD 1.5,栈底是0xbfc00000。根据源码,最高用户地址是0xbfbfe000,因为
最后4MB(2^22)的最后两页(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应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000

# /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c
# ./gstack
Current stack bottom is at 0xffffffff80000000
#

对于SPARC/Solaris 2.6 32-bit kernel mode

# echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem
physmem 3d24
_userlimit:
_userlimit:     f0000000
#

2.5 如何得到一个运行中进程的内存映像

A: Sun Microsystems 1998-03-30

有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这
样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347

# gcore 5347
gcore: core.5347 dumped
# file core.5347
core.5347:      ELF 32-位 MSB core文件 SPARC 版本 1,来自'bash'
#

注意,只能获取属主是你自己的进程的内存映像,除非你是root。

2.6 调试器如何工作的

Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试
   器,该如何做?

A: Erik de Castro Lopo <nospam@mega-nerd.com>

   这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我
   不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN
   和BSD 4.3都支持它。

   为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:

   ptrace( PTRACE_TRACEME, 0, 0, 0 );

   接下来调用exec()家族的函数执行你最终企图跟踪的程序。

   为了单步进入子进程,在父进程中调用:

   ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );

   还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。

   GDB的源代码足以回答这个问题。

2.7 x86/Linux上如何处理SIGFPE信号

Q: 参看如下程序

--------------------------------------------------------------------------
/*
 * gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
 *
 * 注意与下面的编译效果进行对比,去掉优化开关-O3
 *
 * gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c
 */

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

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

       Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void      on_fpe ( int signo );

Sigfunc * signal ( int signo, Sigfunc *func )
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset( &act.sa_mask );
    act.sa_flags   = 0;
    if ( signo == SIGALRM )
    {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;  /* SunOS 4.x */
#endif
    }
    else
    {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;  /* SVR4, 44BSD */
#endif
    }
    if ( sigaction( signo, &act, &oact ) < 0 )
    {
        return( SIG_ERR );
    }
    return( oact.sa_handler );
}  /* end of signal */

static Sigfunc * Signal ( int signo, Sigfunc *func )
{
    Sigfunc *sigfunc;

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

static void on_fpe ( int signo )
{
    fprintf( stderr, "here is on_fpe\n" );
    return;
}  /* end of on_fpe */

int main ( int argc, char * argv[] )
{
    unsigned int i;

    Signal( SIGFPE, on_fpe );
    i = 51211314 / 0;
    /*
     * 另外,增加这行后,再次对比有-O3和无-O3的效果
     *
     * fprintf( stderr, "i = %#X\n", i );
     */
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果
输出"here is on_fpe",则会发现永不停止。

D: 小四 <scz@nsfocus.com> 2001-12-14 18:25

为了便于讨论,约定两个名词,中断和异常。这里中断指最常规的中断,比如int指
令带来的软中断。异常的典型代表有除0错。区别在于,发生异常时,x86架构上CPU
将当前EIP(指向引发异常的指令)压栈,发生中断时,x86架构上CPU将当前EIP的后一
个地址(指向引发中断的指令的后一条指令)压栈。在异常处理代码中,如果认为能够
从灾难中恢复,可以不修改被压栈的EIP,从而返回到引发异常的指令处。更多细节
请查看Intel手册。

这些是从前DOS下残留的汇编知识,不过也快忘光了,刚才又找元宝宝确认了一下。

在上述代码中,on_fpe()直接返回了,导致再次触发异常,所以无休止输出。事实上

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -