出于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];
EDIT3 :
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
答案 0 :(得分: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(或者产生合唱线的下水道老鼠唱出“One”的合唱线;因此未定义行为的性质=)。
每个人一直在哄骗subsection[subsecnum]
某种原因的根源,但我向你提出section->
是真正的问题,因为section
是垃圾,section
是垃圾,因为超出范围的诱导数组索引(counter
)的未定义假设就是这样。
那么counter
怎么可能不好?一种方法是并发。如果这确实是一个多线程应用程序,并且counter
是一个变量,以某种方式同时限制多个线程的访问,它根本不受保护。在另一个循环测试之后,一个循环可以将其增加,从而使后者的测试无效。这可能是您认为将NULL检查放在一边避免并发副作用的方法的原因。老实说,我不知道。
但那是我开始寻找的地方。如果未同时使用调试日志,则将counter
转储到调试日志。确保它在范围内。如果同时访问 ,请确保它受到保护。
答案 1 :(得分: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;
ALSO:
“Gdb”,听起来你已经知道,是你的朋友。单步执行循环并在各个点“打印”数组和指针引用以确保一切正常(并保持正常)将无济于事。
... IMHO
答案 2 :(得分:1)
由于你提到'150个线程',我猜你有一个竞争条件 - 一个线程正在修改(可能是释放)section_t而另一个线程正在访问它。这可以解释为什么复制东西会使bug看起来消失 - 这会使竞争孔变得更小。
由于您可以在崩溃时附加调试器,请尝试检查section_t(p *section
)并尝试找出它的外观。
答案 3 :(得分:0)