Perl系统调用导致核心转储,但$?仍为零。

4

我有一个Perl脚本(在VirtualBox中运行的Xubuntu Lucid Lynx上),它包装了几个C/C++二进制文件,将一个程序的输出作为另一个程序的输入。其中一行通常如下:

my $ret_code=`cat $input | c_binary`;
my $ret_val= $?;

对于一些输入文件,代码会导致核心转储,但$ret_val$ret_code分别为0和""。当我运行它时,我可以看到错误消息滚动,但好像没有办法在程序中"捕获"这些错误。 我该如何做? 目的是在出错时从输入中删除一些行并重试解析。

以下是错误消息:

*** stack smashing detected ***: code/parser terminated
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x50)[0x798390]
/lib/tls/i686/cmov/libc.so.6(+0xe233a)[0x79833a]
code/parser[0x804edd8]
[0x2e303039]
======= Memory map: ========
0043b000-0043c000 r-xp 00000000 00:00 0          [vdso]
0045a000-00475000 r-xp 00000000 08:01 11041      /lib/ld-2.11.1.so
00475000-00476000 r--p 0001a000 08:01 11041      /lib/ld-2.11.1.so
00476000-00477000 rw-p 0001b000 08:01 11041      /lib/ld-2.11.1.so
006b6000-00809000 r-xp 00000000 08:01 10897      /lib/tls/i686/cmov/libc-2.11.1.so
00809000-0080a000 ---p 00153000 08:01 10897      /lib/tls/i686/cmov/libc-2.11.1.so
0080a000-0080c000 r--p 00153000 08:01 10897      /lib/tls/i686/cmov/libc-2.11.1.so
0080c000-0080d000 rw-p 00155000 08:01 10897      /lib/tls/i686/cmov/libc-2.11.1.so
0080d000-00810000 rw-p 00000000 00:00 0 
008ba000-008d7000 r-xp 00000000 08:01 8268       /lib/libgcc_s.so.1
008d7000-008d8000 r--p 0001c000 08:01 8268       /lib/libgcc_s.so.1
008d8000-008d9000 rw-p 0001d000 08:01 8268       /lib/libgcc_s.so.1
00c89000-00cad000 r-xp 00000000 08:01 10901      /lib/tls/i686/cmov/libm-2.11.1.so
00cad000-00cae000 r--p 00023000 08:01 10901      /lib/tls/i686/cmov/libm-2.11.1.so
00cae000-00caf000 rw-p 00024000 08:01 10901      /lib/tls/i686/cmov/libm-2.11.1.so
08048000-08055000 r-xp 00000000 08:01 407893     /home/abugorsk/Documents/code/stepbystep/collins-parser/code/parser
08055000-08056000 r--p 0000c000 08:01 407893     /home/abugorsk/Documents/code/stepbystep/collins-parser/code/parser
08056000-08057000 rw-p 0000d000 08:01 407893     /home/abugorsk/Documents/code/stepbystep/collins-parser/code/parser
08057000-0c50f000 rw-p 00000000 00:00 0 
0e168000-0fa57000 rw-p 00000000 00:00 0          [heap]
b44a3000-b77c9000 rw-p 00000000 00:00 0 
b77da000-b77dc000 rw-p 00000000 00:00 0 
bff2b000-bff40000 rw-p 00000000 00:00 0          [stack]
Aborted

返回的值如下:
LOG: Parser return code: 0    
LOG: Parser return value:

实际的代码片段如下:
my $command = "cd $STEPBYSTEP_HOME/collins-parser; cat models/model$model_num/events | code/parser $src models/model$model_num/grammar 10000 1 1 1 1 1> $dest 2> $parse_log";
llog "Executing command: $command";
my $ret_code = $?;
llog "Parser return code: $ret_code";
my $ret_val = `$command`;

4
这是实际的代码吗?看起来你在实际运行命令之前就执行了my $ret_code = $? - nos
1
顺便说一句,cat $FILE | cmd 几乎总是会引入一个多余的进程。请改用 cmd < $FILE - pilcrow
4个回答

1
首先,你展示的代码有些可疑:在实际运行命令之前,你获取了$?的值。现在我将讨论我认为你想要编写的内容:
my $command = "cd $STEPBYSTEP_HOME/collins-parser;" .
              "cat models/model$model_num/events | code/parser $src models/model$model_num/grammar 10000 1 1 1 1 1> $dest 2> $parse_log";
my $ret_val = `$command`;
my $ret_code = $?;

在此之后,$ret_code 包含整个 shell 命令的状态。这反过来又是列表中最后一个命令的状态,即管道 cat ... | code/parser ... 的状态。根据 shell 的不同,这可能是管道中最后一个命令 code/parser 的状态(ksh、zsh),或者始终为 0(大多数 shell,包括 ash、bash 和 pdksh)。

在您的情况下,有一个简单的解决方法,就是摆脱无用的 cat 使用:

my $command = "cd $STEPBYSTEP_HOME/collins-parser &&" .
              "<models/model$model_num/events code/parser $src models/model$model_num/grammar 10000 1 1 1 1 1> $dest 2> $parse_log";
my $ret_val = `$command`;
my $ret_code = $?;

如果你有一个比cat更有用的命令,你最好完全放弃使用shell。这也有其他一些小的好处:少了一个需要掌握的工具;更容易移植到非Unix系统;可以处理包含shell元字符的文件名(这也可以通过系统地使用quotemeta来实现)。以下是这个想法的要点(未经测试):perldoc -f openperldoc perlipc可能会有所帮助。
use File::Slurp;
if (open my $fh, "|-") {
    # Parent code
    my $ret_val = read_file($fh);
    close($ret_code);
    my $ret_code = $?;
    ...
} else { # Child code
    chdir "$ENV{STEPBYSTEP_HOME}/collins-parser" or die $!;
    open STDIN, "<", "models/model$model_num/events" or die $!;
    open STDOUT, ">", $dest or die $!;
    open STDERR, ">", $parse_log or die $!;
    exec "code/parser", $src, "models/model$model_num/grammar", "1", "1", "1", "1", "1";
    die $!;
}

0
由于 CRT 终止了程序(即它实际上没有通过信号“崩溃”,而是 CRT 检测到了受损的堆栈保护位并手动终止了进程),它的返回值将是零。我认为你在这里能做的最好的事情是:
`cat $input | c_binary 2>&1`

这样可以捕获CRT垃圾并在Perl脚本中检测到它。


1
我认为这不正确,程序应该通过abort()并从SIGABRT中退出。 - Hasturkun
啊,那可能是真的。2>&1 部分仍然有帮助。 - Ana Betts

0

编译这个简单的替代您的c_binary

#include <string.h>
void f(void)
{
  char smallbuf[9];
  strcpy(smallbuf, "dy-no-MITE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
int main(void)
{
  f();
  return 0;
}

以及这个Perl程序来运行它的图像

#! /usr/bin/perl

use warnings;
use strict;

use POSIX;

if (system("./c_binary") == 0) {
  print "$0: c_binary exited normally\n";
}
else {
  warn "$0: c_binary exited ", ($? >> 8), "\n",
       WIFSIGNALED($?)
         ? ("  - terminated by signal ", WTERMSIG($?), "\n") : ();
}

我得到了以下信息:

$ ./boom
*** stack smashing detected ***: ./c_binary terminated ./prog.pl: c_binary exited 0 - terminated by signal 11

因此,您需要使用POSIX模块中的WIFSIGNALEDWTERMSIG来编程检测c_binary是否被信号杀死,而不仅仅是退出状态本身:

WIFSIGNALED

WIFSIGNALED($?)如果子进程由于信号而终止,则返回true

WTERMSIG

WTERMSIG($?)返回子进程终止的信号(仅在WIFSIGNALED($?)为true时有意义)


0

首先,你的命令行中有一个无用的cat,可以轻松地被重定向替代。

我建议将命令更改为以下内容:

my $command = "cd $STEPBYSTEP_HOME/collins-parser && code/parser $src models/model$model_num/grammar 10000 1 1 1 1 < models/model$model_num/events 1> $dest 2> $parse_log";

如果你想要最小化输入文件并找出导致崩溃的原因,我强烈推荐使用Delta工具,它能够有效地自动化这个过程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接