x86_64 Сборка Linux Системный вызов Путаница

В настоящее время я изучаю ассемблер на Linux. Я использую книгу "Программирование с нуля", и все примеры 32-битные. Моя ОС 64-битная, и я пытаюсь сделать все примеры в 64-битной. У меня возникли проблемы, однако:

.section .data

.section .text
.global _start
_start:
movq $60, %rax
movq $2, %rbx
int $0x80

Это просто вызывает системный вызов выхода из Linux или должен. Вместо этого это вызывает ошибку SEG, и когда я вместо этого делаю это

.section .data

.section .text
.global _start
_start:
movq $1, %rax
movq $2, %rbx
int $0x80

оно работает. Очевидно, проблема заключается в значении, которое я перемещаю в%rax. Значение $1, которое я использую во втором примере, - это то, что "Программирование с нуля" сказал использовать, однако несколько источников в Интернете сказали, что номер 64-битного системного вызова составляет $60. Ссылка Что я делаю не так? Кроме того, какие другие вопросы я должен остерегаться и что я должен использовать для справки? На всякий случай, если вам нужно знать, я в главе 5 "Программирование с нуля".

5 ответов

Решение

Вы столкнулись с одной удивительной разницей между i386 и x86_64: они не используют один и тот же механизм системных вызовов. Правильный код:

movq $60, %rax
movq $2,  %rdi   ; not %rbx!
syscall

Прерывание 0x80 всегда вызывает 32-битные системные вызовы. Он используется для запуска 32-битных приложений в 64-битных системах.

В целях обучения вам, вероятно, следует стараться точно следовать учебному руководству, а не переводить на лету 64-разрядную версию - есть несколько других существенных поведенческих различий, с которыми вы, вероятно, столкнетесь. Когда вы ознакомитесь с i386, вы можете выбрать x86_64 отдельно.

Прочтите это. Каковы соглашения о вызовах для системных вызовов UNIX и Linux на x86-64?

и обратите внимание, что с помощью int 0x80 для системного вызова в системах x64 - старый уровень совместимости. ты должен использовать syscall инструкция по x64 системам.

вы все еще можете использовать этот старый метод, но вам нужно скомпилировать ваши двоичные файлы в режиме x86, подробности смотрите в руководстве к вашему компилятору / ассемблеру.

duskwuff правильно указывает на то, что механизм для системных вызовов отличается для 64-битной x86 Linux и 32-битной Linux.

Однако этот ответ является неполным и вводящим в заблуждение по нескольким причинам:

  • Это изменение было фактически введено до того, как 64-битные системы стали популярными, мотивируя это тем, что int 0x80 был очень медленным на Pentium 4. Линус Торвальдс закодировал решение, используя SYSENTER / SYSEXIT инструкции (которые были введены Intel в эпоху Pentium Pro, но были ошибочными и не давали практической пользы). Поэтому современные 32-битные системы Linux на самом деле используют SYSENTER не int 0x80,
  • 64-битные ядра Linux x86 на самом деле не используют SYSENTER а также SYSEXIT, Они на самом деле используют очень похожие SYSCALL / SYSRET инструкции.

Как указано в комментариях, SYSENTER на самом деле не работает на многих 64-битных системах Linux, а именно на 64-битных системах AMD.

Это заведомо запутанная ситуация. Ужасные детали здесь, но к чему это сводится это:

Для 32-битного ядра SYSENTER/SYSEXIT являются единственной совместимой парой [между процессорами AMD и Intel]

Только для 64-битного ядра в длинном режиме… SYSCALL/SYSRET - единственная совместимая пара [между процессорами AMD и Intel]

Похоже, что на процессоре Intel в 64-битном режиме вы можете избежать использования SYSENTER потому что он делает то же самое, что и SYSCALL Однако это не относится к системам AMD.

Итог: всегда используйте SYSCALL в Linux на 64-битных системах x86. Это то, что на самом деле указывает ABI x86-64. (Смотрите этот отличный ответ вики для более подробной информации.)

Многое изменилось между i386 и x86_64, включая как инструкцию, используемую для входа в ядро, так и регистры, используемые для передачи аргументов системного вызова. Вот код, эквивалентный вашему:

.section .data

.section .text
.global _start
_start:
movq $60, %rax
movq $2, %rdi
syscall

Цитируя этот ответ на связанный вопрос:

Номера системных вызовов находятся в исходном коде Linux в каталоге arch/x86/include/asm/unistd_64.h. Номер системного вызова передается в регистр rax. Параметры указаны в rdi, rsi, rdx, r10, r8, r9. Вызов вызывается с помощью инструкции "syscall". Системный вызов перезаписывает регистр rcx. Возвращение в rax.

Если вы проверите /usr/include/asm/unistd_32.h выход соответствует 1 но в/usr/include/asm/unistd_64.h выход соответствует 60,

Другие вопросы по тегам