Пытаюсь выучить системные вызовы в сборке от эта страница на tutorialspoint.
На этой странице есть ассемблерный код, который считывает вводимые пользователем данные и запрашивает их. Что меня сбивает с толку и, к сожалению, не работает:
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start: ;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
После выполнения кода программа никогда не запрашивает ввод - она немедленно вызывает выход из системы.
Я нахожу некоторые части кода сбивающими с толку и предполагаю, что им, возможно, придется что-то делать с ошибкой:
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
В приведенном выше коде для eax
(32-битный регистр накопителя) имеет смысл использовать 3
, поскольку он выполняет системный вызов sys_read
. edx
, вероятно, определяет тип данных, и, учитывая, что мы сохраняем целое число, 5
имеет смысл.
Но 32-битный базовый регистр должен содержать индекс файлового дескриптора (где stdin=0
, stdout=1
, stderr=2
). Но почему ebx=2
в приведенном выше коде?
Извините, если вопрос слишком простой, но почему код не работает? Что-то не так с неправильным выбором входов в регистры? то есть то, что я упоминал выше.
stderr может использоваться как входной поток: stackoverflow.com/a/51308591/3512216
Программа работает но надо собрать и свяжите его как 32-битный исполняемый файл. Я думаю, вы можете прочитать первую часть учебника, но затем перейти к соглашению о вызовах 64-битных системных вызовов, получить список системных вызовов и использовать их, как в C. Раздел 2 руководства описывает оболочки вокруг syscalls, поиск в Google имени системного вызова также даст некоторую документацию. В худшем случае - исходники ядра.
@MargaretBloom: Мой Debian Jessie принимает и запускает эту программу как есть - как в 32-битном, так и в 64-битном режиме.
@rkhb Это не странно, это также происходит в моей CentOS, но это зависит от переключателей по умолчанию, с которыми ваш дистрибутив настроил GCC. См. этот отличный ответ. Я догадался, что это может быть проблема с OP.
@rkhb, как всегда в сборке, тот факт, что «программа работала, как ожидалось», мало что говорит о ее правильности (это, безусловно, ближе к правильному коду, чем программа, которая не дает правильного наблюдаемого поведения, но может быть еще далеким от правильного).
@Margaret Bloom Что-то не так с прерыванием работы ядра? Я слышал, что 0x80 для ядра в каждом дистрибутиве Linux. Я выполнил этот код на нескольких онлайн-ассемблерах nasm, но все они дали одинаковые результаты. На указанной выше странице также есть ссылка для онлайн-сборки их кода (который должен иметь оптимальные настройки для этого определенного кода), но она все еще не работает.
@Michael Но какой смысл использовать индекс stderr в качестве дескриптора ввода, когда есть stdin? Есть ли преимущество?
@ShellRox int 0x80
- это старый 32-битный интерфейс системных вызовов. 64-битная версия использует syscall
. Взгляните на связанный ответ, который я дал rkhb;)
@MargaretBloom Приношу извинения за путаницу, я попытался заменить int 0x80
на syscall
в качестве эксперимента, но это не сработало, возникла недопустимая ошибка инструкции. Из последней ссылки я понял, что int 0x80
будет работать, если данные в указателях не превышают 32 бит.
@ShellRox syscall
- другое дело, лучше найти по нему учебник. Точно! int 0x80
будет работать, если все указатели уместятся в 32-битном формате :) Но вы должны убедиться, что это так.
@MargaretBloom: почему здесь кто-то говорит о портировании на x86-64? Это допустимая программа для i386 Linux, корректно использующая устаревший 32-разрядный ABI int 0x80
. Это также могло бы работать, если бы оно было построено как 64-битный статический исполняемый файл, потому что модель кода по умолчанию помещает статические символы в младшие 32 бита адресного пространства. Он потерпит неудачу только в том случае, если он построен как 64-битный исполняемый файл PIE (который объясняет наблюдаемые симптомы) или на ядрах без CONFIG_IA32_EMULATION, и в этом случае он будет segfault или что-то в этом роде вместо чистого выхода.
Конечно, это довольно дрянная программа. Он читает из stderr
без всякой причины и выгружает весь 5-байтовый буфер вместо сохранения возвращаемого значения из read(2)
для использования в качестве длины для write(2)
.
@PeterCordes, потому что цель OP - научиться писать программу для Linux на ассемблере. Я предложил либо собрать / связать его как 32-битный, либо перейти на 64-битный, поскольку руководство, которому они следуют, устарело и не учитывает эти проблемы.
Я бы предпочел перейти к другому руководству, поскольку код устарел и немного странный. Спасибо вам за помощь!
@ShellRox о int 0x80
, работающем повсюду ... например, встроенный Linux внутри Windows 10 имеет только 64-битное ядро, поэтому 64-битный двоичный код с использованием int 0x80
там не сработает, даже если он работает с обычной 64-битной установкой Ubuntu (и этот Linux внутри win10 основан на Ubuntu , поэтому можно ожидать, что он будет работать аналогичным образом, но это не так). Конечно, обычный 32-битный двоичный файл не будет работать вообще в такой 64-битной системе, но сообщение об ошибке, вероятно, будет более конкретным, чем общий segfault.
Если вы не перенаправили
stderr
, чтение с него, вероятно, будет работать так же, как чтение сstdin
.