Что происходит, когда команда mov вызывает ошибку страницы с отключенными прерываниями на x86?
Недавно я столкнулся с проблемой в произвольном ядре Linux (2.6.31.5, x86), где copy_to_user периодически не копировал любые байты в пространство пользователя. Он вернет количество байтов, переданных ему, что указывает на то, что он ничего не скопировал. После проверки кода мы обнаружили, что код отключил прерывания при вызове copy_to_user, который нарушает его. После исправления этого вопроса проблема прекратилась. Поскольку проблема возникла нечасто, мне нужно доказать, что отключение прерываний вызвало проблему.
Если вы посмотрите на фрагмент кода ниже из arch/x86/lib/usercopy_32.c rep; movsl копирует слова в пользовательское пространство по счету в CX. Размер обновляется CX при выходе. CX будет 0, если movsl выполняется правильно. Поскольку CX не равен нулю, movs? инструкции не должны выполняться, чтобы соответствовать определению copy_to_user и наблюдаемому поведению.
/* Generic arbitrary sized copy. */
#define __copy_user(to, from, size) \
do { \
int __d0, __d1, __d2; \
__asm__ __volatile__( \
" cmp $7,%0\n" \
" jbe 1f\n" \
" movl %1,%0\n" \
" negl %0\n" \
" andl $7,%0\n" \
" subl %0,%3\n" \
"4: rep; movsb\n" \
" movl %3,%0\n" \
" shrl $2,%0\n" \
" andl $3,%3\n" \
" .align 2,0x90\n" \
"0: rep; movsl\n" \
" movl %3,%0\n" \
"1: rep; movsb\n" \
"2:\n" \
".section .fixup,\"ax\"\n" \
"5: addl %3,%0\n" \
" jmp 2b\n" \
"3: lea 0(%3,%0,4),%0\n" \
" jmp 2b\n" \
".previous\n" \
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 4b,5b\n" \
" .long 0b,3b\n" \
" .long 1b,2b\n" \
".previous" \
: "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2) \
: "3"(size), "0"(size), "1"(to), "2"(from) \
: "memory"); \
} while (0)
2 идеи, которые у меня есть:
- когда прерывания отключены, ошибка страницы не возникает и
затем rep; варисторы? пропускается, ничего не делая. Возвращаемое значение
тогда будет CX или количество, не скопированное в пользовательское пространство, так как
определение и наблюдаемое поведение.
- Ошибка страницы, но linux не может ее обработать, поскольку прерывания отключены, поэтому обработчик ошибок страницы пропускает инструкцию, хотя я не знаю, как это сделает обработчик ошибок страницы. Опять же, в этом случае CX останется неизмененным, и возвращаемое значение будет правильным.
Может ли кто-нибудь указать мне разделы в руководствах Intel, которые определяют это поведение, или указать мне на любой дополнительный источник Linux, который может быть полезен?
Ответы
Ответ 1
Я нашел ответ. Мое предложение № 2 было правильным, и механизм был прямо перед моим лицом. Ошибка страницы происходит, но механизм fixup_exception используется для предоставления механизма исключения/продолжения. В этом разделе добавляются записи в таблицу обработчика исключений:
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 4b,5b\n" \
" .long 0b,3b\n" \
" .long 1b,6b\n" \
".previous" \
Это говорит: если IP-адрес является первой записью, и в обработчике ошибок встречается исключение, установите IP-адрес на второй адрес и продолжайте.
Итак, если исключение происходит при "4:", перейдите к "5:". Если исключение происходит при "0:", то переходите к "3:", и если исключение происходит при переходе "1:" на "6:".
Пропущенный фрагмент находится в do_page_fault() в arch/x86/mm/fault.c:
/*
* If we're in an interrupt, have no user context or are running
* in an atomic region then we must not take the fault:
*/
if (unlikely(in_atomic() || !mm)) {
bad_area_nosemaphore(regs, error_code, address);
return;
}
in_atomic возвращается true, потому что мы находимся в блокировке write_lock_bh()! bad_area_nosemaphore в конечном итоге делает исправление.
Если возникла ошибка page_fault (что было маловероятно, из-за концепции рабочего пространства), вызов функции завершился бы неудачей и выскочил из макроса __copy_user, при этом размер незанятых байтов был бы установлен на размер, потому что приостановка была отключена.
Ответ 2
Ошибки страниц не являются прерываниями масок. Фактически, они не являются технически прерываниями вообще, а скорее исключениями, хотя я согласен, что разница более семантическая.
Причина, по которой ваша copy_to_user не удалась, когда вы вызывали ее в атомарном контексте с отключенными прерываниями, состоит в том, что код имеет явную проверку для этого.
См. http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L575