Вопрос по linux-kernel, module, c, kernel-module, linux – Могу ли я заменить функцию ядра Linux модулем?

13

Я начинаю работу над ядром для небольшого количества моих летних исследований. Мы надеемся внести изменения в TCP, в конкретные расчеты RTT. Я хотел бы заменить разрешение одной из функций в tcp_input.c на функцию, предоставляемую динамически загружаемым модулем ядра. Я думаю, что это улучшило бы скорость, с которой мы можем разрабатывать и распространять модификацию.

Интересующая меня функция была объявлена как статическая, однако я перекомпилировал ядро с нестатической функцией и экспортировал ее в EXPORT_SYMBOL. Это означает, что функция теперь доступна для других модулей / частей ядра. Я подтвердил это с помощью "cat / proc / kallsyms".

Теперь я хотел бы иметь возможность загружать модуль, который может переписать адрес символа из начального в мою динамически загруженную функцию. Точно так же, когда модуль должен быть выгружен, он восстановит исходный адрес. Это осуществимый подход? У всех вас есть предложения, как это лучше реализовать?

Спасибо!

Такой же какOverriding functionality with modules in Linux kernel

Редактировать:
Это был мой возможный подход.
Учитывая следующую функцию (которую я хотел переопределить, и не экспортируется):

static void internal_function(void) 
{
  // do something interesting
  return;
}

изменить так:

static void internal_function_original(void)
{
  // do something interesting
  return;
}

static void (*internal_function)(void) = &internal_function_original;
EXPORT_SYMBOL(internal_function);

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

Теперь вы можете написать модуль ядра со следующей формой:

static void (*original_function_reference)(void);
extern void (*internal_function)(void);

static void new_function_implementation(void)
{
  // do something new and interesting
  // return
}

int init_module(void)
{
  original_function_reference = internal_function;
  internal_function           = &new_function_implementation;
  return 0;
}

void cleanup_module(void)
{
  internal_function = original_function_reference;
}

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

Ваш Ответ

4   ответа
7

Я не уверен, что это сработает - я полагаю, что разрешение символов для внутренних вызовов функции, которую вы хотите заменить, уже будет выполнено к моменту загрузки вашего модуля.

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

В итоге я пошел по пути, который вы предложили, добавив глобальный хук. Это было легко реализовать и предоставило именно то, что мне нужно. Спасибо за информацию относительно разрешения символов. Я не нашел источника, который бы однозначно объяснил, как и когда осуществлялся доступ к таблице символов (при каждом вызове функции или только при связывании). Это был полезный совет. Willi Ballenthin
3

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

Я ввел свою собственную функцию в ядро, переписав первые пару байтов кода с помощью перехода, указывающего на мою пользовательскую функцию. Как только вызывается настоящая функция, она переходит к моей функции, которая после того, как она выполнила свою работу, вызвала исходную функцию.


#include <linux/module.h>
#include <linux/kernel.h>

#define CODESIZE 12

static unsigned char original_code[CODESIZE];
static unsigned char jump_code[CODESIZE] =
    "\x48\xb8\x00\x00\x00\x00\x00\x00\x00\x00" /* movq $0, %rax */
    "\xff\xe0"                                          /* jump *%rax */
        ;
/* FILL THIS IN YOURSELF */
int (*real_printk)( char * fmt, ... ) = (int (*)(char *,...) )0xffffffff805e5f6e;

int hijack_start(void);
void hijack_stop(void);
void intercept_init(void);
void intercept_start(void);
void intercept_stop(void);
int fake_printk(char *, ... );


int hijack_start()
{
    real_printk(KERN_INFO "I can haz hijack?\n" );
    intercept_init();
    intercept_start();

    return 0;
}

void hijack_stop()
{
    intercept_stop();
    return;
}

void intercept_init()
{
    *(long *)&jump_code[2] = (long)fake_printk;
    memcpy( original_code, real_printk, CODESIZE );

    return;
}

void intercept_start()
{
    memcpy( real_printk, jump_code, CODESIZE );
}

void intercept_stop()
{
    memcpy( real_printk, original_code, CODESIZE );
}

int fake_printk( char *fmt, ... )
{
    int ret;
    intercept_stop();
    ret = real_printk(KERN_INFO "Someone called printk\n");
    intercept_start();
    return ret;
}

module_init( hijack_start );
module_exit( hijack_stop );

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

Это действительно простой принцип, но очень эффективный. Конечно, реальное решение будет использовать блокировки, чтобы никто не вызывал функцию, пока вы ее перезаписываете.

Повеселись!

3

Вы можете попробовать использоватьKsplice - вам даже не нужно делать его не статичным.

Это не бесплатно. Есть ли альтернатива FOSS?
2

Я думаю, что вы хотитеKProbe.

Другой способ, о котором упомянул caf, - добавить хук в исходную процедуру и зарегистрировать / отменить хук в модуле.

Kprobe выглядит интересным и полезным инструментом. Спасибо за ссылку. Хотя я выбрал другой путь, я думаю, что это мог быть эффективный подход. Willi Ballenthin

Похожие вопросы