перенаправление результата запуска ассемблерного кода в linux в текстовый файл - PullRequest
2 голосов
/ 12 ноября 2011

Я пытаюсь написать скрипт Python, чтобы проверить вывод некоторого различного кода, который я написал в сборке, на ожидаемый результат.Однако я испытываю трудности с перенаправлением вывода в файл.Я написал следующее:

extern printf
LINUX   equ     80H     ; interupt number for entering Linux kernel
EXIT    equ     1       ; Linux system call 1 i.e. exit ()
section .data
    intfmt: db "%ld", 10, 0

segment .text
    global  main


main:
    push rax
    push rsi
    push rdi
    mov rsi, 10
    mov rdi, intfmt
    xor rax, rax
    call printf
    pop rdi
    pop rsi
    pop rax 
    call os_return      ; return to operating system


os_return:
    mov  rax, EXIT      ; Linux system call 1 i.e. exit ()
    mov  rbx, 0     ; Error code 0 i.e. no errors
    mov rcx, 5
    int  LINUX      ; Interrupt Linux kernel

Затем я продолжаю делать следующее в консоли:

nasm -f elf64 basic.asm
gcc -m64 -o basic basic.o
./basic

, который выводит 10 на экран.Однако, если я введу

./basic > basic.txt
cat basic.txt

basic.txt появится как пустой файл.Моя общая цель - написать сценарий оболочки, который перебирает каждый файл сборки для компиляции и запуска файла, а затем перенаправляет вывод этого сценария в файл.Однако я не могу сделать это, пока не смогу заставить его работать с одним файлом.Мне было интересно, что это было связано с моим звонком в printf?Хотя у меня была иллюзия, что printf пишет в STDOUT.

Заранее спасибо!

1 Ответ

7 голосов
/ 12 ноября 2011

Ваше перенаправление правильно; проблема должна быть в сборке, которую вы создаете.

Инструмент для устранения таких проблем - strace. Запуск вашей программы под strace, показывает:

strace ./basic
...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5bb8da000
write(1, "10\n", 3)                     = 3
10
write(1, "z\377n\f\377\177\0\0\0\0\0\0\0\0\0\0\202\377n\f\377\177\0\0\362\377n\f\377\177\0\0"..., 139905561665008 <unfinished ... exit status 0>

Вы можете четко видеть желаемый результат, но также и некоторые "случайные" записи. Откуда эта запись?

ГБД на помощь:

gdb  -q ./basic
Reading symbols from /tmp/basic...done.

(gdb) catch syscall write
Catchpoint 1 (syscall 'write' [1])
(gdb) r

Catchpoint 1 (call to syscall 'write'), 0x00007ffff7b32500 in __write_nocancel ()
(gdb) bt
#0  0x00007ffff7b32500 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:82
#1  0x00007ffff7acd133 in _IO_new_file_write (f=0x7ffff7dd7780, data=0x7ffff7ff8000, n=3) at fileops.c:1276
#2  0x00007ffff7ace785 in new_do_write (fp=0x7ffff7dd7780, data=0x7ffff7ff8000 "10\n", to_do=3) at fileops.c:530
#3  _IO_new_do_write (fp=0x7ffff7dd7780, data=0x7ffff7ff8000 "10\n", to_do=3) at fileops.c:503
#4  0x00007ffff7accd9e in _IO_new_file_xsputn (f=0x7ffff7dd7780, data=0x601023, n=1) at fileops.c:1358
#5  0x00007ffff7a9f9c8 in _IO_vfprintf_internal (s=0x7ffff7dd7780, format=<value optimized out>, ap=0x7fffffffda20) at vfprintf.c:1644
#6  0x00007ffff7aaa53a in __printf (format=0x7ffff7ff8000 "10\n") at printf.c:35
#7  0x000000000040054f in main ()

Хорошо, это ожидаемый звонок для записи.

(gdb) c
10

Catchpoint 1 (returned from syscall 'write'), 0x00007ffff7b32500 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:82
82  in ../sysdeps/unix/syscall-template.S

Это просто возвращение из системного вызова. Писать удалось? (Мы знаем, что это было сделано, так как мы видим его результаты выше, но давайте подтвердим.)

(gdb) p $rax
$1 = 3

Хорошо. Написать написал ожидаемых 3 символов.

(gdb) c

Catchpoint 1 (call to syscall 'write'), 0x0000000000400577 in os_return ()

Это запись, которую мы не ожидали. Откуда?

(gdb) bt
#0  0x0000000000400577 in os_return ()
#1  0x0000000000400557 in main ()
(gdb) disas
Dump of assembler code for function os_return:
   0x0000000000400557 <+0>: movabs $0x1,%rax
   0x0000000000400561 <+10>:    movabs $0x0,%rbx
   0x000000000040056b <+20>:    movabs $0x5,%rcx
   0x0000000000400575 <+30>:    int    $0x80
=> 0x0000000000400577 <+32>:    nop
   0x0000000000400578 <+33>:    nop
   0x0000000000400579 <+34>:    nop
   0x000000000040057a <+35>:    nop
   0x000000000040057b <+36>:    nop
   0x000000000040057c <+37>:    nop
   0x000000000040057d <+38>:    nop
   0x000000000040057e <+39>:    nop
   0x000000000040057f <+40>:    nop
End of assembler dump.
(gdb) quit

Итак, ваш системный вызов выполнил write(2) вместо ожидаемого exit(2). Почему это случилось?

Поскольку вы определили EXIT неправильно:

grep 'define .*NR_exit' /usr/include/asm/unistd*.h
/usr/include/asm/unistd_32.h:#define __NR_exit                1
/usr/include/asm/unistd_32.h:#define __NR_exit_group          252
/usr/include/asm/unistd_64.h:#define __NR_exit                60
/usr/include/asm/unistd_64.h:#define __NR_exit_group          231

Сверху можно сказать, что EXIT должно быть 1 в 32-битном режиме, но 60 в 64-битном режиме.

А как насчет NR_write? Это 1 в 64-битном режиме?

grep 'define .*NR_write' /usr/include/asm/unistd_64.h 
#define __NR_write              1
#define __NR_writev             20

Действительно, это так. Итак, мы решили, «откуда взялась шальная запись?» головоломка. Исправив EXIT до 60 и перезапустив под strace, теперь мы видим:

...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5bb8da000
write(1, "10\n", 3)                     = 3
10
_exit(1)                                = ?

Это все еще не правильно. Мы должны звонить _exit(0), а не _exit(1). Взгляд на x86_64 ABI показывает, что ваш регистр используется некорректно: номер системного вызова должен быть в %rax, а аргументы в %rdi, %rsi, %rdx и т. Д.

Исправив это (и удалив поддельные mov rcx, 5), мы наконец-то получим желаемый результат из strace:

...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa5bb8da000
write(1, "10\n", 3)                     = 3
10
_exit(0)                                = ?

Итак, , теперь , мы готовы посмотреть, не исправили ли вышеупомянутые исправления проблему перенаправления.

Повторный запуск в режиме strace с перенаправлением вывода:

strace ./basic > t
...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f08161eb000
_exit(0)                                = ?

Очевидно, что наш звонок на write отсутствует. Куда это делось?

Ну, вывод stdout по умолчанию буферизован строкой и полностью буферизируется при перенаправлении в файл. Возможно, нам не хватает fflush звонка?

Действительно, добавление вызова к fflush(NULL) непосредственно перед выходом решает проблему:

...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8afd450000
write(1, "10\n", 3)                     = 3
_exit(0)                                = ?

Я надеюсь, что вы узнали что-то сегодня (я сделал; -)

...