我需要一位真正的 C 大师的帮助来分析我的代码中的崩溃。不是为了修复崩溃;我可以很容易地修复它,但在这样做之前,我想了解这种崩溃是如何发生的,因为这对我来说似乎完全不可能。
此崩溃仅发生在客户计算机上,并且我无法在本地重现它(因此我无法使用调试器单步执行代码),因为我无法获取此用户数据库的副本。我的公司也不允许我只更改代码中的几行并为该客户进行自定义构建(因此我无法添加一些 printf 行并让他再次运行代码),当然客户的构建没有调试符号。换句话说,我的调试能力非常有限。尽管如此,我还是可以确定崩溃并获得一些调试信息。但是,当我查看该信息然后查看代码时,我无法理解程序流如何到达有问题的行。代码应该在到达该行之前很久就崩溃了。我完全迷失在这里。
让我们从相关代码开始。这是非常少的代码:
// ... code above skipped, not relevant ...
if (data == NULL) return -1;
information = parseData(data);
if (information == NULL) return -1;
/* Check if name has been correctly \0 terminated */
if (information->kind.name->data[information->kind.name->length] != '\0') {
freeParsedData(information);
return -1;
}
/* Copy the name */
realLength = information->kind.name->length + 1;
*result = malloc(realLength);
if (*result == NULL) {
freeParsedData(information);
return -1;
}
strlcpy(*result, (char *)information->kind.name->data, realLength);
// ... code below skipped, not relevant ...
已经是这样了。它在 strlcpy 中崩溃。我什至可以告诉你 strlcpy 在运行时是如何被真正调用的。strlcpy 实际上是使用以下参数调用的:
strlcpy ( 0x341000, 0x0, 0x1 );
知道这一点,strlcpy 崩溃的原因就很明显了。它试图从 NULL 指针中读取一个字符,这当然会崩溃。而且由于最后一个参数的值是1,所以原来的长度一定是0。我的代码这里显然有一个bug,它没有检查名称数据是否为NULL。我可以解决这个问题,没问题。
我的问题是:
这段代码首先是如何到达 strlcpy 的?
为什么这段代码不会在 if 语句中崩溃?
我在我的机器上本地尝试过:
int main (
int argc,
char ** argv
) {
char * nullString = malloc(10);
free(nullString);
nullString = NULL;
if (nullString[0] != '\0') {
printf("Not terminated\n");
exit(1);
}
printf("Can get past the if-clause\n");
char xxx[10];
strlcpy(xxx, nullString, 1);
return 0;
}
此代码永远不会通过 if 语句。它在 if 语句中崩溃,这绝对是意料之中的。
那么谁能想到为什么如果 name->data 真的为 NULL,第一个代码可以通过该 if 语句而不会崩溃?这对我来说是完全神秘的。这似乎不是确定性的。
重要的额外信息:
两个注释之间的代码真的很完整,没有遗漏任何内容。此外,该应用程序是单线程的,因此没有其他线程可以意外更改后台的任何内存。发生这种情况的平台是 PPC CPU(一个 G4,以防万一)。如果有人想知道“kind.”,这是因为“信息”包含一个名为“kind”的“联合”,而 name 又是一个结构(kind 是一个联合,每个可能的联合值都是不同类型的结构);但这一切在这里都不重要。
我很感激这里的任何想法。如果这不仅仅是一个理论,我会更加感激,如果有一种方法可以验证这个理论是否真的适用于客户。
解决方案
我已经接受了正确的答案,但以防万一有人在 Google 上找到这个问题,这就是真正发生的事情:
指针指向已经被释放的内存。释放内存不会使其全部为零或导致进程立即将其还给系统。因此,即使内存被错误地释放,它也包含正确的值。在执行“ if check ”时,所讨论的指针不为 NULL 。
在那次检查之后,我分配了一些新的内存,调用 malloc。不确定 malloc 在这里究竟做了什么,但每次调用 malloc 或 free 都会对进程的虚拟地址空间的所有动态内存产生深远的影响。在 malloc 调用之后,指针实际上是 NULL。不知何故 malloc (或某些系统调用 malloc 使用)将指针本身所在的已释放内存归零(不是它指向的数据,指针本身位于动态内存中)。将该内存归零,指针现在的值为 0x0,在我的系统上等于 NULL,当调用 strlcpy 时,它当然会崩溃。
所以导致这种奇怪行为的真正错误是在我的代码中完全不同的位置。永远不要忘记:释放的内存会保持它的价值,但它会持续多久是你无法控制的。要检查您的应用程序是否存在访问已释放内存的内存错误,只需确保释放的内存在释放之前始终为零。在 OS X 中,您可以通过在运行时设置环境变量来做到这一点(无需重新编译任何东西)。当然,这会大大减慢程序的速度,但您会更早地发现这些错误。