C 防止字符串操作崩溃导致整个应用程序崩溃

C 防止字符串操作崩溃导致整个应用程序崩溃,c,C,我创建了一个程序,定期从网站下载一个csv格式的文本文件,并对其进行解析,提取相关数据,然后显示出来 我注意到,偶尔,每隔几个月左右,它就会崩溃。考虑到数据下载和解析的周期可能每5分钟甚至更短时间发生一次,崩溃是罕见的。我很确定它在解析字符串和提取数据的函数中崩溃了。当它崩溃时,它发生在拥挤的互联网连接期间,即下载量大和/或连接速度慢。有时远程站点可能正在处理损坏或不完整的数据 我使用了一个测试应用程序,它在处理数据之前保存要处理的数据,当崩溃发生时,它确实显示数据不完整 我已经对函数进行了调整

我创建了一个程序,定期从网站下载一个csv格式的文本文件,并对其进行解析,提取相关数据,然后显示出来

我注意到,偶尔,每隔几个月左右,它就会崩溃。考虑到数据下载和解析的周期可能每5分钟甚至更短时间发生一次,崩溃是罕见的。我很确定它在解析字符串和提取数据的函数中崩溃了。当它崩溃时,它发生在拥挤的互联网连接期间,即下载量大和/或连接速度慢。有时远程站点可能正在处理损坏或不完整的数据

我使用了一个测试应用程序,它在处理数据之前保存要处理的数据,当崩溃发生时,它确实显示数据不完整

我已经对函数进行了调整,以适应一些无效或不完整数据的情况,并检查所有返回值。我还检查用于连接远程站点和下载数据的各种函数的返回值。当返回值指示没有成功时,将不再进一步

该函数的核心使用strep遍历数据并从中提取信息:

/ *
  * delimiters typically contains: <;>, <">, < >
  * strsep() is used to split part of the string using delimiter
  * and copy into token which then is copied into the array
  * normally the function stops way before ARRAYSIZE which is just a safeguard
  * it would normally stop when the end of file is reached, i.e. \0
  */
for(n=0;n<ARRAYSIZE;n++)
{
  token=strsep(&copy_of_downloaded_data, delimiters);
  if (token==NULL)
    break;

  data->array[n].example=strndup(token, strlen(token));

  if (data->array[n].example!=NULL)
  {
    token=strsep(&copy_of_downloaded_data, delimiters);
    if (token==NULL)
      break;

  (..)

  copy_of_downloaded_data=strchr(copy_of_downloaded_data,'\n'); /* find newline */
  if (copy_of_downloaded_data==NULL)
    break; 

  copy_of_downloaded_data=copy_of_downloaded_data+1;
  if (copy_of_downloaded_data=='\0') /* find end of text */
    break;
}
由于我怀疑我无法解释数据可能被破坏的所有方式,我想知道是否有办法对此进行编程,以便在数据被破坏的情况下,函数在运行时不会使整个应用程序崩溃

如果这是不可能的,我可以做什么,使它更强大

编辑:崩溃的一个可能实例是当数据突然结束时,字段的中间被截断,即

例如,这个数据是brok


至少通过查看保存的数据我注意到了这一点,但是我发现它并不一致。必须按照下面的建议对其进行压力测试。

最好的办法是找出是什么输入导致函数崩溃,并修复函数,使其不会崩溃。由于函数正在进行字符串处理,因此可以通过向其提供大量虚拟/测试数据,或者如果是导致崩溃的特定输入,则向其提供正确的测试数据。你基本上想测试这个函数,直到你发现如何让它按需崩溃;在这一点上,您可以开始调查它崩溃的确切位置和原因,一旦您了解了这一点,修复崩溃所需的更改可能会对您来说变得显而易见

在valgrind下运行该程序也可能会指出错误所在


如果由于某种原因无法修复错误,另一种选择是生成一个子进程并在子进程内运行错误代码。这样,如果它崩溃,只有子进程丢失,而不是父进程。您可以通过调用fork在大多数操作系统下生成子进程;当然,您需要想出一些方法让子进程将其结果反馈给父进程。请注意,这样做是一个困难的过程,可能效率不高,而且如果有能够发送您的程序输入的恶意用户能够找出如何操纵错误以控制子进程,则还可能在您的应用程序中引入安全漏洞-因此我不推荐这种方法

最好的办法是找出是什么输入导致函数崩溃,并修复函数,使其不会崩溃。由于函数正在进行字符串处理,因此可以通过向其提供大量虚拟/测试数据,或者如果是导致崩溃的特定输入,则向其提供正确的测试数据。你基本上想测试这个函数,直到你发现如何让它按需崩溃;在这一点上,您可以开始调查它崩溃的确切位置和原因,一旦您了解了这一点,修复崩溃所需的更改可能会对您来说变得显而易见

在valgrind下运行该程序也可能会指出错误所在


如果由于某种原因无法修复错误,另一种选择是生成一个子进程并在子进程内运行错误代码。这样,如果它崩溃,只有子进程丢失,而不是父进程。您可以通过调用fork在大多数操作系统下生成子进程;当然,您需要想出一些方法让子进程将其结果反馈给父进程。请注意,这样做是一个困难的过程,可能效率不高,而且如果有能够发送您的程序输入的恶意用户能够找出如何操纵错误以控制子进程,则还可能在您的应用程序中引入安全漏洞-因此我不推荐这种方法

coredump指向什么

strep-没有内存同步机制,所以在执行strep时将其作为关键部分进行保护并锁定它

看看strep是否能帮上忙 在这一点上,一大块ARRAYSIZE帮不了你


接收已下载数据副本的线程/程序的堆栈大小我知道您只是在引用它,所以请查看接收它的函数

coredump指向什么

strep-没有内存同步机制,所以在执行strep时将其作为关键部分进行保护并锁定它

看看strep能不能处理一大块ARRAYSIZE在这里帮不了你


接收已下载数据副本的线程/程序的堆栈大小我知道您只是在引用它,所以请查看接收它的函数

我建议您应该尝试编写代码,有意识地跟踪字符串长度,而不关心字符串是否以零结尾。尽管空指针被称为数十亿美元的错误,但我认为以零结尾的字符串要糟糕得多。虽然在某些情况下,使用以零结尾的字符串的代码可能比跟踪字符串长度的代码更简单,但要确保没有任何东西会导致字符串处理代码超出缓冲区边界,需要付出额外的努力


例如,如果希望将长度为length1和length2的字符串串联存储到缓冲区If length BUFF_SIZE中,可以轻松测试length1+length2是否我建议您尝试编写代码,有意跟踪字符串长度,而不关心字符串是否以零结尾。尽管空指针被称为数十亿美元的错误,但我认为以零结尾的字符串要糟糕得多。虽然在某些情况下,使用以零结尾的字符串的代码可能比跟踪字符串长度的代码更简单,但要确保没有任何东西会导致字符串处理代码超出缓冲区边界,需要付出额外的努力


例如,如果要将长度为length1和length2的字符串串联存储到缓冲区If length BUFF_SIZE中,可以轻松测试length1+length2是否有人可以控制子进程,他们现在也可以控制进程非常好的建议,谢谢。我一直在考虑给它提供测试数据。在开发的早期阶段,我确实使用了valgrind一段时间,我修复了它发现错误的所有可修复的东西。该漏洞只能每隔几个月触发一次,虽然有时一天内会触发几次,但几周内不会触发,这使得在调试器和类似工具中运行变得不太实际。我发现valgrind对于追踪“千载难逢”的崩溃原因非常有帮助。通常它们是由于细微的内存损坏错误造成的,99.9%的时间没有明显的影响;valgrind可以使隐藏的腐败更加明显。对,我们将再次尝试使用valgrind。如果有人可以控制子进程,他们现在也可以控制进程非常好的建议,谢谢。我一直在考虑给它提供测试数据。在开发的早期阶段,我确实使用了valgrind一段时间,我修复了它发现错误的所有可修复的东西。该漏洞只能每隔几个月触发一次,虽然有时一天内会触发几次,但几周内不会触发,这使得在调试器和类似工具中运行变得不太实际。我发现valgrind对于追踪“千载难逢”的崩溃原因非常有帮助。通常它们是由于细微的内存损坏错误造成的,99.9%的时间没有明显的影响;valgrind可以使隐藏的损坏更加可见。对,我将再次尝试使用valgrind。以Null结尾和以零结尾是同一回事?@ColeJohnson:我更喜欢以零结尾的术语,因为终止符是一个值为零的字节。在ASCII码中,零码被称为空字节,因为它不会导致电传打字机执行任何操作,其纸带表示为空,因此字符串末尾的零字节通常被称为空字节,但对字符串方法来说重要的不是字符码表示空字符,而是它的数值是零。即使C实现使用了一个字符集,其中空字符是代码42,strcpy仍然会复制到第一个零字节。我认为将字符串大小考虑在内可能会使函数过于复杂,而不是空终止。然而,您可能有一个观点,因为一个潜在的bug很可能在“/0”测试中。顺便说一句,使用strncpy和strncat不是更好吗?就目前所知,strcpy和strcat等因缓冲区溢出问题而受到反对。@aseq:strncpy的工作方式与许多人认为的不同。它不能确保结果以零结尾,但零填充字符串后的空间,strncat基本上没有用。它限制了要追加的字符串的长度,而不是结果;如果没有
我不知道要追加的字符串的长度,也不知道可以追加多少,如果你知道没有理由使用搜索要追加的字符串结尾的方法。以Null结尾和以零结尾是同一回事?@ColeJohnson:我更喜欢以零结尾的术语,因为终止符是一个值为零的字节。在ASCII码中,零码被称为空字节,因为它不会导致电传打字机执行任何操作,其纸带表示为空,因此字符串末尾的零字节通常被称为空字节,但对字符串方法来说重要的不是字符码表示空字符,而是它的数值是零。即使C实现使用了一个字符集,其中空字符是代码42,strcpy仍然会复制到第一个零字节。我认为将字符串大小考虑在内可能会使函数过于复杂,而不是空终止。然而,您可能有一个观点,因为一个潜在的bug很可能在“/0”测试中。顺便说一句,使用strncpy和strncat不是更好吗?就目前所知,strcpy和strcat等因缓冲区溢出问题而受到反对。@aseq:strncpy的工作方式与许多人认为的不同。它不能确保结果以零结尾,但零填充字符串后的空间,strncat基本上没有用。它限制了要追加的字符串的长度,而不是结果;如果你不知道要追加的字符串的长度,你就不知道可以追加多少,如果你知道没有理由使用搜索要追加的字符串结尾的方法,那么当我通过gdb运行应用程序时,上面提到的函数会崩溃。下载数据的副本实际上是原始下载数据的单独副本,因为strep实际上更改了数据。锁定它是什么意思?当我通过gdb运行应用程序时,它会在我上面提到的函数中崩溃。下载数据的副本实际上是原始下载数据的单独副本,因为strep实际上更改了数据。你把它锁起来是什么意思?