c – 使用GCC 4.5崩溃编译的程序,而GCC 4.4则没问题

最近我尝试编译并安装ns-2,一个基于C和Tcl的网络模拟器.

使用一些略微修改的源代码(不用担心,它不会导致崩溃),我可以使用最新的gcc 4.5版本进行编译.

但是当我执行二进制文件时,它会给出以下错误:

$bin/ns
*** buffer overflow detected ***: bin/ns terminated

如果用早期的gcc编译,相同的代码运行正常.所以我认为这是由于gcc 4.5中的某些enhanced features.

我该如何处理这个问题?当然用gcc 4.4编译是一个选项,但我想知道出了什么问题:)

更新:

这是使用gdb的完整堆栈跟踪和后跟踪:

$bin/ns
*** buffer overflow detected ***: bin/ns terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(__fortify_fail+0x37)[0x7f01824ac1d7]
/lib/x86_64-linux-gnu/libc.so.6(+0xfd0f0)[0x7f01824ab0f0]
bin/ns[0x8d5b5a]
bin/ns[0x8d56de]
bin/ns[0x841077]
bin/ns[0x842b19]
bin/ns(Tcl_EvalEx+0x16)[0x843256]
bin/ns(Tcl_Eval+0x1d)[0x84327d]
bin/ns(Tcl_GlobalEval+0x2b)[0x84391b]
bin/ns(_ZN3Tcl4evalEPc+0x27)[0x83352b]
bin/ns(_ZN3Tcl5evalcEPKc+0xdd)[0x8334e9]
bin/ns(_ZN11EmbeddedTcl4loadEv+0x24)[0x834712]
bin/ns(Tcl_AppInit+0xb2)[0x8331a5]
bin/ns(Tcl_Main+0x1d0)[0x8ad6a0]
bin/ns(nslibmain+0x25)[0x8330c5]
bin/ns(main+0x20)[0x833254]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xff)[0x7f01823cceff]
bin/ns[0x5bc1a9]

使用GDB并打开符号:

(gdb) bt
#0  0x00007ffff6970d05 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff6974ab6 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007ffff69a9d7b in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007ffff6a3b1d7 in __fortify_fail () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007ffff6a3a0f0 in __chk_fail () from /lib/x86_64-linux-gnu/libc.so.6
#5  0x00000000008d5b5a in strcpy (interp=0xd2dda0, optionIndex=<value optimized out>, objc=<value optimized out>, objv=0x7fffffffdad0)
    at /usr/include/bits/string3.h:105
#6  TraceVariableObjCmd (interp=0xd2dda0, optionIndex=<value optimized out>, objc=<value optimized out>, objv=0x7fffffffdad0)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclTrace.c:912
#7  0x00000000008d56de in Tcl_TraceObjCmd (dummy=<value optimized out>, interp=0xd2dda0, objc=<value optimized out>, objv=0xd2ec00)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclTrace.c:293
#8  0x0000000000841077 in TclEvalObjvInternal (interp=0xd2dda0, objc=5, objv=0xd2ec00,
    command=0x7ffff7f680fe "trace variable defaultRNG w { abort \"cannot update defaultRNG once assigned\"; }\n\n\nClass RandomVariable/TraceDriven -superclass RandomVariable\n\nRandomVariable/TraceDriven instproc init {} {\n$self instv"..., length=80, flags=0)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclBasic.c:3689
#9  0x0000000000842b19 in TclEvalEx (interp=0xd2dda0,
    script=0x7ffff7f52010 "\n\n\n\n\n\nproc warn {msg} {\nglobal warned_\nif {![info exists warned_($msg)]} {\nputs stderr \"warning: $msg\"\nset warned_($msg) 1\n}\n}\n\nif {[info commands debug] == \"\"} {\nproc debug args {\nwarn {Script debugg"..., numBytes=422209, flags=<value optimized out>, line=4141,
    clNextOuter=<value optimized out>,
    outerScript=0x7ffff7f52010 "\n\n\n\n\n\nproc warn {msg} {\nglobal warned_\nif {![info exists warned_($msg)]} {\nputs stderr \"warning: $msg\"\nset warned_($msg) 1\n}\n}\n\nif {[info commands debug] == \"\"} {\nproc debug args {\nwarn {Script debugg"...)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclBasic.c:4386
#10 0x0000000000843256 in Tcl_EvalEx (interp=<value optimized out>, script=<value optimized out>, numBytes=<value optimized out>,
    flags=<value optimized out>) at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclBasic.c:4043
#11 0x000000000084327d in Tcl_Eval (interp=0xd2dda0, script=<value optimized out>)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclBasic.c:4955
#12 0x000000000084391b in Tcl_GlobalEval (interp=0xd2dda0, command=<value optimized out>)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclBasic.c:6005
#13 0x000000000083352b in Tcl::eval(char*) ()
#14 0x00000000008334e9 in Tcl::evalc(char const*) ()
#15 0x0000000000834712 in EmbeddedTcl::load() ()
#16 0x00000000008331a5 in Tcl_AppInit ()
#17 0x00000000008ad6a0 in Tcl_Main (argc=<value optimized out>, argv=0x7fffffffe1d0, appInitProc=0x8330f3 <Tcl_AppInit>)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclMain.c:418
#18 0x00000000008330c5 in nslibmain ()
#19 0x0000000000833254 in main ()    

解决方法:

着名的遗言:“别担心 – 我的改变没有破坏任何东西”.我们怎么能确定呢?

但是,如果代码在4.4下工作并且在4.5以下崩溃,那么你有可能是正确的.

GCC采用了一些与代码相关的积极优化,试图检测整数溢出并将其删除.在这种情况下,您将不得不在ns-2中找到该代码,并尝试通过ns-2开发人员或您自己修复它.

您可能应该尝试在调试器下运行该程序,以便您可以在检测到缓冲区溢出的位置获得控制权,并查看代码的位置.如果您禁用了核心转储(使用ulimit -c 0或等效项),请考虑启用它们,并查看在终止时是否获得核心转储.这应该给你一个起点.

进一步思考:

>编译代码时,警告标志使用的严格程度如何?你可以重新编译并启用更多警告吗?

如果您无法找到其他方法来获取C或C编译器的特殊选项,那么通常可以使用的一种技术(使用AutoTools配置的程序):

./configure --prefix=/opt/ns CC="gcc -Wall -Wextra" CXX="g++ -Wall -Wextra"

(我还使用此技术指定32位与64位版本,添加-m32或-m64.)

警告:如果未在这些选项下创建代码以进行编译清理,则使用这些选项进行第一次编译可能会带来创伤.但是,在所有警告中,还有一个很好的机会是关于问题根源的警告.然而,无可争议的是,任何1(或更糟)的警告可能会有50个与之无关的警告,并且修复因此发现的所有警告仍然可能无法解决问题.如果代码无论如何都会编写严格的警告,那么您将面临启用更多异国情调警告的问题.但是,如果您可以让编译器帮助诊断它所引起的问题,那么您当然应该这样做 – 它比单独找到问题简单得多.
>此外,确保您正在生成可调试程序 – 即使您启用了优化.
>另外,考虑编译优化关闭并查看程序是否仍然崩溃.如果程序在没有优化的情况下没有崩溃并且优化,那么您将获得一些有用的信息.它不会更容易找到原因,但你知道它(可能)与优化器相关.或者它可能只是在没有优化时错误移动并且不会致命地失败.

扩展堆栈跟踪信息很奇怪:

#5  0x00000000008d5b5a in strcpy (interp=0xd2dda0, optionIndex=<value optimized out>,
                                  objc=<value optimized out>, objv=0x7fffffffdad0)
    at /usr/include/bits/string3.h:105
#6  TraceVariableObjCmd (interp=0xd2dda0, optionIndex=<value optimized out>,
                         objc=<value optimized out>, objv=0x7fffffffdad0)
    at /media/Linux/ns-allinone-2.35-RC7/tcl8.5.8/unix/../generic/tclTrace.c:912

这些不是strcpy()的普通参数.通常,您只有两个参数.我不能立即想到一个适合将字符串复制到指向Tcl解释器主控制结构的指针的情况.所以,为了更进一步,我会非常认真地看待tclTrace.c中的第900-920行,特别是第912行.这可能只是优化器挖掘目标代码的方式的假象,或者它可能是一个真正的问题.

我发现tcl8.5.8源代码和tclTrace.c的第912行是此代码中的strcpy():

    if ((enum traceOptions) optionIndex == TRACE_ADD) {
        CombinedTraceVarInfo *ctvarPtr;

        ctvarPtr = (CombinedTraceVarInfo *) ckalloc((unsigned)
                (sizeof(CombinedTraceVarInfo) + length + 1
                - sizeof(ctvarPtr->traceCmdInfo.command)));
        ctvarPtr->traceCmdInfo.flags = flags;
        if (objv[0] == NULL) {
            ctvarPtr->traceCmdInfo.flags |= TCL_TRACE_OLD_STYLE;
        }
        ctvarPtr->traceCmdInfo.length = length;
        flags |= TCL_TRACE_UNSETS | TCL_TRACE_RESULT_OBJECT;
        strcpy(ctvarPtr->traceCmdInfo.command, command);       // Line 912
        ctvarPtr->traceInfo.traceProc = TraceVarProc;
        ctvarPtr->traceInfo.clientData = (ClientData)
                &ctvarPtr->traceCmdInfo;
        ctvarPtr->traceInfo.flags = flags;
        name = Tcl_GetString(objv[3]);
        if (TraceVarEx(interp,name,NULL,(VarTrace*)ctvarPtr) != TCL_OK) {
            ckfree((char *) ctvarPtr);
            return TCL_ERROR;
        }
    } else {

因此,GDB的输出和堆栈跟踪看起来有些误导;有两个变量传递给strcpy(),其中一个在堆上本地分配.

我想考虑从嵌入了ns-2的源代码编译tcl独立版本,看看你是否可以自己解决这个bug(抱歉,可怕的双关语).此代码与跟踪tcl变量 – trace add varname … AFAICT有关.

假设通过,我会考虑获取GCC 4.6并查看在使用它而不是GCC 4.5编译ns-2时是否会出现同样的问题.

Valgrind的

由于您在Linux上运行,因此您应该能够使用Valgrind.它非常适合发现内存滥用问题.为获得最大收益,请使用ns-2的调试版本.

上一篇:用Tkinter打造GUI开发工具(42)Tkinter与TCL/Tk混合编程


下一篇:10.20字符串自闭赛