2

此代码旨在获取文本文件中的名称列表,并转换为电子邮件形式

所以 Kate Jones 变为 kate.jones@yahoo.com 这段代码在 linux mint 12 上运行良好,但现在完全相同的代码在 arch linux 上给出了段错误。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
  FILE *fp;
  fp = fopen("original.txt", "r+");
  if (fp == NULL )
  {
    printf("error opening file 1");
    return (1);
  }

  char line[100];
  char mod[30] = "@yahoo,com\n";
  while (fgets(line, 100, fp) != NULL )
  {
    int i;
    for (i = 0; i < 100; ++i)
    {
      if (line[i] == ' ')
      {
        line[i] = '.';
      }
      if (line[i] == '\n')
      {
        line[i] = '\0';
      }

    }

    strcat(line, mod);

    FILE *fp2;
    fp2 = fopen("final.txt", "a");

    if (fp == NULL )
    {
      printf("error opening file 2");
      return (1);
    }

    if (fp2 != NULL )
    {
      fputs(line, fp2);
      fclose(fp2);
    }

  }

  fclose(fp);

  return 0;
}

Arch Linux 是一个相当全新的安装,难道是我没有安装 C 需要的其他东西吗?

4

5 回答 5

9

我认为问题在于您的原始字符串加上 mod 超过 100 个字符。

当您调用strcat时,它只是将字符串从附加到第一个的第二个复制,假设第一个字符串中有足够的空间,这显然不是这里的情况。

只是增加线的大小,即它可能是

char line[130]; // 130 might be more than what is required since mod is shorter

使用strncat也更好

您可以在其中限制复制到 dst 的最大元素数量,否则,如果给定足够大的字符串,strcat仍然可以超出大小而不会抱怨。

尽管 strncat 的一个警告是它不会自行终止带有 null 的字符串,即 \0,特别是当它们比给定的 n 短时。因此,在实际使用之前,应仔细阅读其文档。

更新:平台特定说明

想到添加,纯属巧合,它没有在薄荷上出现故障并在拱形上坠毁。在实践中,它会调用未定义的行为,迟早会崩溃。这里没有特定的平台。

于 2013-10-09T11:46:38.563 回答
2

首先,您的代码不会产生分段错误。相反,它将调出“Stack Smashing”并在输出控制台中的 libc_message 下方抛出。

*** stack smashing detected ***: _executable-name-with-path_ terminated.

当程序向位于堆栈上的缓冲区写入的数据多于为该缓冲区实际分配的数据时,会导致堆栈缓冲区溢出错误。

Stack Smashing Protector (SSP) 是一个 GCC 扩展,用于保护应用程序免受此类堆栈粉碎攻击。

而且,正如其他答案中所说,您的问题通过递增(strcat()函数的第一个参数)得到解决。从

char line[100] 

char line[130]; // size of line must be atleast `strlen(line) + strlen(mod) + 1`. Though 130 is not perfect, it is safer 

让我们看看问题在代码中的确切位置:

为此,我提出了你的 main 的反汇编代码。

(gdb) disas main
Dump of assembler code for function main:
   0x0804857c <+0>: push   %ebp
   0x0804857d <+1>: mov    %esp,%ebp
   0x0804857f <+3>: and    $0xfffffff0,%esp
   0x08048582 <+6>: sub    $0xb0,%esp
   0x08048588 <+12>: mov    %gs:0x14,%eax
   0x0804858e <+18>: mov    %eax,0xac(%esp)

   .....  //Leaving out Code after 0x0804858e till 0x08048671

   0x08048671 <+245>:   call   0x8048430 <strcat@plt>
   0x08048676 <+250>:   movl   $0x80487d5,0x4(%esp)

   .... //Leaving out Code after 0x08048676 till 0x08048704

   0x08048704 <+392>:   mov    0xac(%esp),%edx
   0x0804870b <+399>:   xor    %gs:0x14,%edx
   0x08048712 <+406>:   je     0x8048719 <main+413>
   0x08048714 <+408>:   call   0x8048420 <__stack_chk_fail@plt>
   0x08048719 <+413>:   leave
   0x0804871a <+414>:   ret

按照通常的汇编语言序言,

指令 at 0x08048582: 堆栈增长 b0(十进制 176)字节,以允许主函数存储堆栈内容。

%gs:0x14 提供用于堆栈保护的随机金丝雀值。

指令 at 0x08048588:将上述值存储到 eax 寄存器中。

指令 at 0x0804858e: eaxcontent(canary value) 被推送到 $esp 处的堆栈,偏移量为 172

在 处保留断点 (1) 0x0804858e

(gdb) break *0x0804858e
Breakpoint 1 at 0x804858e: file program_name.c, line 6.

运行程序:

(gdb) run
Starting program: /path-to-executable/executable-name 

Breakpoint 1, 0x0804858e in main () at program_name.c:6
6   {

一旦程序在断点(1)处暂停,通过打印寄存器“eax”的内容来检索随机金丝雀值

(gdb) i r eax
eax            0xa3d24300   -1546501376

0x08048671在: 恰好在 call 之前保留断点(2)strcat()

(gdb) break *0x08048671
Breakpoint 2 at 0x8048671: file program_name.c, line 33.

继续程序执行到达断点(2)

(gdb) continue
Continuing.

Breakpoint 2, 0x08048671 in main () at program_name.c:33

通过在 gdb 中执行以下命令,打印出我们存储随机金丝雀值的第二个顶部堆栈内容,以确保它在strcat()调用之前是相同的。

(gdb) p *(int*)($esp + 172)
$1 = -1546501376

将断点 (3) 保留在0x08048676: 从调用返回后立即strcat()

(gdb) break *0x08048676
Breakpoint 3 at 0x8048676: file program_name.c, line 36.

继续程序执行到达断点(3)

(gdb) continue
Continuing.

Breakpoint 3, main () at program_name.c:36

通过在 gdb 中执行以下命令打印出我们存储随机金丝雀值的第二个顶部堆栈内容,以确保它不会被调用损坏strcat()

(gdb) p *(int*)($esp + 172)
$2 = 1869111673

但是它被调用破坏了strcat()。你可以看到$1$2不一样。让我们看看由于破坏了随机金丝雀值会发生什么。

指令在0x08048704:提取损坏的随机金丝雀值并存储在“edx”寄存器中

指令在0x0804870b:xor 实际随机金丝雀值和“edx”寄存器的内容

指令 at 0x08048712:如果它们相同,则直接跳转到 main 的末尾并安全返回。在我们的例子中,随机金丝雀值已损坏,“edx”寄存器内容与实际随机金丝雀值不同。因此跳转条件失败并调用 __stack_chk_fail ,这会抛出答案顶部提到的 libc_message 并中止应用程序。

有用的链接:

IBM SSP 页面

有趣的阅​​读 SSP - 警告 pdf。

于 2013-10-10T14:48:25.467 回答
1

既然你没有告诉我们哪里出了问题,我只指出一些可疑的行:

for(i=0; i<100; ++i)

如果一行少于 100 个字符怎么办?这将读取未初始化的内存 - 这样做不是一个好主意。

strcat(line, mod);

如果一行有 90 个长度,然后你又添加了 30 个字符怎么办?那是20出界。。

您需要计算长度并使用 malloc 动态分配字符串,并确保您不会越界读取或写入,并且您的字符串始终以 NULL 结尾。或者,如果不必是 C,您可以使用 C++/std::string 使事情变得更容易

于 2013-10-09T11:30:47.787 回答
0

不仅要检查\n行尾,还要添加对\r字符的检查。

if(line[i] == '\n' || line[i] == '\r')

另外,在使用之前strcat确保line有足够的空间来存放mod. 您可以通过检查是否(i < /* Some value far less than 100 */),如果i == 100那么这意味着它从未遇到因此未添加到的\n字符来执行此操作,因此内部发生了无效的内存访问,因此发生了Seg Fault\0linestrcat()

于 2013-10-09T12:08:01.780 回答
0

解决它。我只是增加了线串的大小。

于 2013-10-09T18:08:50.770 回答