Захват сообщения "Ошибка сегментации" для разбитого подпроцесса: нет выхода и ошибки после вызова для связи()

У меня есть проблемы с использованием модуля подпроцесса, чтобы получить выход из разбитых программ. Я использую python2.7 и подпроцесс для вызова программы со странными аргументами, чтобы получить некоторые segfaults Чтобы вызвать программу, я использую следующий код:

proc = (subprocess.Popen(called,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE))
out,err=proc.communicate()
print out,err

called - это список, содержащий имя программы и аргумент (строка, содержащая случайные байты, кроме байта NULL, который подпроцесс не нравится вообще)

Код ведет себя и показывает мне stdout и stderr, когда программа не сбой, но когда она вылетает, out и err являются пустыми, а не показывают знаменитую "ошибку сегментации".

Я хочу найти способ получить и исправить, даже если программа сбой.

Надеюсь, что кто-то здесь как идея:)

PS: Я также попробовал методы check_output/call/check_call

ИЗМЕНИТЬ:

  • Я запускаю этот script на 64-битных Archlinux в виртуальной среде python (здесь не должно быть что-то важное, но вы никогда не знаете: p)

  • Segfault происходит в программе C, которую я пытаюсь запустить, и является следствием переполнения буфера

  • Проблема заключается в том, что когда возникает segfault, я не могу получить результат того, что произошло с подпроцессом

  • Я получаю код возврата справа: -11 (SIGSEGV)

  • Используя python, я получаю:

     ./dumb2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
     ('Exit code was:', -11) 
     ('Output was:', '') 
     ('Errors were:', '')
    
  • Во внешнем python я получаю:

    ./dumb2 $(perl -e "print 'A'x50")  
    BEGINNING OF PROGRAM 
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    END OF THE PROGRAM
    Segmentation fault (core dumped)
    
  • Возвращаемое значение оболочки равно: echo $? возвращает 139 так -11 ($? и 128)

Ответ 1

EDIT: вернулась сюда: она работает как прелесть с подпроцессом из python3, и если вы находитесь в Linux, есть backport для python2, называемый subprocess32, который делает работу достаточно хорошо

  • SOLVED: я использовал pexpect и работает

    def cmdlineCall(name, args):
        child = pexpect.spawn(name, args)
        # Wait for the end of the output
        child.expect(pexpect.EOF) 
        out = child.before # we get all the data before the EOF (stderr and stdout)
        child.close() # that will set the return code for us
        # signalstatus and existstatus read as the same (for my purpose only)
        if child.exitstatus is None:
            returncode = child.signalstatus
        else:
            returncode=child.exitstatus
        return (out,returncode)
    

PS: немного медленнее (потому что он порождает псевдо-tty)

Ответ 2

"Segmentation fault" сообщение может быть сгенерировано оболочкой. Чтобы узнать, убивает ли процесс SIGSEGV, отметьте proc.returncode == -signal.SIGSEGV.

Если вы хотите увидеть сообщение, вы можете запустить команду в оболочке:

#!/usr/bin/env python
from subprocess import Popen, PIPE

proc = Popen(shell_command, shell=True, stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
print out, err, proc.returncode

Я тестировал его с помощью shell_command="python -c 'from ctypes import *; memset(0,1,1)'", который вызывает segfault, и сообщение записывается в err.

Если сообщение напечатано непосредственно на терминале, вы можете использовать модуль pexpect для его записи:

#!/usr/bin/env python
from pipes import quote
from pexpect import run # $ pip install pexpect

out, returncode = run("sh -c " + quote(shell_command), withexitstatus=1)
signal = returncode - 128 # 128+n
print out, signal

Или напрямую используя pty модуль stdlib:

#!/usr/bin/env python
import os
import pty
from select import select
from subprocess import Popen, STDOUT

# use pseudo-tty to capture output printed directly to the terminal
master_fd, slave_fd = pty.openpty()
p = Popen(shell_command, shell=True, stdin=slave_fd, stdout=slave_fd,
          stderr=STDOUT, close_fds=True)
buf = []
while True:
    if select([master_fd], [], [], 0.04)[0]: # has something to read
        data = os.read(master_fd, 1 << 20)
        if data:
            buf.append(data)
        else: # EOF
            break
    elif p.poll() is not None: # process is done
        assert not select([master_fd], [], [], 0)[0] # nothing to read
        break
os.close(slave_fd)
os.close(master_fd)
print "".join(buf), p.returncode-128

Ответ 3

proc = (subprocess.Popen(called, stdout=subprocess.PIPE, stderr=subprocess.PIPE))

print(proc.stdout.read())
print(proc.stderr.read())

Это должно работать лучше.
Лично я бы пошел с:

from subprocess import Popen, PIPE

handle = Popen(called, shell=True, stdout=PIPE, stderr=PIPE)
output = ''
error = ''

while handle.poll() is None:
    output += handle.stdout.readline() + '\n'
    error += handle.stderr.readline() + '\n'

handle.stdout.close()
handle.stderr.close()

print('Exit code was:', handle.poll())
print('Output was:', output)
print('Errors were:', error)

И, возможно, используйте epoll(), если возможно, для stderr, поскольку он иногда блокирует вызов, потому что он пуст, поэтому я заканчиваю делать stderr=STDOUT, когда я ленив.