I've run into a segmentation fault (seemingly caused by memory corruption)
in TclpAlloc(). I'm configuring Tcl with the following options (and it's
being compiled and run on Redhat Enterprise Linux 4.9 x86_64):
./configure \
--prefix=/usr/local/tcl84 \
--enable-64bit \
--enable-symbols \
--enable-threads
I'm specifically working with Tcl 8.4.19, but I've verified that the crash
occurs with both 8.5.9 and 8.5.10. The crashes look like this in gdb:
---- 8< -------------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1084729984 (LWP 3783)]
TclpAlloc (reqsize=292) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclThreadAlloc.c:344
344 cachePtr->buckets[bucket].firstPtr = blockPtr->b_next;
(gdb) list
339 while (binfo[bucket].blocksize < size) {
340 ++bucket;
341 }
342 if (cachePtr->buckets[bucket].nfree || GetBlocks(cachePtr, bucket)) {
343 blockPtr = cachePtr->buckets[bucket].firstPtr;
344 cachePtr->buckets[bucket].firstPtr = blockPtr->b_next;
345 --cachePtr->buckets[bucket].nfree;
346 ++cachePtr->buckets[bucket].nget;
347 cachePtr->buckets[bucket].nrequest += reqsize;
348 }
(gdb) backtrace
#0 TclpAlloc (reqsize=292) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclThreadAlloc.c:344
#1 0x0000002a959022d5 in Tcl_Alloc (size=
4023649519) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclCkalloc.c:1002
#2 0x0000002a959185f0 in TclInitByteCodeObj (objPtr=0x20aacb0, envPtr=0x40a6f660)
at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclCompile.c:1805
#3 0x0000002a9591a85d in TclSetByteCodeFromAny (interp=0x36d07d0, objPtr=0x20aacb0, hookProc=0, clientData=0x0)
at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclCompile.c:427
[...etc...]
---- 8< -------------------------------------------------
The Tcl_Alloc()/TclpAlloc() sequence is always the last thing on the call
stack, though the calling routines may differ. The problem is that blockPtr
(aka cachePtr->buckets[bucket].firstPtr) is an invalid pointer:
---- 8< -------------------------------------------------
(gdb) print bucket
$1 = 4
(gdb) print cachePtr->buckets[bucket]
$2 = {firstPtr = 0x2aef8804ef, nfree = 1, nget = 7429, nput = 4813, nwait = 0, nlock = 133, nrequest = 658018}
(gdb) print blockPtr
$3 = (Block *) 0x2aef8804ef
(gdb) print blockPtr->b
Cannot access memory at address 0x2aef8804ef
---- 8< -------------------------------------------------
One potentially significant fact: over the dozens of crashes I've observed,
the crash *always* happens with bucket 4.
Also: in the backtrace above, notice that the size variable is
4023649519
in the Tcl_Alloc() stack frame (#1) but reqsize is 292 in the TclpAlloc()
stack frame (#0)--despite the fact that Tcl_Alloc() just calls TclpAlloc()
and passes its size value in as reqsize, so the two variables should be
the same. Also, there's no hex address printed before stack frame #0, and
there are local variables in TclpAlloc() that gdb was unable to report
("Variable 'size' is not available"). All of which led me to believe there
might be stack corruption going on that's affecting gdb's ability to debug
the crash.
One of the things I've done to try debugging this issue supports that
guess. To test the possibility that the crashes might be caused by a
free'd pointer being accessed, I recompiled Tcl with the free() calls in
TclpFree() and TclFreeAllocCache() disabled (one in the former and two in
the latter). When I did this the crash still occurred--but now the size
and reqsize variables matched, the hex address was printed correctly for
stack frame #0, the size variable within TclpAlloc() was available, etc:
---- 8< -------------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1084729984 (LWP 31246)]
0x0000002a9597a397 in TclpAlloc (reqsize=184) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclThreadAlloc.c:344
344 cachePtr->buckets[bucket].firstPtr = blockPtr->b_next;
(gdb) print size
$1 = 201
(gdb) bt
#0 0x0000002a9597a397 in TclpAlloc (reqsize=184) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclThreadAlloc.c:344
#1 0x0000002a95909697 in Tcl_Alloc (size=184) at /home/tclbuild/rpm/BUILD/tcl8.4.19/unix/../generic/tclCkalloc.c:1002
#2 0x0000002aa39487fb in CRYPTO_malloc () from /usr/local/openssl-64bit/lib/libcrypto.so.0.9.8
#3 0x0000002aa37c87c3 in ssl_cert_dup () from /usr/local/openssl-64bit/lib/libssl.so.0.9.8
[...]
---- 8< -------------------------------------------------
The fact that removing free() calls has any effect at all would seem to
mean that a freed pointer is in fact being referenced somewhere, but the
fact that the crash still occurs means this isn't the only problem.
If you're wondering how to reproduce this crash: unfortunately, so far I
can only reproduce it as part of a complex series of operations within a
larger suite of software above Tcl. I'd love to be able to boil it down
to a simpler test case, but I'm not sure if I can get to that point (given
the number of puts and gets to the memory buckets I don't know how I could
reproduce the required sequence of allocations and frees).
So: can someone offer any pointers on ways to debug this issue? I've
tried --enable-symbols=mem but it didn't produce useful results thus far
(and given the overall complexity of the software stack I can't meet the
requirement that all parts of the stack call the Tcl debugging routines).
I've also tried valgrind, but haven't had much luck there (I haven't had
a chance to try it again since I've been testing the no-free version of
Tcl--that might make it work better). Any and all assistance would be
greatly appreciated.
I do have more information (though these are the critical points), but
this is already too long, so I'll leave it there. Feel free either to
respond here or email me directly (just remove "SPAM" and "AWAY" from
my email address) if you'd like to help or want more info.
- John