此代码旨在获取文本文件中的名称列表,并转换为电子邮件表单
所以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需要的东西吗?
答案 0 :(得分:9)
我认为问题出在你的原始字符串加mod超过100个字符时。
当您调用 strcat 时,它只是将第二个附加到第一个字符串的字符串复制,假设第一个字符串中有足够的空间,这里似乎不是这种情况。< / p>
只需增加线条的大小即可以
char line[130]; // 130 might be more than what is required since mod is shorter
使用strncat
也好得多你可以限制复制到dst的最大元素数量,否则,如果给定足够大的字符串,strcat可以仍然超出大小而不会抱怨。
虽然strncat的一个注意事项是它不会终止带有null的字符串,即它自己的\ 0,特别是当它们比给定的n短时。所以在实际使用之前应该仔细阅读它的文档。
更新:平台特定说明
添加的想法,纯粹巧合的是它没有在薄荷上划分错误并在拱门上坠毁。在实践中,它正在调用未定义的行为并且应该更快或更快地崩溃。这里没有特定的平台。
答案 1 :(得分: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
让我们看看问题在您的代码中的确切位置:
为此,我提出了主要的反汇编代码。
(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
按照通常的汇编语言序言,
0x08048582
处的指令:堆栈增长了b0(十进制176个)字节,用于允许主函数的存储堆栈内容。
%gs:0x14提供用于堆栈保护的随机金丝雀值。
0x08048588
处的指令:将上述值存储到eax寄存器中。
0x0804858e
处的指令:eax
内容(金丝雀值)被推送到$ esp堆叠,偏移量为172
在0x0804858e
保留断点(1)。
(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
处设置断点(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中执行以下命令打印出我们存储随机canary值的第二个顶层堆栈内容,以确保在调用strcat()
之前它是相同的。
(gdb) p *(int*)($esp + 172)
$1 = -1546501376
在0x08048676
处设置断点(3):从通话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中执行以下命令打印出我们存储随机canary值的第二个顶层堆栈内容,以确保它不会被调用strcat()
(gdb) p *(int*)($esp + 172)
$2 = 1869111673
但是通过调用strcat()
来破坏它。您可以看到$1
和$2
不相同。
让我们看看由于破坏随机金丝雀值会发生什么。
0x08048704
处的指令:拉出损坏的随机金丝雀值并存储在'edx`寄存器中
0x0804870b
处的指示:xor实际的随机金丝雀值和'edx'寄存器的内容
0x08048712
处的指示:如果它们相同,则直接跳到主的末尾并安全返回。在我们的例子中,随机金丝雀值被破坏,'edx'寄存器内容与实际随机金丝雀值不同。因此跳转条件失败并调用__stack_chk_fail,它会抛出答案顶部提到的libc_message并中止应用程序。
有用的链接:
Interesting Read on SSP - 小心pdf。
答案 2 :(得分:1)
既然你没有告诉我们哪里出错,我只会指出一些可疑的行:
for(i=0; i<100; ++i)
如果一条线少于100个字符怎么办?这将读取未初始化的内存 - 这不是一个好主意。
strcat(line, mod);
如果一条线的长度为90,然后你又增加30个字符怎么办?那是20个超出界限..
您需要计算长度并使用malloc动态分配字符串,并确保您不读取或写出越界,并且您的字符串始终以NULL结尾。或者你可以使用C ++ / std :: string来使事情变得更容易,如果 不是C。
答案 3 :(得分:0)
不是仅检查\n
,而是在行末添加\r
字符的检查。
if(line[i] == '\n' || line[i] == '\r')
此外,在使用strcat
之前,请确保line
有足够的空间mod
。您可以通过检查(i < /* Some value far less than 100 */)
是否为i == 100
来执行此操作,这意味着它从未遇到过\n
个字符,因此\0
未添加到line
,因此无效内存访问发生在strcat()
内,因此 Seg Fault 。
答案 4 :(得分:0)
修正了它。我只是增加了我的行字符串的大小。