2

由于 IP 原因,我无法发布实际代码,但要点如下:

 ...
 double valueA = 0.0;
 double valueB = 0.0;
 section_t * section = &some_global_table[counter].section;
 if (NULL == section) continue;
 else
 {
      for (subsecnum = 0; subsecnum < section->entries; subsecnum++)
      {
          valueA = (double) section->subsection[subsecnum].value //CRASHES HERE
          valueB = (double) section->subsection[subsecnum+1].value; // subsecnum + 1 is a valid entry
          ...//do something with values//...
      }
 }
 ...

上述代码被多次调用,具体取决于所需的部分,

最近我正在使用 jmeter 对我们的应用程序进行压力测试 - 连续循环上的 150 个线程(它是一个服务器应用程序),它崩溃了(SIGSEGV)。通过 GDB 运行它会将我指向标记为 的行//CRASHES HERE。之后我通过 GDB 运行了几次,它总是在同一点崩溃。

但是:它并不总是在表中的值上崩溃。例如,它第一次崩溃时:

counter = 2
subsecnum = 21

第二次崩溃:

counter = 19
subsecnum = 10

等等...

我已经检查并仔细检查了越界错误的值,但事实并非如此。这些值都是有效的。

注意:我发现如果我实际上将整个复制some_global_table[counter].section到缓冲区而不是仅使用指针,则不会发生崩溃。但是,即使在读取部分周围使用互斥锁也不起作用......

非常感谢任何帮助,如果需要更多详细信息,请告诉我。

编辑:全局表在开始时加载,之后的任何时候都不会更改,因此section->entries一旦加载数据,特定部分的值将始终相同。

EDIT2:section_t 的结构

 typedef struct
 {
     int entries;
     subsection_t * subsections;
 } section_t;

 typedef struct
 {
     int value;
     char title[MAX_LEN_TITLE];
 } subsection_t;

 typedef struct
 {
     char bookname[MAX_LEN_BOOK_TITLE];
     FILE * bookfile;
     section_t section;
 } global_table_t;

 global_table_t some_global_table[MAX_TABLES];

编辑3

 Dump of assembler code from 0x4132a1 to 0x413321:
    0x00000000004132a1 <myfunc+389>:    roll   0x0(%rcx)
    0x00000000004132a4 <myfunc+392>:    mov    $0x0,%eax
    0x00000000004132a9 <myfunc+397>:    callq  0x408382 <log>
    0x00000000004132ae <myfunc+402>:    jmpq   0x413517 <myfunc+1019>
    0x00000000004132b3 <myfunc+407>:    mov    -0x68(%rbp),%rax
    0x00000000004132b7 <myfunc+411>:    mov    (%rax),%rax
    0x00000000004132ba <myfunc+414>:    sub    $0x1,%eax
    0x00000000004132bd <myfunc+417>:    mov    %eax,-0xc(%rbp)
    0x00000000004132c0 <myfunc+420>:    movl   $0x0,-0x5c(%rbp)
    0x00000000004132c7 <myfunc+427>:    jmpq   0x413505 <myfunc+1001>
    0x00000000004132cc <myfunc+432>:    mov    -0x68(%rbp),%rax
    0x00000000004132d0 <myfunc+436>:    mov    0x10(%rax),%rdx
    0x00000000004132d4 <myfunc+440>:    mov    -0x5c(%rbp),%eax
    0x00000000004132d7 <myfunc+443>:    cltq   
    0x00000000004132d9 <myfunc+445>:    shl    $0x4,%rax
    0x00000000004132dd <myfunc+449>:    lea    (%rdx,%rax,1),%rax
 => 0x00000000004132e1 <myfunc+453>:    mov    0x8(%rax),%eax
    0x00000000004132e4 <myfunc+456>:    mov    %eax,-0x8(%rbp)
    0x00000000004132e7 <myfunc+459>:    mov    -0x68(%rbp),%rax
    0x00000000004132eb <myfunc+463>:    mov    0x10(%rax),%rax
    0x00000000004132ef <myfunc+467>:    lea    0x10(%rax),%rdx
    0x00000000004132f3 <myfunc+471>:    mov    -0x5c(%rbp),%eax
    0x00000000004132f6 <myfunc+474>:    cltq   
    0x00000000004132f8 <myfunc+476>:    shl    $0x4,%rax
    0x00000000004132fc <myfunc+480>:    lea    (%rdx,%rax,1),%rax
    0x0000000000413300 <myfunc+484>:    mov    0x8(%rax),%eax
    0x0000000000413303 <myfunc+487>:    mov    %eax,-0x4(%rbp)
    0x0000000000413306 <myfunc+490>:    cvtsi2sdl -0x8(%rbp),%xmm0
    0x000000000041330b <myfunc+495>:    movsd  %xmm0,-0x50(%rbp)
    0x0000000000413310 <myfunc+500>:    cvtsi2sdl -0x4(%rbp),%xmm0
    0x0000000000413315 <myfunc+505>:    movsd  %xmm0,-0x40(%rbp)
    0x000000000041331a <myfunc+510>:    mov    -0x68(%rbp),%rax
    0x000000000041331e <myfunc+514>:    mov    0x10(%rax),%rdx

    rax            0xa80    2688
    rbx            0x7fffc03f9710   140736418780944
    rcx            0x4066c00000000000   4640607572284407808
    rdx            0x0  0
    rsi            0xfffff00000000  4503595332403200
    rdi            0x7fffc039e8f0   140736418408688
    rbp            0x7fffc039e9f0   0x7fffc039e9f0
    rsp            0x7fffc039e950   0x7fffc039e950
    r8             0x13 19
    r9             0x1  1
    r10            0x9  9
    r11            0x7fffc039e848   140736418408520
    r12            0x7fffedd86d60   140737183772000
    r13            0x7fffc03f99d0   140736418781648
    r14            0x4  4
    r15            0x7  7
    rip            0x4132e1 0x4132e1 <myfunc+453>
    eflags         0x10202  [ IF RF ]
    cs             0x33 51
    ss             0x2b 43
    ds             0x0  0
    es             0x0  0
    fs             0x0  0
    gs             0x0  0
4

4 回答 4

4

我的猜想,是的,这是一个延伸,它不一定是错误的小节;随之而来的是counter论点和随后的取消引用。你有一个counter循环遍历我们希望是你的全局表的东西。人们希望它不超过MAX_TABLES-1,因为这样做会引入未定义的行为。尽管您的示例不包含循环,但我只能假设它看起来像这样:

size_t counter=0;
for (;counter < some_upper_limit; ++counter)
{
    double valueA = 0.0;
    double valueB = 0.0;
    section_t * section = &some_global_table[counter].section;
    if (NULL == section)
        continue;
    else
    {
        for (subsecnum = 0; subsecnum < section->entries; subsecnum++)
        {
            valueA = (double) section->subsection[subsecnum].value //CRASHES HERE
            valueB = (double) section->subsection[subsecnum+1].value; // subsecnum + 1 is a valid entry
          ...//do something with values//...
        }
    }
}

注意检查 NULL 吗?问题是,为什么要在这些结构的固定全局数组中获取结构的固定成员的地址,然后针对 NULL “验证”它?

从字面上看,您假设如果counter索引不在其中,[0..MAX_TABLES-1]那么使用该索引取消引用的数组中保存的结构的地址将以某种方式为 NULL。这不能保证。您引用的内存可能是“有效的”,但肯定没有定义。

因此,您现在正在携带一个完全非法的指针,一旦它被取消引用,它可能会立即变为 kerboom(或产生一条下水道老鼠的合唱线,唱着“一”的合唱;因此未定义行为的性质 =)。

每个人都在哄骗这个想法,这subsection[subsecnum]在某种程度上是造成这种情况的根源,但我向你提出,这才section->是真正的问题,因为section它是垃圾,而且section是垃圾,因为一个超出范围的诱导数组索引的未定义假设( counter) 做到了。

那么怎么可能counter不好呢?一种方法是并发。如果这确实是一个多线程应用程序,并且counter是一个变量,它以某种方式被多个线程同时访问,那么它根本不受保护。一个循环可以在另一个循环测试它之后增加它,从而使后者的测试无效。这可能正是您认为将 NULL-check 放入是规避这种并发副作用的一种方式的原因。老实说,我不知道。

但那是我开始寻找的地方。counter如果未同时使用,则转储到调试日志。确保它在范围内。如果它并发访问,请确保它受到保护。

于 2013-05-01T21:03:30.347 回答
3

我完全同意 WhozCraig。此外:

// OK...
for (subsecnum = 0; subsecnum < section->entries; subsecnum++) {
  // Also OK (provided "section" and "subsection" are both allocated and initialized)
  valueA = (double) section->subsection[subsecnum].value //CRASHES HERE
  // Are you *sure* "subsecnum + 1" is a valid entry?
  valueB = (double) section->subsection[subsecnum+1].value;

还:

“Gdb”,就像你已经知道的那样,是你的朋友。单步执行循环并在各个点“打印”数组和指针引用以确保一切正常(并保持正常),这无济于事。

恕我直言...

于 2013-05-01T21:17:55.627 回答
1

既然你提到了“150 个线程”,我猜你有一个竞争条件——一个线程正在修改(也许释放)section_t,而另一个线程正在访问它。这可以解释为什么复制东西会使错误看起来消失 - 这使得比赛漏洞变得更小。

由于您可以在崩溃时附加调试器,因此请尝试检查 section_t ( p *section) 并尝试找出它的外观。

于 2013-05-02T03:22:19.393 回答
0

没有完整的上下文,很难说。我建议做的一件事是在Valgrind下运行您的服务器程序,以检查您是否确实遇到了内存溢出。由于您正在进行数组访问,我怀疑那里有问题。正如评论者所指出的,我怀疑它与铸造有任何问题。

于 2013-05-01T19:46:41.107 回答