2

このコードは、テキスト ファイル内の名前のリストを取得し、電子メール形式に変換するためのものです。

そのため、Kate Jones は kate.jones@yahoo.com になります。このコードは Linux mint 12 では正常に機能しましたが、まったく同じコードが arch Linux で segfault を引き起こしています。

#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を呼び出すと、最初の文字列に十分なスペースがあると仮定して、最初の文字列に追加された 2 番目の文字列が単純にコピーされますが、ここでは明らかにそうではないようです。

行のサイズを大きくするだけです。

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

また、 strncatを使用する方がはるかに優れています

dst にコピーされる要素の最大数を制限できます。それ以外の場合、十分な大きさの文字列が与えられた場合、strcat は文句を言わずにサイズを超えることができます。

ただし、strncat で注意すべき点は、文字列が指定された n よりも短い場合は特に、文字列を null (つまり \0) で終了しないことです。そのため、実際に使用する前にドキュメントをよく読む必要があります。

更新: プラットフォーム固有の注意事項

付け加えると、 mint でセグ フォールトが発生せず、arch でクラッシュしたのはまったくの偶然です。実際には、未定義の動作を呼び出しており、遅かれ早かれクラッシュするはずです。ここにはプラットフォーム固有のものはありません。

于 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 

問題がコードのどこに正確にヒットするかを見てみましょう。

そのために、私はあなたのメインの逆アセンブル コードを作成しています。

(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

いつものアセンブリ言語のプロローグに続いて、

Instruction at 0x08048582: スタックは、メイン関数のストレージ スタック コンテンツを許可するために b0 (10 進数で 176) バイト増加します。

%gs:0x14 は、スタック保護に使用されるランダムなカナリア値を提供します。

Instruction at 0x08048588: 上記の値を eax レジスタに格納します。

命令0x0804858e:eaxコンテンツ (カナリア値) は、オフセット 172 で $esp のスタックにプッシュされます

ブレークポイント (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

breakpoint(2) を0x08048671: call の直前に保持します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

strcat()が呼び出される前に同じであることを確認するために、gdb で次のコマンドを実行して、ランダム カナリア値を保存した 2 番目のトップ スタック コンテンツを出力します。

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

Keep a breakpoint (3) at 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 で次のコマンドを実行してランダム カナリア値を保存した 2 番目のトップ スタック コンテンツを出力し、呼び出しによって破損していないことを確認します。strcat()

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

しかし、呼び出しによって破損していますstrcat()。見えて同じでは$1あり$2ません。ランダム カナリア値が破損したために何が起こるか見てみましょう。

命令0x08048704: 破損したランダム カナリア値を取得し、「edx」レジスタに格納します。

Instruction at 0x0804870b: 実際のランダム カナリア値と 'edx' レジスタの内容の xor

Instruction 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

修正しました。線の文字列のサイズを大きくしただけです。

于 2013-10-09T18:08:50.770 に答える
0

\n行末のみをチェックする代わりに、\r文字のチェックも追加します。

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

また、使用する前に、十分なスペースがあることをstrcat確認してください。をチェックすることでこれを行うことができます。その場合は、文字に遭遇したことがなく、追加されなかったことを意味します。したがって、無効なメモリ アクセスが内部で発生し、したがってSeg Faultが発生します。linemod(i < /* Some value far less than 100 */)i == 100\n\0linestrcat()

于 2013-10-09T12:08:01.780 に答える