Видео ролики бесплатно онлайн

Смотреть красивое видео

Официальный сайт netfuncards 24/7/365

Смотреть видео бесплатно

17.08.10 02:55 dementiy

CodingIBM. Kernel command using Linux system calls

На днях наткнулся на статью Тима Джонса (Tim Jones) под названием "Kernel command using Linux system calls" и поначалу обрадовался, что автор решил вдохнуть в статью новую жизнь (так как первый раз она была опубликована в 2007 году), но тут же пришло разочарование, увидев, что статья не сильно изменила свой облик и все еще шла речь о ядре 2.6.18 и более ранних версиях. Было принято решение о том, чтобы перевести статью (перевод получился крайне поршивый) и изменить ее содержимое, наполнив более актуальной информацией, то есть для ядер с версии 2.6.32 (на более ранних версиях я не проводил тесты) и до версии 2.6.35. Итак, что из этого вышло судить Вам...

Системные вызовы Linux® — мы используем их каждый день. Но знаете ли Вы как системный вызов осуществляет переход из пространства пользователя в пространство ядра? Исследуем system call interface (SCI), изучим как добавить новые системные вызовы и узнаем утилиты связанные с SCI.

Системные вызовы (system calls) являются интерфейсом между приложениями пространства пользователя и сервисами, которые предоставляет ядро. Вызов не может быть осуществлен напрямую, так как сервисы предоставляются ядром (реализованы в ядре); вместо этого Вам необходимо использовать процесс пересечения границы user-space/kernel. Способы сделать это отличаются в зависимости от конкретной архитектуры. По этой причине я буду придерживаться наиболее распространенной архитектуры i386.
В этой статье я рассмотрю Linux SCI, продемонстрирую добавление системного вызова в ядре 2.6.32 и более поздних (до ядра 2.6.35) и затем вызов этой функции из пространства пользователя. Я также рассмотрю некоторые из функций, которые Вы можете счесть полезными для реализации системного вызова. Наконец я опишу некоторые вспомогательные механизмы связанные с системными вызовами, такие как трассировка.

SCI

Реализация системных вызовов в Linux различается в зависимости от архитектуры, но она также может отличатся в рамках одной архитектуры. Например, старые процессоры x86 использовали механизм прерываний для перехода из пространства пользователя в пространство ядра, но новые процессоры IA-32 обеспечивают инструкции, которые оптимизируют этот переход (используя инструкции sysenter и sysexit). Поскольку существует очень много вариантов и конечный результат является довольно сложным, то я буду придерживаться поверхностного обсуждения.

Вы не должны полностью понимать строение SCI, чтобы изменять его, поэтому я рассмотрю простой вариант работы системного вызова (см. Рис.1). Каждый системный вызов мультиплексируется (multiplexed) в ядро через единую точку входа. Регистр eax используется для определения конкретного системного вызова, который должен быть вызван и который определен в библиотеке языка C (для вызова из пользовательского приложения). Когда библиотека C загрузила индекс (номер) системного вызова и необходимые аргументы, вызывается программное прерывание (прерывание 0x80), которое приводит к выполнению (через обработчик прерываний) функции system_call. Эта функция обрабатывает все системные вызовы, которые определяются содержимым регистра eax. После нескольких простых тестов, фактически, системный вызов вызывается, используя system_call_table и номер, содержащийся в eax. После возвращения из системного вызова, в конечном счете достигается syscall_exit и вызывается resume_userspace для перехода назад в пространство пользователя. Выполнение возобновляется в библиотеке C, которая затем возвращается к пользовательскому приложению.


Рис.1. Упрощенная схема системного вызова, использующего прерывание

В основе SCI лежит таблица демультиплексирования системного вызова. Эта таблица показана на Рис.2, используя номер в eax для определения, какой системный вызов должен быть вызван из таблицы (system_call_table). Также показан простой пример содержимого этой таблицы и расположение системных вызовов.


Рис.2. Таблица системных вызовов и различные связи

Добавление системного вызова Linux

Демультиплексирование системного вызова
Некоторые системные вызовы в дальнейшем демультиплексируются ядром. Например, вызовы сокетов (socket, bind, connect и т.д.) Berkeley Software Distribution (BSD) связаны с одним номером системного вызова (__NR_socketcall), но демультиплексируются в ядре к соответствующему вызову через дополнительный аргумент. См. функцию sys_socketcall в /linux/net/socket.c.

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

Для добавления нового системного вызова в ядро, необходимо проделать три основных шага:
  1. Добавить новую функцию.
  2. Обновить заголовочные файлы.
  3. Обновить таблицу системных вызовов для новой функции.

Создадим новый файл для функций arch/x86/kernel/my_call.c. Первые две функции показанные в Листинге 1 являются простыми примерами системных вызовов. Третья функция более сложная, и использует указатель в аргументах.

Листинг 1. Примеры функций системных вызовов
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <linux/linkage.h>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
asmlinkage long sys_getjiffies( void )
{
        return (long)get_jiffies_64();
}
asmlinkage long sys_diffjiffies( long ujiffies )
{
        return (long)get_jiffies_64() - ujiffies;
}
asmlinkage long sys_pdiffjiffies( long ujiffies,
                                     long __user *presult )
{
        long cur_jiffies = (long)get_jiffies_64();
        long result;
        int err = 0;

        if (presult) {
                result = curr_jiffies — ujiffies;
                err = put_user( result, presult );
        }

        return err ? -EFAULT : 0;
}

В Листинге 1 функции sys_getjiffies и sys_diffjiffies предназначены для мониторинга jiffies. Первая функция возвращает текущий jiffies, а вторая возвращает разницу между текущим и значением, которое передается. Обратите внимание на использование модификатора asmlinkage. Это макрос (определен в arch/x86/include/asm/linkage.h) говорит компилятору положить все аргументы функции в стек.
Kernel jiffies
Ядро Linux поддерживает глобальную переменную jiffies, которая представляет собой число тиков таймера начиная со старта машины. Эта переменная инициализируется нулем и наращивается с каждым прерыванием таймера. Вы можете получить значение jiffies с помощью функции get_jiffies_64, а затем преобразовать это значение в миллисекунды, используя jiffies_to_msecs или микросекунды с помощью jiffies_to_usecs. Время работы системы равно jiffies/HZ секунд, где HZ — количество прерываний системного таймера в секунду (определен в include/asm-generic/param.h). Переменная jiffies и связанные с ней функции определены в include/linux/jiffies.h.

Третья функция принимает два аргумента: long и указатель на long, который определен как __user. Макрос __user просто сообщает компилятору (через noderef), что указатель не должен быть разыменован (так как это не имеет значения в текущем адресном пространстве). Эта функция высчитывает разницу между двумя значениями jiffies, а затем возвращает результат пользователю через указатель пространства пользователя. Функция put_user размещает значение результата в том месте пользовательского пространства, на которое указывает presult. Если произойдет ошибка, то будет возращено значение -EFAULT (плохой адрес; ошибки определены в файле include/asm-generic/errno-base.h), т.о., также уведомив пользователя о результате.

На втором шаге я обновляю заголовочные файлы, чтобы освободить место для новых функций в таблице системных вызовов. Для этого я обновляю заголовочный файл arch/x86/include/asm/unistd_32.h с номером нового системного вызова.

Листинг 2. Изменения в unistd_32.h
1
2
3
4
5
#define __NR_rt_tgsigqueueinfo  335    
#define __NR_perf_event_open    336
#define __NR_getjiffies         337
#define __NR_diffjiffies        338
#define __NR_pdiffjiffies       339


Теперь я имею в ядре системные вызовы и номера для их представления. Все что осталось сделать, это провести зависимости между номерами (таблицей индексов) и самими функциями. Третий шаг — обновление таблицы системных вызовов. Как показано в Листинге 3, я обновил файл arch/x86/kernel/syscall_table_32.S для новых функций, к которым можно будет обратиться по индексам, показанным в Листинге 2.

Листинг 3. Обновление таблицы системных вызовов
1
2
3
4
5
.long sys_rt_tgsigqueueinfo     /* 335 */
.long sys_perf_event_open
.long sys_getjiffies
.long sys_diffjiffies
.long sys_pdiffjiffies

Замечание: Размер этой таблицы определен символической константой NR_syscalls

К этому моменту сделаны все изменения. Теперь осталось перекомпилировать ядро и создать новый образ доступный для загрузки, прежде чем тестировать приложения пространства пользователя.

Замечание: Не забудьте добавить запись о файле с нашими системными вызовами в arch/x86/kernel/Makefile:

obj-y   += my_call.o

Чтение и запись пользовательской памяти

Ядро Linux предоставляет несколько функций, которые Вы можете использовать для перемещения аргументов системного вызова в/из пространства пользователя. Основные методы включают простые функции для базовых типов (такие как get_user и put_user). Для перемещения блоков данных, таких как структуры или массивы, вы можете использовать другой набор функций: copy_from_user и copy_to_user. Для перемещения NULL-terminated строк (т.е. с нулевым символом) есть свои собственные вызовы: strncpy_from_user и strlen_from_user. Вы также можете проверить является ли указатель пространства пользователя действительным через вызов access_ok. Эти функции определены в include/asm-generic/uaccess.h.

Вы можете использовать макрос access_ok, чтобы проверить действительность указателя пространства пользователя для данной операции. Эта функция принимает тип доступа (VERIFY_READ или VERIFY_WRITE) , указатель на блок памяти пространства пользователя и размер блока (в байтах). Функция возвращает нуль в случае успеха:

#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))

Перемещение простых типов между ядром и пространством пользователя (таких как целые или длинные) легко осуществляется при помощи put_user и get_user. Каждый из этих макросов принимает значение и указатель на переменную. Функция get_user перемещает значение пользовательского пространства, на которое указывает адрес (ptr) в указанную переменную ядра (x). Функция put_user перемещает значение, на которое указывает переменная ядра (x) в пространство пользователя по указанному адресу (ptr). Функции возвращают нуль в случае успеха:

1
2
#define get_user(x, ptr)
#define put_user(x, ptr)

Для перемещения больших объектов, таких как структуры или массивы, Вы можете использовать функции copy_from_user и copy_to_user. Эти функции перемещают весь блок данных между пространством пользователя и пространством ядра. Функция copy_from_user перемещает блок данных из пространства пользователя в пространство ядра, а copy_to_user перемещает блок данных из пространства ядра в пространство пользователя:

1
2
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

Наконец вы можете копировать NULL-terminated строки из пространства пользователя в пространство ядра, используя функцию strncpy_from_user. Перед вызовом этой функции Вы должны получить размер строки пользовательского пространства с помощью функции strlen_user:

1
2
static inline long strncpy_from_user(char *dst, const char __user *src, long count)
static inline long strlen_user(const char __user *src)

Эти функции являются основными для перемещения значений между пространством пользователя и пространством ядра. Существуют дополнительные функции (такие как те, которые уменьшают количество выполнения проверок). Вы можете найти эти функции в uaccess.h.

Использование системных вызовов

Использование ядер 2.6.18 и выше
Макрос _syscallN был удален в ядре 2.6.18, так что вместо использования макросов должны быть использована сама функция syscall. Эта функция поддерживает произвольное количество аргументов (int syscall(int number, ...)).

Теперь, когда в ядро включены несколько новых системных вызовов, давайте посмотрим на то, что необходимо для их использования в приложении пространства пользователя. Есть два пути, которые позволяют использовать новые системные вызовы. Первый метод удобный (но не настолько, чтобы вы захотели включить его в производительный код), второй метод традиционный и требует чуть больше усилий.

В первом методе, Вы вызываете Ваши новые системные вызовы через функцию syscall. В функции syscall Вы можете вызвать системный вызов указав его номер и набор аргументов. Например, маленькая программа, показанная в Листинге 4 вызывает Вашу функцию sys_getjiffies, используя ее номер.

Листинг 4. Использование syscall для исполнения системного вызова
1
2
3
4
5
6
7
8
9
10
#include <linux/unistd.h>
#include <sys/syscall.h>
#define __NR_getjiffies 320
int main()
{
        long jiffies;
        jiffies = syscall( __NR_getjiffies );
        printf( "Current jiffies is %lx\n", jiffies );
        return 0;
}


Как Вы можете заметить, функция syscall включает в себя в качестве первого аргумента номер из таблицы системных вызовов. Если есть еще аргументы, то они должны быть указаны после номера системного вызова. Большинство системных вызовов включает символическую константу SYS_, указывая свое отображение на номера __NR_. Например, Вы можете ссылаться на номер __NR_getpid в syscall как:

syscall( SYS_getpid )

Функция syscall архитектурно-зависима и использует механизм передачи управления ядру. Аргумент основывается на отображении индексов __NR в символы SYS_, представленные в /usr/include/bits/syscall.h (определенный, когда libc собран). Никогда не используйте этот файл на прямую; вместо него используйте /usr/include/sys/syscall.h.

Традиционный метод требует, чтобы Вы создавали функции, вызовы которых соответствуют определениям в ядре с точки зрения номера системного вызова (так, чтобы Вы вызывали правильные службы ядра). Linux предоставляет набор макросов для реализации этой возможности. Макрос _syscallN определен в /usr/include/linux/unistd.h и имеет следующий формат:

1
2
3
_syscall0 ( ret-type, func-name )
_syscall1 ( ret-type, func-name, arg1-type, arg1-name )
_syscall2 ( ret-type, func-name, arg1-type, arg1-name, arg2-type, arg2-name )

Пространство пользователя и константы __NR
Обратите внимание, что в Листинге 6 я указал символические константы __NR. Вы можете найти их в /usr/include/asm/unistd_32.h (для стандартных системных вызовов).

Макрос _syscall принимает до шести аргументов (хотя здесь показаны только три).

Здесь показано, как используется макрос _syscall, чтобы сделать Ваши новые системные вызовы видимыми пространству пользователя. Листинг 6 демонстрирует приложение, которое использует все Ваши системные вызовы, определив их с помощью макроса _syscall.

Листинг 5. Использование макроса _syscall для разработки приложений пространства пользователя
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <linux/unistd.h>
#include <sys/syscall.h>
#define __NR_getjiffies         320
#define __NR_diffjiffies        321
#define __NR_pdiffjiffies       322
_syscall0( long, getjiffies );
_syscall1( long, diffjiffies, long, ujiffies );
_syscall2( long, pdiffjiffies, long, ujiffies, long*, presult);
int main()
{
        long jifs, result;
        int err;
        jifs = getjiffies();
        printf( "difference is %lx\n", diffjiffies(jifs) );
        err = pdiffjiffies( jifs, &result );
        if (!err) {
                printf( "difference is %lx\n", result );
        } else {
                printf( "error\n" );
        }
        return 0;
}


Заметьте, что индексы __NR необходимы в этом приложении, потому что макрос _syscall использует имя функции, чтобы создать индекс __NR (getjiffies -> __NR_getjiffies). Результат заключается в том, что теперь Вы можете вызывать Ваши функции ядра, используя их имена, также как и любой другой системный вызов.

Замечание: Чтобы программа из Листинга 5 работала на ядрах 2.6.18 и выше, следует заменить:

1
2
3
_syscall0( long, getjiffies );
_syscall1( long, diffjiffies, long, ujiffies );
_syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );
на
1
2
3
#define getjiffies()                    syscall(__NR_getjiffies)
#define diffjiffies(ujiffies)           syscall(__NR_diffjiffies, ujiffies)
#define pdiffjiffies(ujiffies, presult) syscall(__NR_pdiffjiffies, ujiffies, presult)

Трассировка системных вызовов при помощи strace

Ядро Linux предоставляет удобный способ трассировки системных вызовов, которые вызывает процесс (а также те сигналы, которые получит процесс). Утилита называется strace и вызывается из командной строки, используя в качестве аргумента приложение, которое вы хотите трассировать. Например, если вы хотите знать какие системные вызовы были вызваны в течении работы программы из Листинга 4 (назвав программу call_test1), выполните следующую команду:

strace ./call_test1

Результатом будет довольно большой дамп, показывающий различные системные вызовы, которые были выполнены в контексте вызова команды call_test1. Вы можете увидеть загрузку разделяемых библиотек, отображение памяти и — в конце трассировки — вывод информации о текущем значении jiffies на стандартный вывод.

1
2
3
4
5
6
7
...                                                                                            
SYS_337(0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345, 0xb78d3d20) = 468207     
...                                                                                            
write(1, "Current jiffies is 724ef\n", 25Current jiffies is 724ef                      
) = 25                                                                                         
exit_group(0)                           = ?                                            
$

Трассировка осуществляется в ядре и если текущий системный вызов имеет установленный набор специальных полей (флагов), то вызывается syscall_trace, который приводит к вызову функции do_syscall_trace. Вы также можете найти трассировку функции, как часть системного вызова в arch/x86/kernel/entry_32.S (см. syscall_trace_entry).

Если воспользоваться командой ltrace, то можно получить чуть более интересный результат:

1
2
3
4
5
6
$ ltrace -S ./call_test1                                                                       
...                                                                                            
syscall(337,  0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345 <unfinished...>     
SYS_337(0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345, 0xb78d3d20) = 468207     
<...syscall resumed> )                                                                 
...

Здесь можно видеть вызов функции syscall и передачу ей в качестве аргумента номер системного вызова (337) и дальнейший вызов нашей функции sys_getjiffies (SYS_337).

Источники

  • Статья Тима Джонса - Kernel command using Linux system calls

  • rflinux.ru - Системные вызовы в Linux

  • Manugarg - Sysenter Based System Call Mechanism in Linux 2.6
P.S. 1) Пара слов о изменениях. Практически все пути и заголовочные файлы были изменены (в том числе и в Рис.2), также Вы могли заметить, что для системных вызовов мы создаем новый файл и включаем туда все три функции предложенные для тестирования автором, это сделано для большей наглядности. В разделе "Использование системных вызовов" я решил оставить старый метод (как говорит автор традицонный) несмотря на то, что он более не актуален. Последние два раздела "Альтернативы" и "Двигаясь дальше" я не стал включать, так как на мой взгляд это "вода". Остальные изменения Вы можете получить сравнив оригинал статьи с данной.
2) Если встретите грубые ошибки перевода (т.е. те, которые искажают смысл) сообщите в ЛС.
3) Выкладываю pdf'ку.
4) Offtop - Агитация:
Мы с Вами используем ОС Linux, либо для повседневной деятельности, либо для решения узких задач. Но многие из нас не имеют представления о том, что происходит за кулисами пользовательских приложений или во время "простоя" системы. Поэтому я хотел бы сагитировать участников проекта приподнять вуаль таинственности над работой, которая зачастую остается незамеченной, в самом сердце системы - ядре. Я понимаю, что многие из нас не являются выходцами технических профессий, а кому-то это просто не интересно, но надеюсь, что найдутся и такие, кто захочет разобраться, потратить толику свободного времени и выложить результаты своих исследований на общее обозрение и критику (которой не стоит бояться). Надеюсь, что вскоре появится блог посвященный ядру и будет также активно пополнятся, как и блог "Есть вопрос!".


Теги:

predator 17.08.10 05:33 # +2
Офигенно!
main 17.08.10 09:29 # +3
Вы молодец!
Даёшь качественный текст!
strk 17.08.10 11:16 # +-3
Отлично! Больше матана на вилинуксе!
blackraven 17.08.10 23:29 # +0
Изменения выделены жирным шрифтом и показаны в Листинге 2.

Не узрел жирного шрифта...

А вообще - хорошая, годная статья - продолжайте в том же духе!
Особенно в направлении Kernel hacking :)
dementiy 17.08.10 23:42 # +1
Спасибо, я недоглядел. Продолжать обязательно буду.

Лучшие блоги (все 133)
Топ пользователей Топ блогов
Топ пользователей Топ блогов
Элита (все 2589 из 203 городов)
Топ пользователей Топ блогов
welinux.ru

Смотреть онлайн бесплатно

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн