GB | BIG5
|
| 首頁 > 編程技術 > C/C++ > 正文 |
 |
| Unix編程/應用問答中文版 ---2.堆棧相關問題 |
| 本文出自:http://www.nsfocus.com 維護:小四 (2002-10-18 06:02:00) |
2. 堆棧相關問題
2.1 如何理解pstack的輸出信息
2.2
2.3 Solaris中如何獲取一個C程序的調用棧回溯
2.4 如何編程獲取棧底地址
2.5 如何得到一個運行中進程的內存映像
2.6 調試器如何工作的
2.7 x86/Linux上如何處理SIGFPE信號
--------------------------------------------------------------------------
2. 堆棧相關問題
2.1 如何理解pstack的輸出信息
Q: 080603a7 main (1, 80479b8, 80479c0) + d53
結尾的d53是什
A: Roger A. Faulkner <raf@sunraf.Sun.COM>
在代碼段絕對地址0x080603a7處,main()調用了一個函數,0x080603a7正是
main + 0xd53,換句話說,從main()函數開始的0xd53偏移處。
2.3 Solaris中如何獲取一個C程序的調用棧回溯
Q: 我想在Solaris 2.6極其續版本上獲取一個C程序的調用棧回溯,類似如下輸出
(10) 0x00045e08 integ + 0x408 [./two_brn.e]
(11) 0x0006468c trajcem + 0x128 [./two_brn.e]
(12) 0x00055490 fly_traj + 0xf58 [./two_brn.e]
(13) 0x0004052c top_level + 0x14 [./two_brn.e]
(14) 0x000567e4 _start + 0x34 [./two_brn.e]
這樣我就可以知道當程序崩潰、死鎖的時候代碼執行到了何處。在HP-UX和IRIX上
可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?
Q: 有沒有辦法顯示當前堆棧中的數據(GNU/Linux系統)?我希望自己的異常處理程序
在進程結束前dump整個棧區(stack),以便觀察到棧頂是什函數。對調試意想
不到的運行時錯誤而言,這很重要。
A: Bjorn Reese <breese@mail1.stofanet.dk>
用/usr/proc/bin/pstack [-F] <pid ...>
參看這個例子代碼,http://home1.stofanet.dk/breese/debug/debug.tar.gz
Q: is there a way to access call stack information at run time from within
a program? i've been maintaining my own crude stack using __FUNCTION__
and linked lists but can't help but think there's gotta be a better
way...
A: Nate Eldredge <neldredge@hmc.edu>
這依賴你的系統,如果使用glibc 2.1或更新版本,可以使用backtrace()函數,
參看<execinfo.h>,其他系統可能有不同的技術支持。
注意,你所使用的辦法可能是唯一能夠保証跨平台使用的
A: Andrew Gabriel <andrew@cucumber.demon.co.uk> Consultant Software Engineer
下面是一個backtrace()的應用舉例,如果你使用Solaris 2.4及其續版本,那
這個例子可以很好的工作。很可能無法工作在64-bit模式下,我沒有嘗試過,
好像Solaris 7已經提供了一個類似的演示程序。還可以增加某些功能,我沒有時
間了。
/*
* Produce a stack trace for Solaris systems.
*
* Copyright (C) 1995-1998 Andrew Gabriel <andrew@cucumber.demon.co.uk>
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*
*/
/* ......................................................................... */
#include <setjmp.h>
#include <sys/types.h>
#include <sys/reg.h>
#include <sys/frame.h>
#include <dlfcn.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#if defined(sparc) || defined(__sparc)
#define FLUSHWIN() asm("ta 3");
#define FRAME_PTR_INDEX 1
#define SKIP_FRAMES 0
#endif
#if defined(i386) || defined(__i386)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 3
#define SKIP_FRAMES 1
#endif
#if defined(ppc) || defined(__ppc)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 0
#define SKIP_FRAMES 2
#endif
/* ......................................................................... */
static void print_address ( void * pc )
{
Dl_info info;
if ( dladdr( pc, &info ) == 0 )
{
/* not found */
fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc );
}
else
{
/* found */
fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
}
return;
} /* end of print_address */
/* ......................................................................... */
static int validaddr ( void * addr )
{
static long pagemask = -1;
char c;
if ( pagemask == -1 )
{
pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
}
addr = ( void * )( ( long )addr & pagemask );
if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
{
return 0; /* invalid */
}
else
{
return 1; /* valid */
}
} /* end of validaddr */
/* ......................................................................... */
/*
* this function walks up call stack, calling print_addess
* once for each stack frame, passing the pc as the argument.
*/
static void print_stack ( void )
{
struct frame * sp;
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()直接返回了,導致再次觸發異常,所以無休止輸出。事實上
在所有的計算器處理程序中,都會對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;
/*
* 另外,增加這行,再次對比有-O3和無-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
關-O3的討論,對gcc編譯器熟悉的朋友請繼續,呵,我對Linux下的這此東西,實
在缺乏興趣。
(http://www.fanqiang.com)
進入【UNIX論壇】
|
|
| 相關文章 |
Unix編程/應用問答中文版 ---1.系統管理配置問題 (2002-10-17 06:02:00) Unix編程/應用問答中文版 ---0.簡介 Unix/C傳奇問題 (2002-10-16 06:02:01)
|
|
|
|
 |
★ 樊強制作 歡迎分享 ★ |