[英]Finding bugs in C code
更新:确实,依赖某些程序的段错是一个错误。 但是我从这里收到的答案中感谢并学到了很多东西。
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";
你要求Z解决Y才能解决X ... 停止! 返回到X。您不需要日志行就可以弄清楚它为什么被分段。 您需要对其进行分段处理的源代码行才能确定为什么进行分段处理,我想您知道如何获取。 从那条线开始工作,跟随它可能采取的任何分支,并策略性地放置assert()
离子以确定错误输入的来源。 在继续“消除bug”的过程中,将重复的代码分为多个函数以进行重用,并且您将同时重构代码以实现模块化和稳定性。
编辑:如果您确实想继续遵循这些原则,则不需要perl脚本。 您需要做的就是在代码中添加一个计数器,以便在解析时对行进行计数,并在signal的帮助下进行计数。 运行以下代码时, ideone直到segfault才计数65535。希望您能看到依靠segfaults来防止缓冲区溢出是多么愚蠢...
#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);
}
检测段错误的最可靠方法可能是使用fork
, exec
和wait
。 您派生了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位。
如果您决定完全使用它,则需要对其进行调整。
由于您可以编辑过滤程序的源代码,为什么不编辑它以在处理之前将每个日志文件行写入另一个文件。 然后在segfault之后,查看新文件以查看当时正在处理的行。 这也将比你的二进制搜索更快,每次需要精确处理N-1行,而这种方法将平均处理N / 2行(取决于违规行的位置),其中有N日志文件中的行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.