GNU/Linux >> Linux Esercitazione >  >> Linux

Come posso usare ioctl() per manipolare il mio modulo del kernel?

Esempio eseguibile minimo

Testato in un ambiente QEMU + Buildroot completamente riproducibile, quindi potrebbe aiutare gli altri a ottenere il loro ioctl Lavorando. GitHub upstream:modulo kernel |intestazione condivisa |userland.

La parte più fastidiosa è stata capire che alcuni ID bassi vengono violati:ioctl non viene chiamato se cmd =2 , devi usare _IOx macro.

Modulo kernel:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */

#include "ioctl.h"

MODULE_LICENSE("GPL");

static struct dentry *dir;

static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
    void __user *arg_user;
    union {
        int i;
        lkmc_ioctl_struct s;
    } arg_kernel;

    arg_user = (void __user *)argp;
    pr_info("cmd = %x\n", cmd);
    switch (cmd) {
        case LKMC_IOCTL_INC:
            if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
            pr_info("0 arg = %d\n", arg_kernel.i);
            arg_kernel.i += 1;
            if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
                return -EFAULT;
            }
        break;
        case LKMC_IOCTL_INC_DEC:
            if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
            pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
            arg_kernel.s.i += 1;
            arg_kernel.s.j -= 1;
            if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
                return -EFAULT;
            }
        break;
        default:
            return -EINVAL;
        break;
    }
    return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = unlocked_ioctl
};

static int myinit(void)
{
    dir = debugfs_create_dir("lkmc_ioctl", 0);
    /* ioctl permissions are not automatically restricted by rwx as for read / write,
     * but we could of course implement that ourselves:
     * https://stackoverflow.com/questions/29891803/user-permission-check-on-ioctl-command */
    debugfs_create_file("f", 0, dir, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(dir);
}

module_init(myinit)
module_exit(myexit)

Intestazione condivisa tra il modulo del kernel e userland:

ioctl.h

#ifndef IOCTL_H
#define IOCTL_H

#include <linux/ioctl.h>

typedef struct {
    int i;
    int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC     _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)

#endif

Area utente:

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "../ioctl.h"

int main(int argc, char **argv)
{
    int fd, arg_int, ret;
    lkmc_ioctl_struct arg_struct;

    if (argc < 2) {
        puts("Usage: ./prog <ioctl-file>");
        return EXIT_FAILURE;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open");
        return EXIT_FAILURE;
    }
    /* 0 */
    {
        arg_int = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d\n", arg_int);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    puts("");
    /* 1 */
    {
        arg_struct.i = 1;
        arg_struct.j = 1;
        ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
        if (ret == -1) {
            perror("ioctl");
            return EXIT_FAILURE;
        }
        printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
        printf("ret = %d\n", ret);
        printf("errno = %d\n", errno);
    }
    close(fd);
    return EXIT_SUCCESS;
}

Il codice di esempio di cui hai bisogno può essere trovato in drivers/watchdog/softdog.c (da Linux 2.6.33 al momento in cui è stato scritto), che illustra le corrette operazioni sui file e come consentire a userland di riempire una struttura con ioctl().

In realtà è un ottimo tutorial funzionante per chiunque abbia bisogno di scrivere driver di dispositivo con caratteri banali.

Ho sezionato l'interfaccia ioctl di softdog quando ho risposto alla mia stessa domanda, che potrebbe esserti utile.

Ecco il succo (anche se tutt'altro che esaustivo) ...

In softdog_ioctl() vedi una semplice inizializzazione di struct watchdog_info che pubblicizza funzionalità, versione e informazioni sul dispositivo:

    static const struct watchdog_info ident = {
            .options =              WDIOF_SETTIMEOUT |
                                    WDIOF_KEEPALIVEPING |
                                    WDIOF_MAGICCLOSE,
            .firmware_version =     0,
            .identity =             "Software Watchdog",
    };

Quindi esaminiamo un semplice caso in cui l'utente desidera solo ottenere queste funzionalità:

    switch (cmd) {
    case WDIOC_GETSUPPORT:
            return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;

... che ovviamente riempirà il corrispondente watchdog_info dello spazio utente con i valori inizializzati sopra. Se copy_to_user() fallisce, viene restituito -EFAULT che fa sì che la corrispondente chiamata ioctl() dello spazio utente restituisca -1 con un errno significativo impostato.

Nota, le richieste magiche sono effettivamente definite in linux/watchdog.h , in modo che il kernel e lo spazio utente le condividano:

#define WDIOC_GETSUPPORT        _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS         _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS     _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP           _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS        _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE         _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT        _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT        _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT     _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT     _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT       _IOR(WATCHDOG_IOCTL_BASE, 10, int)

WDIOC significa ovviamente "Watchdog ioctl"

Puoi facilmente fare un ulteriore passo avanti, chiedendo al tuo driver di fare qualcosa e posizionare il risultato di quel qualcosa nella struttura e copiarlo nello spazio utente. Per esempio, se struct watchdog_info avesse anche un membro __u32 result_code . Nota, __u32 è solo la versione del kernel di uint32_t .

Con ioctl(), l'utente passa l'indirizzo di un oggetto, sia esso una struttura, un numero intero, qualunque cosa al kernel, aspettandosi che il kernel scriva la sua risposta in un oggetto identico e copi i risultati nell'indirizzo fornito.

La seconda cosa che dovrai fare è assicurarti che il tuo dispositivo sappia cosa fare quando qualcuno lo apre, lo legge, ci scrive o usa un hook come ioctl(), che puoi vedere facilmente studiando softdog.

Di interesse è:

static const struct file_operations softdog_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .write          = softdog_write,
        .unlocked_ioctl = softdog_ioctl,
        .open           = softdog_open,
        .release        = softdog_release,
};

Dove vedi andare il gestore unlocked_ioctl ... hai indovinato, softdog_ioctl().

Penso che potresti giustapporre uno strato di complessità che in realtà non esiste quando hai a che fare con ioctl(), è davvero così semplice. Per lo stesso motivo, la maggior parte degli sviluppatori del kernel disapprova l'aggiunta di nuove interfacce ioctl a meno che non siano assolutamente necessarie. È semplicemente troppo facile perdere traccia del tipo che ioctl() riempirà rispetto alla magia che usi per farlo, che è la ragione principale per cui copy_to_user() fallisce spesso con il risultato che il kernel marcisce con orde di processi dello spazio utente bloccati sospensione del disco.

Per un timer, sono d'accordo, ioctl() è la via più breve per la sanità mentale.


Ti manca un .open puntatore a funzione nel tuo file_operations struttura per specificare la funzione da chiamare quando un processo tenta di aprire il file del dispositivo. Dovrai specificare un .ioctl puntatore di funzione anche per la tua funzione ioctl.

Prova a leggere la Linux Kernel Module Programming Guide, in particolare i capitoli 4 (Character Device Files) e 7 (Talking to Device Files).

Il Capitolo 4 introduce il file_operations struttura, che contiene puntatori a funzioni definite dal modulo/driver che eseguono varie operazioni come open o ioctl .

Il capitolo 7 fornisce informazioni sulla comunicazione con un modulo/azionamento tramite ioctls.

Linux Device Drivers, Third Edition è un'altra buona risorsa.


Linux
  1. Come trovare il modulo del kernel per un determinato dispositivo?

  2. Linux:come determinare quale modulo contamina il kernel?

  3. Linux:come ricaricare correttamente un modulo del kernel?

  4. Come aggiungere i parametri del modulo del kernel?

  5. Come codificare un modulo del kernel Linux?

Come caricare o scaricare un modulo del kernel Linux

Come utilizzare il comando Modprobe in Linux

Come caricare e scaricare i moduli del kernel in Linux

Come utilizzare Nginx per reindirizzare

Come creare un modulo Terraform

Come utilizzare Instagram nel terminale