TCL 正则表达式是否跨解释器共享 - TclReExec 程序以信号 11,分段错误终止?
Are TCL Regular Expressions Shared Across Interpreters - TclReExec Program terminated with signal 11, Segmentation fault?
在单个EXE进程中,Tcl_CreateInterp
返回的每个解释器实例是否共享TCL正则表达式?具有 4 个不同解释器实例 (0x94fbcd8,0x94dff20,0x94c4170,0x94a8760) 的线程怎么会像 TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
?
这样调用
TCL 手册中的这条评论暗示对象可能会被共享...
Tcl objects are allocated on the heap and are shared as much as possible to
reduce storage requirements. Reference counting is used to determine when an
object is no longer needed and can safely be freed.
我们在 32 位服务器应用程序中遇到崩溃。我们已经将根本原因隔离到线程之间并发共享的 TCL 正则表达式 运行ning 在单独的 TCL 解释器实例中。
解释器在 TCL 的这一行上失败
regsub "\*" $s "\*" s
应用程序并发运行s TCL 8.4.11 解释器实例。每个解释器都在单独的线程中执行 "user TCL scripts"。该应用程序创建的线程 "own" 1 个使用 Tcl_CreateInterp
创建的解释器实例。然后每个线程告诉解释器实例 运行 一个 "user TCL script" 和 Tcl_EvalObjv
。当每个解释器都配置为 运行 在包含上面显示的 regsub
的行上相同的 "user TCL script" 时,就会发生崩溃。
此应用 运行15 年来已在数十种不同的生产环境中运行。在当前环境中,应用程序 运行ning on Red Hat Linux 6.5 64-bit.
核心转储看起来像...
Program terminated with signal 11, Segmentation fault.
#0 0x0811c020 in miss ()
(gdb) bt
#0 0x0811c020 in miss ()
#1 0x0811b7ed in shortest ()
#2 0x0811a4fa in find ()
#3 0x0811a429 in TclReExec ()
#4 0x080fc83f in RegExpExecUniChar ()
#5 0x080fc970 in Tcl_RegExpExecObj ()
#6 0x080bb9f1 in Tcl_RegsubObjCmd ()
#7 0x080b027a in TclEvalObjvInternal ()
#8 0x080d2726 in TclExecuteByteCode ()
#9 0x080d1bd1 in TclCompEvalObj ()
#10 0x080fbd6c in TclObjInterpProc ()
#11 0x080b027a in TclEvalObjvInternal ()
#12 0x080d2726 in TclExecuteByteCode ()
#13 0x080d1bd1 in TclCompEvalObj ()
#14 0x080fbd6c in TclObjInterpProc ()
#15 0x080b027a in TclEvalObjvInternal ()
#16 0x080b0527 in Tcl_EvalObjv ()
使用带有编译标志 --enable-symbols=mem 并与 D.U.M.A 链接的 TCL 版本重新编译应用程序后。 - 检测意外的内存访问 http://duma.sourceforge.net/(Electric Fence 的一个分支,用于帮助捕获超过 运行s 的缓冲区),我得到一个像
这样的核心转储
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xea8eeb70 (LWP 31004)]
0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
52 (*((struct fns *)re->re_fns)->free)(re);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6_5.2.i686
(gdb) list
47 regfree(re)
48 regex_t *re;
49 {
50 if (re == NULL)
51 return;
52 (*((struct fns *)re->re_fns)->free)(re);
53 }
(gdb) bt
#0 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
#1 0x08124360 in FreeRegexp (regexpPtr=0x86b0440) at ./../generic/tclRegexp.c:989
#2 0x08123ec2 in FreeRegexpInternalRep (objPtr=0xf64041b8) at ./../generic/tclRegexp.c:746
#3 0x08128cab in SetStringFromAny (interp=0x0, objPtr=0xf64041b8) at ./../generic/tclStringObj.c:1762
#4 0x08127894 in Tcl_GetUnicodeFromObj (objPtr=0xf64041b8, lengthPtr=0xea8ecee8) at ./../generic/tclStringObj.c:567
#5 0x080c3e9a in Tcl_RegsubObjCmd (dummy=0x0, interp=0x94fbcd8, objc=4, objv=0x94fbf28) at ./../generic/tclCmdMZ.c:718
#6 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=5, objv=0x94fbf24, command=0x0, length=0, flags=0) at ./../generic/tclBasic.c:3088
#7 0x080e5a88 in TclExecuteByteCode (interp=0x94fbcd8, codePtr=0x95174e0) at ./../generic/tclExecute.c:1417
#8 0x080e4959 in TclCompEvalObj (interp=0x94fbcd8, objPtr=0x95097f0) at ./../generic/tclExecute.c:981
#9 0x08122a35 in TclObjInterpProc (clientData=0x9514520, interp=0x94fbcd8, objc=2, objv=0x94fbf1c) at ./../generic/tclProc.c:1100
#10 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=2, objv=0x94fbf1c, command=0x0, length=0, flags=0) at ./../generic/tclBasic.c:3088
#11 0x080e5a88 in TclExecuteByteCode (interp=0x94fbcd8, codePtr=0xf64011f8) at ./../generic/tclExecute.c:1417
#12 0x080e4959 in TclCompEvalObj (interp=0x94fbcd8, objPtr=0x9513f68) at ./../generic/tclExecute.c:981
#13 0x08122a35 in TclObjInterpProc (clientData=0x9514d10, interp=0x94fbcd8, objc=2, objv=0xea8ee34c) at ./../generic/tclProc.c:1100
#14 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=2, objv=0xea8ee34c, command=0x81a4ffe "", length=0, flags=0) at ./../generic/tclBasic.c:3088
#15 0x080b15e4 in Tcl_EvalObjv (interp=0x94fbcd8, objc=2, objv=0xea8ee34c, flags=0) at ./../generic/tclBasic.c:3204
#16 0x0808a812 in run_tcl_proc (pDevice=0x82405e0, pInterp=0x830d340, iNumArgs=2, objv=0xea8ee34c, bIsCommand=0 '[=12=]0', pCommand=0x0)
#17 0x08093492 in Tcl_begin_next_state (pDevice=0x82405e0, iNextState=RunPoll, pCommand=0x0)
#18 0x08093579 in Tcl_port_thread (dummy=0x8232c00)
#19 0x0014fb39 in start_thread () from /lib/libpthread.so.0
#20 0x00967d7e in clone () from /lib/libc.so.6
(gdb)
这个 gdb 会话还清楚地显示并发线程在同一正则表达式上执行 regfree,即使每个线程的 TCL 解释器实例完全是线程绑定的。线程之间应该有零共享。它们唯一的共同点是它们正在执行具有相同文件名的 "user TCL script" 文件。这些文件都用 Tcl_EvalFile
加载到每线程解释器实例中。
(gdb) info threads
45 Thread 0xe30e2b70 (LWP 31017) 0x00110430 in __kernel_vsyscall ()
--snip--
34 Thread 0xe9eedb70 (LWP 31005) 0x00110430 in __kernel_vsyscall ()
* 33 Thread 0xea8eeb70 (LWP 31004) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
32 Thread 0xeb2efb70 (LWP 31003) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
31 Thread 0xebcf0b70 (LWP 31002) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
30 Thread 0xec6f1b70 (LWP 31001) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
29 Thread 0xed0f2b70 (LWP 31000) 0x00110430 in __kernel_vsyscall ()
--snip--
1 Thread 0xf7fec8d0 (LWP 30970) 0x00110430 in __kernel_vsyscall ()
(gdb)
请注意,这个问题与我之前的问题完全不同 。
在深入研究了应用程序的代码后,我发现了这样一种情况:在线程 A 中创建了一个解释器并要求 运行 一个过程,但随后在线程 B 中用于 运行 许多其他过程。我猜这可能是这次崩溃的根本原因。奇怪的是,该应用程序不会在 Windows 时崩溃,但会在 Linux 时立即崩溃(大部分时间)。该应用程序创建线程:
- 在 Windows 上,使用 Win32 API。
- 在 Linux 上,使用 POSIX 线程/pthread。
为了回答您眼前的问题,RE 由两种机制共享。首先,它们绑定到从脚本中的值生成的 Tcl_Obj 值的内部表示(例如,文字和操作结果)。其次,它们还存储在大小有界的 per-thread LRU 缓存中。
这两种机制都是严格线程绑定的。 RE 不在线程之间共享; Tcl 在线程之间共享极少。
但是,您的问题中存在一些较大的问题。
如果您要在执行的线程之间发送消息(错误,脚本),强烈建议您为此使用 Thread 扩展,因为它会注意复制需要复制的内容。 Thread 扩展附带了 Tcl 8.6 的完整分发版(它现在是一个贡献包,连同 [incr Tcl]、SQLite 和 TDBC)但它应该单独用于旧版本的 Tcl。
此外,您使用的是双重不受支持的 Tcl 版本。 8.4 的最新版本是 8.4.20(它应该是一个直接替代品),甚至 security/build 支持它已经有好几年了。建议您真的升级。 8.5.17 是当前的长期支持版本,8.6.3 是当前的生产版本。 (它们在很多代码上也快了很多。)
在单个EXE进程中,Tcl_CreateInterp
返回的每个解释器实例是否共享TCL正则表达式?具有 4 个不同解释器实例 (0x94fbcd8,0x94dff20,0x94c4170,0x94a8760) 的线程怎么会像 TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
?
TCL 手册中的这条评论暗示对象可能会被共享...
Tcl objects are allocated on the heap and are shared as much as possible to reduce storage requirements. Reference counting is used to determine when an object is no longer needed and can safely be freed.
我们在 32 位服务器应用程序中遇到崩溃。我们已经将根本原因隔离到线程之间并发共享的 TCL 正则表达式 运行ning 在单独的 TCL 解释器实例中。
解释器在 TCL 的这一行上失败
regsub "\*" $s "\*" s
应用程序并发运行s TCL 8.4.11 解释器实例。每个解释器都在单独的线程中执行 "user TCL scripts"。该应用程序创建的线程 "own" 1 个使用 Tcl_CreateInterp
创建的解释器实例。然后每个线程告诉解释器实例 运行 一个 "user TCL script" 和 Tcl_EvalObjv
。当每个解释器都配置为 运行 在包含上面显示的 regsub
的行上相同的 "user TCL script" 时,就会发生崩溃。
此应用 运行15 年来已在数十种不同的生产环境中运行。在当前环境中,应用程序 运行ning on Red Hat Linux 6.5 64-bit.
核心转储看起来像...
Program terminated with signal 11, Segmentation fault.
#0 0x0811c020 in miss ()
(gdb) bt
#0 0x0811c020 in miss ()
#1 0x0811b7ed in shortest ()
#2 0x0811a4fa in find ()
#3 0x0811a429 in TclReExec ()
#4 0x080fc83f in RegExpExecUniChar ()
#5 0x080fc970 in Tcl_RegExpExecObj ()
#6 0x080bb9f1 in Tcl_RegsubObjCmd ()
#7 0x080b027a in TclEvalObjvInternal ()
#8 0x080d2726 in TclExecuteByteCode ()
#9 0x080d1bd1 in TclCompEvalObj ()
#10 0x080fbd6c in TclObjInterpProc ()
#11 0x080b027a in TclEvalObjvInternal ()
#12 0x080d2726 in TclExecuteByteCode ()
#13 0x080d1bd1 in TclCompEvalObj ()
#14 0x080fbd6c in TclObjInterpProc ()
#15 0x080b027a in TclEvalObjvInternal ()
#16 0x080b0527 in Tcl_EvalObjv ()
使用带有编译标志 --enable-symbols=mem 并与 D.U.M.A 链接的 TCL 版本重新编译应用程序后。 - 检测意外的内存访问 http://duma.sourceforge.net/(Electric Fence 的一个分支,用于帮助捕获超过 运行s 的缓冲区),我得到一个像
这样的核心转储Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xea8eeb70 (LWP 31004)]
0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
52 (*((struct fns *)re->re_fns)->free)(re);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6_5.2.i686
(gdb) list
47 regfree(re)
48 regex_t *re;
49 {
50 if (re == NULL)
51 return;
52 (*((struct fns *)re->re_fns)->free)(re);
53 }
(gdb) bt
#0 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
#1 0x08124360 in FreeRegexp (regexpPtr=0x86b0440) at ./../generic/tclRegexp.c:989
#2 0x08123ec2 in FreeRegexpInternalRep (objPtr=0xf64041b8) at ./../generic/tclRegexp.c:746
#3 0x08128cab in SetStringFromAny (interp=0x0, objPtr=0xf64041b8) at ./../generic/tclStringObj.c:1762
#4 0x08127894 in Tcl_GetUnicodeFromObj (objPtr=0xf64041b8, lengthPtr=0xea8ecee8) at ./../generic/tclStringObj.c:567
#5 0x080c3e9a in Tcl_RegsubObjCmd (dummy=0x0, interp=0x94fbcd8, objc=4, objv=0x94fbf28) at ./../generic/tclCmdMZ.c:718
#6 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=5, objv=0x94fbf24, command=0x0, length=0, flags=0) at ./../generic/tclBasic.c:3088
#7 0x080e5a88 in TclExecuteByteCode (interp=0x94fbcd8, codePtr=0x95174e0) at ./../generic/tclExecute.c:1417
#8 0x080e4959 in TclCompEvalObj (interp=0x94fbcd8, objPtr=0x95097f0) at ./../generic/tclExecute.c:981
#9 0x08122a35 in TclObjInterpProc (clientData=0x9514520, interp=0x94fbcd8, objc=2, objv=0x94fbf1c) at ./../generic/tclProc.c:1100
#10 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=2, objv=0x94fbf1c, command=0x0, length=0, flags=0) at ./../generic/tclBasic.c:3088
#11 0x080e5a88 in TclExecuteByteCode (interp=0x94fbcd8, codePtr=0xf64011f8) at ./../generic/tclExecute.c:1417
#12 0x080e4959 in TclCompEvalObj (interp=0x94fbcd8, objPtr=0x9513f68) at ./../generic/tclExecute.c:981
#13 0x08122a35 in TclObjInterpProc (clientData=0x9514d10, interp=0x94fbcd8, objc=2, objv=0xea8ee34c) at ./../generic/tclProc.c:1100
#14 0x080b1386 in TclEvalObjvInternal (interp=0x94fbcd8, objc=2, objv=0xea8ee34c, command=0x81a4ffe "", length=0, flags=0) at ./../generic/tclBasic.c:3088
#15 0x080b15e4 in Tcl_EvalObjv (interp=0x94fbcd8, objc=2, objv=0xea8ee34c, flags=0) at ./../generic/tclBasic.c:3204
#16 0x0808a812 in run_tcl_proc (pDevice=0x82405e0, pInterp=0x830d340, iNumArgs=2, objv=0xea8ee34c, bIsCommand=0 '[=12=]0', pCommand=0x0)
#17 0x08093492 in Tcl_begin_next_state (pDevice=0x82405e0, iNextState=RunPoll, pCommand=0x0)
#18 0x08093579 in Tcl_port_thread (dummy=0x8232c00)
#19 0x0014fb39 in start_thread () from /lib/libpthread.so.0
#20 0x00967d7e in clone () from /lib/libc.so.6
(gdb)
这个 gdb 会话还清楚地显示并发线程在同一正则表达式上执行 regfree,即使每个线程的 TCL 解释器实例完全是线程绑定的。线程之间应该有零共享。它们唯一的共同点是它们正在执行具有相同文件名的 "user TCL script" 文件。这些文件都用 Tcl_EvalFile
加载到每线程解释器实例中。
(gdb) info threads
45 Thread 0xe30e2b70 (LWP 31017) 0x00110430 in __kernel_vsyscall ()
--snip--
34 Thread 0xe9eedb70 (LWP 31005) 0x00110430 in __kernel_vsyscall ()
* 33 Thread 0xea8eeb70 (LWP 31004) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
32 Thread 0xeb2efb70 (LWP 31003) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
31 Thread 0xebcf0b70 (LWP 31002) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
30 Thread 0xec6f1b70 (LWP 31001) 0x08151496 in TclReFree (re=0x86b0444) at ./../generic/regfree.c:52
29 Thread 0xed0f2b70 (LWP 31000) 0x00110430 in __kernel_vsyscall ()
--snip--
1 Thread 0xf7fec8d0 (LWP 30970) 0x00110430 in __kernel_vsyscall ()
(gdb)
请注意,这个问题与我之前的问题完全不同
在深入研究了应用程序的代码后,我发现了这样一种情况:在线程 A 中创建了一个解释器并要求 运行 一个过程,但随后在线程 B 中用于 运行 许多其他过程。我猜这可能是这次崩溃的根本原因。奇怪的是,该应用程序不会在 Windows 时崩溃,但会在 Linux 时立即崩溃(大部分时间)。该应用程序创建线程:
- 在 Windows 上,使用 Win32 API。
- 在 Linux 上,使用 POSIX 线程/pthread。
为了回答您眼前的问题,RE 由两种机制共享。首先,它们绑定到从脚本中的值生成的 Tcl_Obj 值的内部表示(例如,文字和操作结果)。其次,它们还存储在大小有界的 per-thread LRU 缓存中。
这两种机制都是严格线程绑定的。 RE 不在线程之间共享; Tcl 在线程之间共享极少。
但是,您的问题中存在一些较大的问题。
如果您要在执行的线程之间发送消息(错误,脚本),强烈建议您为此使用 Thread 扩展,因为它会注意复制需要复制的内容。 Thread 扩展附带了 Tcl 8.6 的完整分发版(它现在是一个贡献包,连同 [incr Tcl]、SQLite 和 TDBC)但它应该单独用于旧版本的 Tcl。
此外,您使用的是双重不受支持的 Tcl 版本。 8.4 的最新版本是 8.4.20(它应该是一个直接替代品),甚至 security/build 支持它已经有好几年了。建议您真的升级。 8.5.17 是当前的长期支持版本,8.6.3 是当前的生产版本。 (它们在很多代码上也快了很多。)