Обработка неопределенной инструкции в ядре - PullRequest
6 голосов
/ 16 апреля 2020

Так что я играю с чтением системных регистров в ядре, и недавно я столкнулся с некоторым препятствием.

В ARM64 некоторые системные регистры (например, OSECCR_EL1) не всегда реализованы. Если они реализованы, то пробовать инструкцию mrs нормально - ничего плохого не происходит. Но если они НЕ реализованы, то ядро ​​выдает Oops из-за неопределенной инструкции.

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

Есть ли какой-нибудь простой способ заранее определить, является ли системный регистр допустимо, или, по крайней мере, обрабатывает ядро ​​так, что это не останавливает выполнение функции модуля ядра?

1 Ответ

3 голосов
/ 16 апреля 2020

Поскольку вы говорите, что просто «играете», я собираюсь предложить довольно грязное, но довольно простое решение.

Ядро Linux для ARM имеет собственный способ обработки неопределенных инструкций чтобы эмулировать их, это делается с помощью простых «неопределенных хуков команд», определенных в arch/arm64/include/asm/traps.h:

struct undef_hook {
    struct list_head node;
    u32 instr_mask;
    u32 instr_val;
    u64 pstate_mask;
    u64 pstate_val;
    int (*fn)(struct pt_regs *regs, u32 instr);
};

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

Чтобы решить вашу проблему, у вас есть два варианта:

  1. Экспорт обеих функций путем изменения arch/arm64/kernel/traps.c добавив следующие две строки кода:

    // after register_undef_hook
    EXPORT_SYMBOL(register_undef_hook);
    
    // after unregister_undef_hook
    EXPORT_SYMBOL(unregister_undef_hook);
    

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

  2. Используйте kallsyms_lookup_name() для поиска символов во время выполнения непосредственно из вашего модуля, без необходимости повторной компиляции ядра , Немного сложнее, но, вероятно, проще и, безусловно, в целом более быстрое решение.

Для option # 1 , вот пример модуля, который делает именно то, что вам нужно:

// SPDX-License-Identifier: GPL-3.0
#include <linux/init.h>   // module_{init,exit}()
#include <linux/module.h> // THIS_MODULE, MODULE_VERSION, ...
#include <asm/traps.h>    // struct undef_hook, register_undef_hook()
#include <asm/ptrace.h>   // struct pt_regs

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static void whoops(void)
{
    // Execute a known invalid instruction.
    asm volatile (".word 0xf7f0a000");
}

static int undef_instr_handler(struct pt_regs *regs, u32 instr)
{
    pr_info("*gotcha*\n");

    // Just skip over to the next instruction.
    regs->pc += 4;

    return 0; // All fine!
}

static struct undef_hook uh = {
    .instr_mask  = 0x0, // any instruction
    .instr_val   = 0x0, // any instruction
    .pstate_mask = 0x0, // any pstate
    .pstate_val  = 0x0, // any pstate
    .fn          = undef_instr_handler
};

static int __init modinit(void)
{
    register_undef_hook(&uh);

    pr_info("Jumping off a cliff...\n");
    whoops();
    pr_info("Woah, I survived!\n");

    return 0;
}

static void __exit modexit(void)
{
    unregister_undef_hook(&uf);
}

module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Test undefined instruction handling on arm64.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

Для опция # 2 , вы можете просто изменить вышеуказанный код, добавив следующее:

#include <linux/kallsyms.h> // kallsyms_lookup_name()

// Define two global pointers.
static void (*register_undef_hook_ptr)(struct undef_hook *);
static void (*unregister_undef_hook_ptr)(struct undef_hook *);

static int __init modinit(void)
{
    // Lookup wanted symbols.
    register_undef_hook_ptr   = (void *)kallsyms_lookup_name("register_undef_hook");
    unregister_undef_hook_ptr = (void *)kallsyms_lookup_name("unregister_undef_hook");

    if (!register_undef_hook_ptr)
        return -EFAULT;

    // ...

    return 0;
}

static void __exit modexit(void)
{
    if (unregister_undef_hook_ptr)
        unregister_undef_hook_ptr(&uh);
}

Вот вывод dmesg:

[    1.508253] testmod: Jumping off a cliff...
[    1.508781] testmod: *gotcha*
[    1.509207] testmod: Woah, I survived!

Некоторые примечания

  • В приведенном выше примере для undef_hook масок / значений инструкций / pstate установлено значение 0x0, это означает, что для вызова будет вызываться любая неопределенная инструкция, которая выполняется. Возможно, вы захотите ограничить это значение msr XX,YY, и вы сможете сделать это следующим образом:

    // didn't test these, you might want to double-check
    .instr_mask  = 0xfff00000,
    .instr_val   = 0xd5100000,
    

    Где 0xfff00000 соответствует всему, кроме операндов (согласно инструкции * 1061) *, стр. 779 из PDF). Вы можете взглянуть на исходный код , чтобы увидеть, как эти значения проверяются, чтобы решить, вызывать ли ловушку или нет, это довольно просто. Вы также можете проверить значение instr, которое передается в ловушку: pr_info("Instr: %x\n", instr).

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

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

  • Проверено на Linux ядре v5.6, qemu-system-aarch64.

...