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

📄 unix编程应用问答中文版.txt

📁 这份文档不是FAQ(Frequently Answered Question)
💻 TXT
📖 第 1 页 / 共 5 页
字号:
    {
        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 0x%08x\n",
             ( unsigned int )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跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了
保存恢复,有些系统没有做,比如x86/Linux Kernel 2.4.7-10的setjmp/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
#

Q: 在x86/Linux平台上如何定位栈区(stack)的栈底(高址)与栈顶(低址)位置。

D: "Andrew Gabriel" <andrew@cucumber.demon.co.uk>

试试getcontext(2)

A: "Shaun Clowes" <delius@zero.spam.progsoc.org>

检查/proc/<pid>/stat,其中有两个域对应栈底(非页对齐的)与栈顶。

如果使用getcontext(2),可以通过struct ucontext的uc_mcontext成员获取栈顶位
置,参看/usr/include/sys/ucontext.h。不幸的是此时uc_stack成员未被设置,无
法简单获取栈底位置,至少对于我所检测的版本而言,Redhat 2.4.18-3smp kernel
with glibc 2.2.5。

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

在上述代码中,on_fpe()直接返回了,再次触发除零错,所以无休止输出。事实上在
所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的时
候又碰上过。正确的做法是,利用远跳转转移,让开触发除零错的代码。

代码修改如下

--------------------------------------------------------------------------
/*
 * gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
 *
 * 注意与下面的编译效果进行对比,去掉优化开关-O3
 *
 * gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.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 );

static sigjmp_buf             jmpbuf;
static volatile sig_atomic_t  canjump = 0;

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 )
{
    if ( canjump == 0 )
    {
        return;  /* unexpected signal, ignore */
    }
    canjump = 0;
    fprintf( stderr, "here is on_fpe\n" );
    siglongjmp( jmpbuf, signo );  /* jump back to main, don't return */
    return;
}  /* end of on_fpe */

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

    if ( sigsetjmp( jmpbuf, 1 ) != 0 )
    {
        fprintf( stderr, "c u later\n" );
        return( EXIT_SUCCESS );
    }
    /*
     * now sigsetjump() is OK
     */
    canjump = 1;
    Signal( SIGFPE, on_fpe );
    i = 51211314 / 0;
    /*
     * 另外,

⌨️ 快捷键说明

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