查找C代码中的错误

时间:2013-04-02 04:21:12

标签: c linux perl segmentation-fault

更新:确实,依赖某些程序的段错是一个错误。但是我很欣赏并从我在这里得到的答案中学到了很多东西。

segfault.c

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


/**
  *
  * This binary receives 2 numbers, an L and an R
  *
  * It will intentionally hit Segmentation Fault(actually SIGSEGV) whenever 30 is in the interval [L,R]
  *
  * It will be used to test the binary search segfault finder
  *
  */
int main(int argc, int **argv) {
  char exists;
  char *Q[3000];
  int i;
  int L = atoi((char*)argv[1]);
  int R = atoi((char*)argv[2]);

  printf("L=%d R=%d",L,R);
  /*exit(0);*/
  for(i=0;i<3000;i++)
    Q[i] = &exists;

  Q[30] = NULL; // <==== I want to cause a SIGSEGV through this !

  for(i=L;i<=R;i++) {
    int T = *Q[i]; // <== will segfault when i == 30 because I said so :)
  };
};

automate.pl

#!/usr/bin/env perl
use strict;
use warnings;



my $segfaulting_file = "s";
my $L = 0;
my $R = 7266786; #`cat $segfaulting_file | wc -l`;
my $M;
my $binary = "./filter";

while ($L < $R) {
  $M     = int(($L+$R)/2);
  # head argument for right side
  my $HL = $M;
  # tail argument for right side
  my $TL = $M-$L;
  # head argument for left  side
  my $HR = $R;
  # tail argument for left  side
  my $TR = $R-$M;

  print "M=$M L=$L R=$R\n";
  my $go_left ;
  my $go_right;
  my $cmd_R = "cat $segfaulting_file | head -$HR | tail -$TR | $binary > /dev/null;";
  my $cmd_L = "cat $segfaulting_file | head -$HL | tail -$TL | $binary > /dev/null;";

  print "\nRunning $cmd_R\n";
  `$cmd_R`;
  #`./a.out $M $R`;
  print "RETVAL=$?\n";
  $go_right = ($? > 30000); # right side caused SEGFAULT
  `rm core`;
  print "\nRunning $cmd_L\n";
  `$cmd_L`;
  print "RETVAL=$?\n";
  #`./a.out $L $M`;
  $go_left  = ($? > 30000); # left  side caused SEGFAULT
  `rm core`;

  if(  $L == $R ) {
    last;
  }elsif ( $go_left  ) {
    print "GO left  L=$L R=$R\n";
    $R = $M  ;
  }elsif ( $go_right ) {
    print "GO right L=$L R=$R\n";
    $L = $M+1;
  };
};


# the loop stopped because $L==$R==$M , so we just print out $M
print "Segfault caused by line $M\n";

3 个答案:

答案 0 :(得分:3)

你要求Z解决Y才能解决X ... 停止!回到X.你不需要日志行来解决为什么它是segfaulted。你需要它所分离的源代码行来找出它为什么会被分割出来,我认为你知道如何获得它。从该行返回,遵循它可能采取的任何分支,并策略性地放置assert()离子以确定错误输入来自何处。将重复的代码分离成函数以便重用,因为您继续“消除错误”,并且您将同时重构代码以实现模块化和稳定性。

编辑:如果您真的想继续这些行,则不需要perl脚本。您需要的只是代码中的一个计数器,用于在解析它们时计算行数,以及signal的帮助。在运行以下代码时,ideone在计数为65535之前不会发生段错误。希望您能看到依赖于段错误的缓冲区溢出是多么愚蠢......

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>

void segfault_handler(int sig);

int counter = 0;

int main(void) {
    int *fubar = malloc(0); // allocate ZERO bytes!

    assert(signal(SIGSEGV, segfault_handler) != SIG_ERR);
    assert(signal(SIGILL, segfault_handler) != SIG_ERR);
    assert(signal(SIGFPE, segfault_handler) != SIG_ERR);

    for (;;) {
        printf("Assigning to fubar[%d]\n", counter);
        fubar[counter] = INT_MIN;
        counter = counter * 2 + 1;
    }
}

void segfault_handler(int sig) {
    /* NOTE: Using printf inside a signal handler is also undefined behaviour */
    printf("Fault when counter is %d\n", counter);
    exit(EXIT_FAILURE);
}

答案 1 :(得分:2)

检测seg错误的最可靠方法可能是使用forkexecwait。你分叉Perl脚本;孩子执行二进制文件。父级从二进制文件中收集退出状态。退出状态有两个部分 - 如果孩子在控制下退出时退出的值,或者如果它从信号中死亡则收到的信号。您只需检查信号分量是否为零;如果不是零,则假设它是一个段错误(尽管如果你愿意,你可以验证它的号码)。剩下的技巧是在标准输入上获取二进制数据。您可以让子项启动它从中读取的管道,并将管道复制到标准输入,然后关闭管道。比真正困难更加繁琐。


大纲Perl代码

我需要一个崩溃的程序,所以我创建了一个Perl脚本crash_after_reading

#!/usr/bin/env perl
use POSIX;

$| = 1;
print "." while (<>);
print "\n";
abort;

然后,Perl脚本的模拟器看起来像这样(forkwait.pl):

#!/usr/bin/env perl
use strict;
use warnings;
use POSIX;

my $pipeline = "cat /etc/group";
my $pid;

die "$!" if (($pid = fork) < 0);

if ($pid == 0)
{
    # Child
    open STDIN, "-|", $pipeline or die "open";
    exec "crash_after_reading";
    die "failed to exec crash_after_reading";
}

my $corpse = waitpid($pid, 0);
printf "PID = %d; status = 0x%.4X\n", $corpse, $?;

示例输出

..................................................................................
PID = 92413; status = 0x0006

低8位非零,因此子程序与信号6(SIGABRT)崩溃。如果低位8​​位全为零,则子程序成功退出,状态为高位8位。

如果您决定使用它,则需要根据代码进行调整。

答案 2 :(得分:2)

由于您可以编辑过滤程序的源代码,为什么不编辑它以在处理之前将每个日志文件行写入另一个文件。然后在segfault之后,查看新文件以查看当时正在处理的行。这也将比你的二进制搜索更快,每次需要精确处理N-1行,而这种方法将平均处理N / 2行(取决于违规行的位置),其中有N日志文件中的行。