📄 unix编程应用问答中文版.txt
字号:
{
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 + -