Генерирование таблицы исключений и кода обработки
Директива .section ассемблера GNU Assembler позволяет программистам указать, в какой секции исполняемого файла содержится код, идущий следом за директивой. Как мы увидим в главе 20, исполняемый файл включает в себя сегмент кода, который, в свою очередь, может быть разбит на секции. Приведем ассемблерные инструкции, которые добавляют запись в таблицу исключений (атрибут «а» показывает, что секция должна быть загружен в память вместе с остальной частью образа ядра:
.section ex_table, «а»
. long
адрес_плохой_инструкции
адрес_кода_обработки .previous
Директива .previous заставляет ассемблер вставить последующий код в секцию, которая была активной, когда была встречена последняя директива
.section.
Вернемся К функциям get_user_l , get_user_2 И get_user_4 , рассмотренным ранее. Инструкции, которые обращаются к адресному пространству процесса, имеют метки 1, 2 и з:
get_user_l:
[.]
1: movzbl (еах), edx [.]
get_user_2:
[.]
2: movzwl -l(eax), edx [.]
get_user_4:
[.]
3: movl -3(eax), edx [.]
bad_get_user:
xorl edx, edx movl $-EFAULT, eax ret
.section ex_table,»a»
.long lb, bad_get_user .long 2b, bad_get_user .long 3b, bad_get_user .previous
Каждая запись таблицы исключений состоит из двух меток. Первая представляет собой число с суффиксом ь, показывающим, что метка находится «сзади» (от англ. «backward» — «назад»), т. е. расположена в одной из предыдущих строчек программы. Код обработки является общим для всех трех функций и помечен как bad get user. Если ошибка обращения к странице будет вызвана инструкцией с меткой 1, 2 или 3, выполнится код обработки. Он просто возвращает -efault процессу, сделавшему системный вызов.
Другие функции ядра, действующие в адресном пространстве режима пользователя, тоже применяют технику кода обработки. Рассмотрим в качестве примера макрос strien_user (string). Он возвращает либо длину строки с завершающим нулем, переданной в качестве параметра системному вызову, либо 0 в случае ошибки. Макрос генерирует следующие ассемблерные инструкции:
movl $0, еах movl $0x7fffffff, есх movl есх, ebx movl string, edi 0: repne; scasb
subl ecx, ebx movl ebx, eax
1:
.section .fixup,»ax”
2: xorl eax, eax jmp lb .previous
.section ex_table,»a»
.long Ob, 2b .previous
Регистры ecx и ebx инициализируются значением 0x7fffffff, представляющим максимальную разрешенную длину строки в адресном пространстве режима пользователя. Ассемблерные инструкции repne;scasb итеративно сканируют строку, на которую указывает регистр edi, в поисках нулевого значения (символа \о, обозначающего конец строки). Поскольку инструкция scasb уменьшает регистр есх на каждой итерации, регистр еах, в конечном счете, будет содержать количество байтов в просканированной строке (то есть ее длину).
Код обработки исключения, содержащийся в этом макросе, вставлен в секцию . fixup. Атрибуты «ах» показывают, что секция должна быть загружена в память и содержит выполняемый код. Если ошибка обращения к странице будет вызвана инструкцией с меткой о, выполнится код обработки исключения. Он просто загружает 0 в регистр еах, заставляя макрос возвратить код ошибки 0 вместо длины строки, а затем переходит на метку 1, т. е. на инструкцию, следующую за макросом.
Вторая директива .section добавляет запись, содержащую адрес пары инструкций repne; scasb и адрес соответствующего кода обработки в секцию
ex_table.