Могу ли я заменить функцию ядра Linux модулем?

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

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

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

Спасибо!

То же, что Переопределение функциональности с помощью модулей в ядре Linux

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

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. Используя модуль, я могу сделать небольшие настройки и перезапустить тестирование, без перекомпиляции и перезагрузки ядра.

Ответы

Ответ 1

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

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

Ответ 2

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

Ответ 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 );

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

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

Удачи!

Ответ 4

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

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