Терминал печатает символ «?» при операции вычитания x86

#assembly #x86 #nasm #i386

# #сборка #x86 #nasm #i386

Вопрос:

Собираем некоторую сборку x86 для изучения простой задачи вычитания, и я сталкиваюсь с проблемой. Ниже приведен мой код, который я пробовал до сих пор:

 section .text
    global _start


_start:
    MOV EAX, 4          ; sys_write call
    MOV EBX, 1          ; file descriptor
    MOV ECX, dispMsg
    MOV EDX, dispMsgLen
    INT 0x80            ; execute instruction set

    MOV EAX, 3          ; sys_read call
    MOV EBX, 2
    MOV [tempf], ECX    ; move user-input to tempf
    MOV EDX, 3
    INT 0x80

    MOV EAX, [tempf]    ; move tempf to EAX
    SUB EAX, '0'        ; convert EAX to decimal

    MOV EBX, 32         ; move value 32 to EBX
    SUB EBX, '0'        ; convert EBX to decimal

    SUB EAX, EBX        ; sub EAX to EBX
    ADD EAX, '0'        ; convert EAX to ascii

    MOV [res], EAX      ; move EAX to res

    MOV EAX, 4
    MOV EBX, 1
    MOV ECX, [res]
    MOV EDX, 3
    INT 0x80

    MOV EAX, 1          ; sys_exit call
    MOV EBX, 1
    INT 0x80


section .bss
    tempf resb 3
    res resb 3


section .data
    dispMsg db 'Input a value', 0xa
    dispMsgLen equ $-dispMsg
 

После компиляции и запуска пользователю предлагается ввести число. Когда они нажимают enter, я ожидаю, что код вычитает ввод пользователя на 32 и выведет результат на консоль. Однако я получаю символ «?» в ромбе в консоли только тогда, когда пользователь вводит число и нажимает enter. Я читал, что это может быть связано с тем, что в переменной [res] ничего нет для печати, но я уверен, что я сохранил разницу между user input - 32 в переменной res.

Любая помощь приветствуется, спасибо, ребята.

Комментарии:

1. Возможно, вы не поняли разницу между кодом ASCII для цифровых символов и целочисленными значениями. mov ebx, 32 устанавливает EBX = 32 = 0x20 = 0b10000. В самом процессоре он хранится в виде двоичных битов. Вычитание '0' (число 48 десятичное, иначе 0x30 шестнадцатеричное) оставляет EBX = 32-48 = -16. Итак, вы вычитаете это из кода ASCII для первого байта того, что вы ввели. Используйте отладчик, чтобы просмотреть младший байт EAX (AL), в который вы сохраняете res .

2. Кстати, вы считываете входные данные из fd=2 (stderr), что странно. Это работает в обычном терминале, потому что все 3 стандартных файловых дескриптора (0, 1 и 2) являются дубликатами одного и того же описания файла для чтения и записи, открытого в tty, когда вы запускаете программу из оболочки bash.

Ответ №1:

Итак, ваш read системный вызов и его последствия не имеют особого смысла.

     MOV EAX, 3          ; sys_read call
    MOV EBX, 2
 

Почему вы пытаетесь прочитать из файлового дескриптора 2? Это стандартная ошибка, и она не обязательно должна быть открыта для чтения. Если вы хотите получить ввод от пользователя, считайте из стандартного ввода, дескриптор файла 0.

     MOV [tempf], ECX    ; move user-input to tempf
 

Это не то, что это делает; он принимает (мусорное) значение, которое находится в ECX (которое, кстати, составляет четыре байта), и сохраняет его по адресу tempf (который, кстати, составляет всего три байта). Я думаю, вы хотели написать MOV ECX, tempf . Обратите внимание на отсутствие скобок, потому что мы хотим, чтобы адрес tempf был в ECX, а не значение, расположенное по этому адресу в памяти.

     MOV EDX, 3
 

Итак, вы собираетесь прочитать три байта. Я не совсем уверен, почему три, но…

     INT 0x80

    MOV EAX, [tempf]    ; move tempf to EAX
 

… это загружает четыре байта, поскольку EAX это 32-разрядный регистр.

Если вам просто нужна одна цифра от пользователя (и это все, что ваш код, похоже, способен обрабатывать), тогда вам, вероятно, следует:

  • просто прочитайте один байт
  • загрузите один байт в 8-разрядный регистр, например MOV AL, [tempf]
  • С этого момента работайте с этим 8-разрядным регистром, например SUB AL, '0' , и так далее.