Stac, Stace, ace - blog space
Stacey Marshall's Weblog
« Previous day (Sep 14, 2004) | Main | Next day (Sep 15, 2004) »
20040915 Wednesday September 15, 2004
mdb: drilling sigsegv; by way of an introduction

I have been using mdb(1), Solaris Modular Debugger, to help diagnose an issue with vold(1M).

Shortly after startup vold core dumped. So I configured the system to save the core file using coreadm(1M) and restarted vold with some additional debug flags. When vold next crashed the logs it created were not particularly helpful in this case, but the core file told another story:

$ mdb /var/cores/vold:seth-5361:0-0:1094116587
Loading modules: [ libc.so.1 ld.so.1 ]
> ::status
debugging core file of vold (32-bit) from seth
file: /usr/sbin/vold
initial argv: /usr/sbin/vold -v -t -L 99
threading model: multi-threaded
status: process terminated by SIGSEGV (Segmentation Fault)
> ::regs
%g0 = 0x0000000000000000                 %l0 = 0xfef50692 
%g1 = 0x000000006e6c2cff                 %l1 = 0x01010101 
%g2 = 0x0000000000007b34                 %l2 = 0xff266b00 libc.so.1`_uberdata
%g3 = 0xffffffffffffffff                 %l3 = 0x00001084 
%g4 = 0xfffffffffffff810                 %l4 = 0x00001000 
%g5 = 0x0000000000000000                 %l5 = 0x00000000 
%g6 = 0x0000000000000000                 %l6 = 0x00000000 
%g7 = 0x00000000ff139000                 %l7 = 0x01000000 
%o0 = 0x00000000ff1e1010 libc.so.1`strlcat %i0 = 0xff30dda8 
%o1 = 0x0000000000000041                 %i1 = 0xfef50680 
%o2 = 0x00000000ff29f2dc libnsl.so.1`parse_default+0xec %i2 = 0xfef50620 
%o3 = 0x00000000ff3ea108 ld.so.1`lml_main %i3 = 0x00000000 
%o4 = 0x00000000ff3ec644 ld.so.1`dbg_mask %i4 = 0x0000ff00 
%o5 = 0x0000000000000000                 %i5 = 0x80808080 
%o6 = 0x00000000fef4f948                 %i6 = 0xfef505b8 
%o7 = 0x00000000ff3b36e8 ld.so.1`elf_rtbndr+0x10 %i7 = 0xff2155d0 
libc.so.1`snprintf+0x7c

 %psr = 0xfe900000 impl=0xf ver=0xe icc=NzvC
                   ec=0 ef=0 pil=0 s=0 ps=0 et=0 cwp=0x0
   %y = 0x00000000
  %pc = 0xff21192c libc.so.1`_ndoprnt+4
 %npc = 0xff211930 libc.so.1`_ndoprnt+8
  %sp = 0xfef4f948
  %fp = 0xfef505b8

 %wim = 0x00000000
 %tbr = 0x00000000
> 

mdb has debugger commands, known as dcmds and walkers: see the man page.

First off, the ::status dcmd shows us a summary of the current target. A segmentation fault occurs when a process tries to reference an area of memory (a segment) that has not been allocated (mapped). The ::regs dcmd shows us the registers when the fault occurred. The register %pc (Program Counter) points to the instruction that caused the violation, so lets look at the instructions around %pc

> <pc-8/4i
libc.so.1`___const_seg_900000505+0x168:         unimp     0x808
                save      %sp, -0xc70, %sp
                st        %i3, [%sp + 0x6c]
                mov       %o7, %i5
> <pc ::dis -n 2
libc.so.1`___const_seg_900000505+0x168: unimp     0x808
libc.so.1`_ndoprnt:             save      %sp, -0xc70, %sp
libc.so.1`_ndoprnt+4:           st        %i3, [%sp + 0x6c]
libc.so.1`_ndoprnt+8:           mov       %o7, %i5
libc.so.1`_ndoprnt+0xc:         clr       %i3
>

Firstly I used the older adb command to disassemble instructions around the PC, as SPARC instructions are always 4 words its easy to rewind a little first. However mdb provides the dis dcmd which nicely backs up a little for you and highlights the starting address.

Anyhow, I digress... The instructions at the PC show register %i3 being stored on the stack, %sp offset 0x6c. As the fault is a SIGSEGV we can presume that %sp+0x6c is non-existent. To confirm:

> <sp+0x6c=X
                fef4f9b4        
> <sp+0x6c/X
mdb: failed to read data from target: no mapping for address
0xfef4f9b4:
> ::mappings ! egrep 'fef|SIZE'
    BASE    LIMIT     SIZE NAME
fef00000 fef02000     2000 /usr/lib/smedia/sm_scsi.so.1
fef12000 fef14000     2000 /usr/lib/smedia/sm_scsi.so.1
fef20000 fef22000     2000 /usr/lib/straddr.so.2
fef32000 fef34000     2000 /usr/lib/straddr.so.2
fef40000 fef48000     8000 
fef50000 fef58000     8000 
fef60000 fef64000     4000 /usr/lib/vold/dev_rmdisk.so.1
fef74000 fef76000     2000 /usr/lib/vold/dev_rmdisk.so.1
fef80000 fef84000     4000 /usr/lib/vold/dev_pcmem.so.1
fef94000 fef96000     2000 /usr/lib/vold/dev_pcmem.so.1
fefb0000 fefb2000     2000 /usr/lib/vold/dev_floppy.so.1
fefc2000 fefc4000     2000 /usr/lib/vold/dev_floppy.so.1
fefd6000 fefd8000     2000 
fefe4000 fefe6000     2000 
feff6000 feff8000     2000 
> 

<sp+0x6c=X calculates the address and displays it in hex. <sp+0x6c/X calculates the address and attempts to display the data from that location in hex.

Sure enough the stack pointer register seems to be in no mans land. The mappings dcmd (output abbreviated by grep) confirms that.

So who set %sp to this value? Well that would have been the save instruction just prior to this store instruction (see disassemble above). Now the save instruction causes a new stack to be popped on top (lowest address) of the current stack. The SPARC save instruction places the old %sp in %o6 and the new %sp in %i6 (in fact %sp is a pseudo for %i6).

So Why was my stack limited to only 0x8000 (32,768): 32k?Good question to which I thank Nobutomo Nakano for his assistance in helping me dig deeper...

One clue was right at the very beginning threading model: multi-threaded. Another is at the bottom (highest address) of the stack:

> ::stack
libc.so.1`_ndoprnt+4(ff30dda8, fef50680, fef50620, 0, ff00, 80808080)
libc.so.1`snprintf+0x7c(fef50e90, 800, ff30dda8, fef54150, 59048, f)
libnsl.so.1`parse_default+0x370(fef54150, 0, 694c0, 7e, 59048, 800)
libnsl.so.1`parse_path+0xc4(fef54150, fef516f0, 5a5a8, 694b8, 80, 3)
libnsl.so.1`__nis_getnames+0xfc(fef54150, fef549e4, 5a5a8, 694b8, 694b8, 
ffbff415)
libnsl.so.1`nis_list+0x2a8(fef54a48, 10043, 0, 0, fef54a60, 1)
nss_nisplus.so.1`_nss_nisplus_expand_lookup+0x168(7ead8, fef54f78, fee86b3c, 
fef575b9, fee86b44, 0)
nss_nisplus.so.1`getbyname+0x2c(7ead8, fef54f78, ff266b00, 15684, ff1d8364, 
fee98000)
libc.so.1`nss_search+0x1d4(fee8296c, ff26573c, ff26a7cc, fef54f78, 1, ff262228)
libnsl.so.1`_switch_gethostbyname_r+0x50(fef575b9, 7c99c, 7c9b0, 2120, fef57088
, ff2b81d4)
libnsl.so.1`_door_gethostbyname_r+0x108(fef575b9, 7c99c, 7c9b0, 2120, fef57088, 
fef575b9)
libnsl.so.1`_get_hostserv_inetnetdir_byname+0xac0(2, fef57138, fef57130, 0, 
fef570ac, 0)
libnsl.so.1`netdir_getbyname+0x84(56f48, fef571bc, fef571b8, ff295aa8, 760c0, 
ff22e73c)
libnsl.so.1`_getclnthandle_timed+0x198(fef575b9, 56f48, fef5728c, ff314f00, 0, 0
)
libnsl.so.1`__rpcb_findaddr_timed+0x134(1873b, 1, 56f48, fef575b9, fef57304, 
ff314f00)
libnsl.so.1`clnt_tp_create_timed+0x5c(fef575b9, 1873b, 56f48, 73cb0, 0, ff29cc90
)
libnsl.so.1`clnt_create_timed+0x198(fef575b9, 1873b, 1, 0, 0, 516a8)
libsmedia.so.1`is_server_running+0x38(58a28, 10ea0, 1234, ff3515cc, ff110580, 
ff362000)
libsmedia.so.1`get_handle_from_fd+0x10c(17, 0, 58a28, 46008, d, ff362000)
dev_reset_symname+4(66760, 17, 666b0, 800017, 1c, 7)
dev_rmdisk.so.1`rmdisk_thread_wait+0x26c(66760, 666ec, 1, fef62a00, 8, 2)
libc.so.1`_lwp_start(0, 0, 0, 0, 0, 0)
>

A stack belonging to _lwp_start(): vold(1M) is threaded and the first argument to thr_create(3C) is the stack address. Now we could use truss(1M) or (for those using Solaris Express) dtrace(1M) to see thr_create() being used. But we need not as mdb (bless its cotton socks) has a some walkers to help:

> $l
5
> ::walk thread
1
2
4
5
> ::walk ulwps
ff138000
ff138400
ff138c00
ff139000
>

"$l" Displays the thread number. "::walk thread" displays a list of valid threads. "::walk ulwps lists ulwp_t pointers.

I can sense that you are not impressed! But wait... Look at the man page to libthread(3LIB). Notice a man page for thr_stksegment(3C) which states "The thr_stksegment() function returns, in its stack_t argument, the address and size of the calling threads stack." meaning that its got to be stored (or calculated) somewhere (or somehow). Well it would make sense to have that stored somewhere in the ulwp_t structure... And it is! Thanks to the walker ulwps we know where that is:

> ff139000 ::print ulwp_t ! grep stk
    ul_stk = 0xfef50000
    ul_stktop = 0xfef58000
    ul_stksiz = 0x8000
> 

Using print dcmd we can print the contents of the ulwp_t data structure.

Again I have used grep to show you only what is important here. Now remembering that SPARC stacks grow down, the end of the stack for this LWP is at ul_stk = 0xfef50000, its size is ul_stksiz = 0x8000 (32k) and thus its start is at ul_stktop = 0xfef58000.

So the problem is that this program didn't allow for the stack to grow so large. Lets look at the stack and see what made it out grow:

> ::stackregs 1 ! grep '('
fef4f948 libc.so.1`_ndoprnt+4(ff30dda8)
fef505b8 libc.so.1`snprintf+0x7c(fef50e90)
fef50630 libnsl.so.1`parse_default+0x370(fef54150)
fef51690 libnsl.so.1`parse_path+0xc4(fef54150)
fef540f0 libnsl.so.1`__nis_getnames+0xfc(fef54150)
fef54950 libnsl.so.1`nis_list+0x2a8(fef54a48)
fef549e8 nss_nisplus.so.1`_nss_nisplus_expand_lookup+0x168(7ead8)
fef54e48 nss_nisplus.so.1`getbyname+0x2c(7ead8)
fef54ea8 libc.so.1`nss_search+0x1d4(fee8296c)
fef54f18 libnsl.so.1`_switch_gethostbyname_r+0x50(fef575b9)
fef54fa8 libnsl.so.1`_door_gethostbyname_r+0x108(fef575b9)
fef57018 libnsl.so.1`_get_hostserv_inetnetdir_byname+0xac0(2)
fef570b0 libnsl.so.1`netdir_getbyname+0x84(56f48)
fef57150 libnsl.so.1`_getclnthandle_timed+0x198(fef575b9)
fef571d0 libnsl.so.1`__rpcb_findaddr_timed+0x134(1873b)
fef57298 libnsl.so.1`clnt_tp_create_timed+0x5c(fef575b9)
fef57308 libnsl.so.1`clnt_create_timed+0x198(fef575b9)
fef57398 libsmedia.so.1`is_server_running+0x38(58a28)
fef579c0 libsmedia.so.1`get_handle_from_fd+0x10c(17)
fef57bb0 dev_reset_symname+4(66760)
fef57c28 dev_rmdisk.so.1`rmdisk_thread_wait+0x26c(66760)
fef57fa0 libc.so.1`_lwp_start(0)
>

Here I have used stackregs dcmd as it prints out the stack pointer at the beginning of each frame. Stackregs provides more information than I need here, so again I have trimmed it with grep.

To work out where the space went one simply needs to calculate the differences between the addresses (stack/frame pointers) down the left.

But hang on a minute. This is mdb(1m), we can copy /usr/demo/mdb and write our own module to do the calculation for us....

> >fp ::walk simple_stack | ::stack_size
   FRAME    bytes kilobyte Owner
fef505b8      120        0 0
fef50630     4192        4 libnsl.so.1`parse_default+0x370
fef51690    10848       11 libnsl.so.1`parse_path+0xc4
fef540f0     2144        2 libnsl.so.1`__nis_getnames+0xfc
fef54950      152        0 libnsl.so.1`nis_list+0x2a8
fef549e8     1120        1 nss_nisplus.so.1`_nss_nisplus_expand_lookup+0x168
fef54e48       96        0 nss_nisplus.so.1`getbyname+0x2c
fef54ea8      112        0 libc.so.1`nss_search+0x1d4
fef54f18      144        0 libnsl.so.1`_switch_gethostbyname_r+0x50
fef54fa8     8304        8 libnsl.so.1`_door_gethostbyname_r+0x108
fef57018      152        0 libnsl.so.1`_get_hostserv_inetnetdir_byname+0xac0
fef570b0      160        0 libnsl.so.1`netdir_getbyname+0x84
fef57150      128        0 libnsl.so.1`_getclnthandle_timed+0x198
fef571d0      200        0 libnsl.so.1`__rpcb_findaddr_timed+0x134
fef57298      112        0 libnsl.so.1`clnt_tp_create_timed+0x5c
fef57308      144        0 libnsl.so.1`clnt_create_timed+0x198
fef57398     1576        2 libsmedia.so.1`is_server_running+0x38
fef579c0      496        0 libsmedia.so.1`get_handle_from_fd+0x10c
fef57bb0      120        0 dev_reset_symname+4
fef57c28      888        1 dev_rmdisk.so.1`rmdisk_thread_wait+0x26c
fef57fa0        0        0 libc.so.1`_lwp_start
>

The Modular nature of MDB makes it easy to add your own custom dcmds and walkers. Note: kilobyte column is rounded to nearest K.

In short the fix was to provide a larger stack for this thread.

I hope this little walk through of mdb has been of some interest if not educational to some.

Stace


Sep 15 2004, 10:45:17 AM GMT+00:00 | Technorati tags: | Permalink Comments [3] del.icio.us technorati digg