Почему _exit(0) (выход по системному вызову) не позволяет мне получать содержимое stdout?

У меня есть программа сборки Linux x86-32 GAS, завершающаяся следующим образом:

movl $1, %eax
movl $0, %ebx # argument for _exit
int $0x80

Когда я выхожу таким образом, программа работает как обычно, но если я пытаюсь прочитать вывод stdout, я ничего не получаю (используя, например, less или wc).

Я попытался скомпилировать минимальную программу на C и сравнить результаты strace. Единственное отличие, которое я обнаружил, заключалось в том, что GCC заставил программу C (int main() { printf("donkey\n"); }) неявно завершать работу с exit_group(0) в выводе strace.

Я попытался изменить свою программу ASM, чтобы она выходила с call exit вместо необработанного системного вызова. Стандартный вывод теперь читался как обычно.

Тестовый пример

.data
douout: .string "monkey\n"
.text
.globl main

main:

pushl $douout
call printf
# Exit
movl $1, %eax
movl $0, %ebx
int $0x80

Скомпилируйте и запустите:

$ yasm -g dwarf2 -f elf -p gas t.asm && gcc -g -melf_i386 -o t t.o && ./t | wc -c
0

Ожидал:

7

РЕДАКТИРОВАТЬ:

Я пробовал звонить как tcflush, так и fflush, но проблема осталась. С fflush я даже получаю segfault.

0xb7e9e7c9 in _IO_fflush (fp=0x804a018) at iofflush.c:42
42  iofflush.c: No such file or directory.
    in iofflush.c
(gdb) bt
#0  0xb7e9e7c9 in _IO_fflush (fp=0x804a018) at iofflush.c:42
#1  0x08048434 in main () at t.asm:12
(gdb) frame 1
#1  0x08048434 in main () at t.asm:12
12  call fflush
(gdb) list
7   
8   pushl $douout
9   call printf
10  # Exit
11  movl $0, %eax
12  call fflush
13  movl $1, %eax
14  movl $0, %ebx
15  int $0x80

РЕДАКТИРОВАТЬ2:

Хорошо, теперь все работает. Я использовал неправильное соглашение о вызовах, которое скопировал отсюда: Printf без новой строки в сборке

Аргумент для fflush должен быть в стеке, как обычно.

$ cat t.asm 
.data
douout: .string "monkey\n"
.text
.globl main

main:

pushl $douout
call printf
# Exit
pushl $0
call fflush
movl $1, %eax
movl $0, %ebx
int $0x80
$ yasm -g dwarf2 -f elf -p gas t.asm && gcc -g -melf_i386 -o t t.o && ./t | wc -c
7
$

Спасибо всем, особенно Nos.


person Janus Troelsen    schedule 18.03.2012    source источник


Ответы (3)


когда вы передаете stdout в wc, stdout становится полностью буферизованным.

_exit немедленно завершает процесс и не запускает atexit() и другие обработчики очистки. Среда выполнения зарегистрирует такие обработчики для запуска при выходе, которые сбрасывают открытый ФАЙЛ*, например стандартный вывод. Когда эти обработчики не выполняются при выходе, буферизованные данные будут потеряны.

Вы должны увидеть вывод, если вы вызываете fflush(stdout) после вызова printf, или если вы просто запускаете программу в консоли, не перенаправляя вывод в другую программу — в этом случае stdout обычно буферизуется строкой, поэтому stdout сбрасывается всякий раз, когда вы пишете \ н

person nos    schedule 18.03.2012
comment
Как бы вы назвали fflush(stdout)? Согласно unistd.h номер файла равен 1, но согласно stackoverflow. com/questions/8502945/ , это 0. - person Janus Troelsen; 18.03.2012
comment
stdout в этом контексте — это глобальный ФАЙЛ* с именем stdout (который по умолчанию подключен к файловому дескриптору 1, стандартный вывод). Вы не можете использовать дескрипторы файлов, 0 или 1, в качестве аргументов для функций, которые ожидают FILE*. Однако вы можете вызвать fflush() с NULL в качестве аргумента, чтобы сбросить все открытые FILE*. - person nos; 18.03.2012
comment
Но я не думал, что в сборке есть NULL? А в C NULL == 0. Как узнать разницу? - person Janus Troelsen; 18.03.2012
comment
Вы бы не стали. Вы бы просто использовали 0 того же размера, что и указатель. Но это не означает, что файловый дескриптор равен 0, хотя их значения совпадают. - person nos; 18.03.2012
comment
Сотрите это, конечно, он бы знал разницу, поскольку он принимает ФАЙЛ *, а не дескриптор файла. - person Janus Troelsen; 18.03.2012
comment
Да, я знаю, что они неотличимы. Я перепутал файловые дескрипторы и FILE* и подумал, что для этого нужен файловый дескриптор. Вот почему я сказал это, чтобы вы знали, что я знаю, как он узнает разницу (поскольку FILE* для stdout не NULL, дескриптор файла равен 0, что обычно равно NULL). Я понимаю, как это работает сейчас. Mange tak for hjælpen, kære nordmand :) - person Janus Troelsen; 18.03.2012

Вывод, отправляемый на стандартный вывод, обычно буферизуется. Если вы вызовете fflush(stdout) перед вызовом _exit, вы должны получить результат.

Причина, по которой exit работает, заключается в том, что эта функция гарантированно закрывает и очищает любые открытые потоки (например, стандартный вывод) перед вызовом самого _exit для фактического завершения программы.

person SoapBox    schedule 18.03.2012
comment
Но я получаю результат, если не передаю его. Кроме того, man _exit отмечает, что я могу использовать tcflush. Я пробовал это, и это не сработало. Как мне позвонить fflush(stdout)? pushl $1; call fflush? - person Janus Troelsen; 18.03.2012
comment
Я нашел файл stdout в unistd.h. Кроме того, я только что попробовал это с 1 в стеке и в %eax, оба из них segfault. - person Janus Troelsen; 18.03.2012
comment
Отвечаю себе: требуется ФАЙЛ*, а не файловый дескриптор. - person Janus Troelsen; 18.03.2012

Согласно справочной странице для _exit(2)

Очищает ли он стандартные буферы ввода-вывода и удаляет ли временные файлы, созданные с помощью tmpfile(3), зависит от реализации. С другой стороны, _exit() закрывает дескрипторы открытых файлов, и это может вызвать неизвестную задержку, ожидая завершения ожидающего вывода. Если задержка нежелательна, может быть полезно вызвать такие функции, как tcflush(3), перед вызовом _exit(). Отменяется ли какой-либо ожидающий ввод-вывод и какой ожидающий ввод-вывод может быть отменен при _exit(), зависит от реализации.

Поэтому, если вы не сбросили стандартный вывод, он может быть отброшен.

person Jim Garrison    schedule 18.03.2012
comment
Пробовал звонить tcflush, разницы никакой. - person Janus Troelsen; 18.03.2012
comment
tcflush работает на терминальном устройстве. Вам нужно вызвать fflush(stdout). (stdout — это глобальный ФАЙЛ*, управляемый libc, вы не можете использовать файловый дескриптор). Если у вас возникли проблемы с этим, вызовите fflush(NULL), который очистит все ФАЙЛЫ*. - person nos; 18.03.2012
comment
Вы не передаете правильный аргумент в fflush, согласно обычному соглашению о вызовах, аргументы должны быть в стеке, а не в %eax - person nos; 18.03.2012