linux - El uso de printf en el ensamblaje conduce a una salida vacía
assembly x86-64 (2)
Intento usar
printf
desde mi código de ensamblador, este es un ejemplo mínimo que solo debería imprimir
hello
a stdout:
.section .rodata
hello:
.ascii "hello/n/0"
.section .text
.globl _start
_start:
movq $hello, %rdi #first parameter
xorl %eax, %eax #0 - number of used vector registers
call printf
#exit
movq $60, %rax
movq $0, %rdi
syscall
Lo construyo con
gcc -nostdlib try_printf.s -o try_printf -lc
y cuando lo ejecuto, parece funcionar: la cadena
hello
se imprime y el estado de salida es
0
:
XXX$ ./try_printf
hello
XXX$ echo $?
0
XXX$
Pero cuando trato de capturar el texto, es obvio que algo no funciona correctamente:
XXX$ output=$(./try_printf)
XXX$ echo $output
XXX$
La
output
variable debe tener el valor
hello
, pero está vacía.
¿Qué tiene de malo mi uso de
printf
?
La biblioteca estándar de C a menudo contiene código de inicialización para las secuencias de E / S estándar: código de inicialización que está omitiendo al definir su propio punto de entrada.
Intente definir
main
lugar de
_start
:
.globl main
main:
# _start code here.
y luego compile con
gcc try_printf.s -o try_printf
(
es decir
, sin
-nostdlib
).
Use
call exit
lugar de una
_exit
raw _exit después de usar funciones stdio como printf.
Como explicó Michael, está bien vincular la biblioteca C dinámicamente. Así es también como se introduce en el libro "Programación de abajo hacia arriba" (ver capítulo 8).
Sin embargo, es importante llamar a
exit
desde la biblioteca C para finalizar el programa y no
exit-syscall
, que fue lo que hice mal al llamar a
exit-syscall
.
Según lo insinuado por Michael, la salida hace una gran cantidad de
limpieza
como chorros de agua.
Eso es lo que sucedió: como se explica here , la biblioteca C almacena las secuencias estándar de la siguiente manera:
- Sin almacenamiento en búfer para error estándar.
- Si la salida / entrada estándar es una terminal, está almacenada en línea.
- Si la salida / entrada estándar no es un terminal, está completamente protegida y, por lo tanto, se necesita una descarga al final de la escritura.
El caso aplicable se decide cuando se llama a
printf
por primera vez para una secuencia.
Por lo tanto, si se llama a
printf_try
directamente en el terminal, se puede ver la salida del programa porque
hello
tiene
/n
al final (que activa el vaciado en el modo de almacenamiento en línea) y es un terminal, también el 2. caso.
Llamar a
printf_try
través de
$(./printf_try)
significa que el stdout ya no es un terminal (en realidad no sé si es un archivo temporal o un archivo de memoria) y, por lo tanto, el caso 3. está en vigor: hay necesidad de una descarga explícita, es decir, una llamada a C-
exit
.