Сбой базовой программы ввода-вывода NASM

Следуя этой теме, Как мне прочитать односимвольный ввод с клавиатуры с помощью nasm (сборки) в ubuntu?, я пытаюсь скомпилировать программу, которая повторяет ввод в NASM. Я сделал следующие файлы:

my_load2.asm:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
call canonical_off
call canonical_on

testio.inc:

termios:        times 36 db 0
stdin:          equ 0
ICANON:         equ 1<<1
ECHO:           equ 1<<3

canonical_off:
        call read_stdin_termios

        ; clear canonical bit in local mode flags
        push rax
        mov eax, ICANON
        not eax
        and [termios+12], eax
        pop rax

        call write_stdin_termios
        ret

echo_off:
        call read_stdin_termios

        ; clear echo bit in local mode flags
        push rax
        mov eax, ECHO
        not eax
        and [termios+12], eax
        pop rax

        call write_stdin_termios
        ret

canonical_on:
        call read_stdin_termios

        ; set canonical bit in local mode flags
        or dword [termios+12], ICANON

        call write_stdin_termios
        ret

echo_on:
        call read_stdin_termios

        ; set echo bit in local mode flags
        or dword [termios+12], ECHO

        call write_stdin_termios
        ret

read_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5401h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

write_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5402h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

Потом бегу:

[root@localhost asm]# nasm -f elf64 my_load2.asm 
[root@localhost asm]# ld -m elfx86_64 my_load2.o -o my_load2

Когда я пытаюсь запустить его, я получаю:

[root@localhost asm]# ./my_load2
Segmentation fault

Отладчик говорит:

(gdb) run
Starting program: /root/asm/my_load2

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000b1 in canonical_off ()

Может кто-нибудь объяснить, почему он вылетает без шага «импорта»? Кроме того, я запускаю RHEL в Virtualbox под 64-разрядной версией Win7. Может ли это вызвать проблемы с компиляцией?


person Community    schedule 25.11.2013    source источник
comment
В коде, который вы публикуете, не определен символ write_stdin_termios. У вас может быть опубликована неполная версия кода.   -  person starrify    schedule 25.11.2013
comment
Вы никогда не выходите из своей программы.   -  person Daniel Kamil Kozar    schedule 25.11.2013
comment
Исправлено. Я пытаюсь скомпилировать полный код.   -  person    schedule 25.11.2013
comment
Тот факт, что я не выхожу, не объясняет, почему он вылетает на canonical_off, но не после этого.   -  person    schedule 25.11.2013
comment
Ваша termios структура находится в section .text - постоянной памяти. Помогло бы помещение в section .data?   -  person Frank Kotler    schedule 25.11.2013
comment
Разве режим эмуляции для команды ld не должен быть elf_x86_64 вместо elfx86_64?   -  person Richard Fearn    schedule 30.12.2013


Ответы (1)


Во-первых, давайте рассмотрим проблему отказа от выхода, о которой говорил Даниэль. Закомментируем две call инструкции, так что программа практически ничего не делает:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
;call canonical_off
;call canonical_on

Когда мы запускаем это:

$ ./my_load2 
Segmentation fault (core dumped)

Он все еще умирает! Даниил прав - нужно выходить:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
;call canonical_off
;call canonical_on

mov eax, 1
mov ebx, 0
int 0x80

В этот раз:

$ ./my_load2
$ 

Нет segfault. Итак, раскомментируем calls:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
call canonical_off
call canonical_on

mov eax, 1
mov ebx, 0
int 0x80

И снова запускаем:

$ ./my_load2
Segmentation fault (core dumped)

Мы снова получаем segfault. Но, по крайней мере, мы можем быть (достаточно) уверены, что это исходит изнутри одной из подпрограмм called.

Также довольно информативен запуск исполняемого файла с strace:

$ strace ./my_load2
execve("./my_load2", ["./my_load2"], [/* 57 vars */]) = 0
setsockopt(0, SOL_IP, 0x400080 /* IP_??? */, NULL, 0) = -1 EFAULT (Bad address)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x40008c} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

Строка setsockopt связана с ioctl запросом в read_stdin_termios. strace сообщает нам, что возвращаемое значение было EFAULT. Справочная страница setsockopt(2) сообщает нам, что это происходит, когда:

Адрес, на который указывает optval, не находится в допустимой части адресного пространства процесса.

Фактически это говорит нам о том, что блок памяти, в который записана структура termios, доступен только для чтения. Фрэнк прав; все в программе, включая пространство termios и весь код, находится в разделе .text только для чтения. Вы можете увидеть это с помощью:

$ objdump -h my_load2.o

my_load2.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000000cd  0000000000000000  0000000000000000  000001c0  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

т.е. есть только один раздел, .text, и это READONLY.

Однако строка, которая на самом деле вызывает segfault, такова:

and [termios+12], eax

потому что он также пытается записывать в (только для чтения) termios память.

Самый быстрый способ исправить это - поместить termios память в раздел .data, а все остальное - в раздел .text:

section .data

termios:        times 36 db 0

section .text

stdin:          equ 0
ICANON:         equ 1<<1
ECHO:           equ 1<<3

canonical_off:
        call read_stdin_termios

[...]

(stdin, ICANON и ECHO могут находиться в разделе .text только для чтения, потому что они используются просто как константы, т.е. мы не записываем в эти биты памяти.)

Внеся эти изменения:

$ ./my_load2 
$ 

Программа запускается и завершается нормально.

person Richard Fearn    schedule 30.12.2013
comment
В 64-битном коде, как правило, следует избегать int 0x80. Используйте mov eax, 60 / syscall, чтобы сделать _exit(edi) системный вызов. - person Peter Cordes; 06.10.2020