Introdução aos Sistemas Operacionais/Exemplo de técnica de Debug: Debug com ioctl

Dados gerais

editar

Autoria

editar

Arthur Henrique (warthur@gmail.com), Danilo Inácio (danilo.isr@gmail.com), Luíla Moraes (luilamoliveira@gmail.com), Paulo Júnior (pjuniorlima@gmail.com)

Licenciamento

editar

Beerware em conjunto com a Attribution Non-Commercial Share Alike

Créditos

editar

Objetivo e descrição

editar

Demonstrar tecnicas de debug utilizando o método ictl

O método ioctl (i-o-control) é uma chamada no sistema que possibilita a transferência de dados do módulo para o espaço do usuário. Essa técnica é bastante utilizada para debugar módulos, já que podemos obter dados do módulo. Essa técnica é mais eficiente que o uso do sistema de arquivos /proc, por ser mais rápida que a leitura de arquivos no /proc, pois acessar os dados binários (ioctl) é mais eficiente que em texto(/proc), em contra partida o método ioctl é um pouco mais complexo que a administração de arquivos /proc.

Para usar este método devemos implementar o método device_ioctl no código do módulo, a seguir um exemplo do código comentado que pode ser encontrado aqui:

/*
 * Está função recebe 4 parâmetros, os dois primeiros são comuns às
 * chamadas de modulos, e os dois últimos são o número da chamada
 * ioctl e o parametro passado
 *
 */
int device_ioctl(struct inode *inode,
		 struct file *file,
		 unsigned int ioctl_num,
		 unsigned long ioctl_param)
{
	int i;
	char *temp;
	char ch;

	/*
	 * Switch para o número da chamada
	 */
	switch (ioctl_num) {
	case IOCTL_SET_MSG:
		/*
		 * Pega o ponteiro da mensagem passada por parâmetro para esta
		 * função. O ponteiro 'temp' pertence a este módulo
		 */
		temp = (char *)ioctl_param;

		/*
		 * Calcula o tamanho da mensagem passada, o tamanho ficará
		 * armazenado na variável 'i'. A função 'get_user' é usada para
		 * transferir dados do ambiente do usuário para o módulo
		 */
		get_user(ch, temp);
		for (i = 0; ch && i < BUF_LEN; i++, temp++)
			get_user(ch, temp);

		/*
		 * Chama a função que escreve no módulo
		 */
		device_write(file, (char *)ioctl_param, i, 0);
		break;

	case IOCTL_GET_MSG:
		/*
		 * Passa a mensagem atual do módulo para o ponteiro passado
		 * por parâmetro usando a função device_read().
		 */
		i = device_read(file, (char *)ioctl_param, 99, 0);

		/*
		 * Finaliza a string com um '\0'
		 */
		put_user('\0', (char *)ioctl_param + i);
		break;

	case IOCTL_GET_NTH_BYTE:
		/*
		 * Retorna o caractere na possição passada por parâmetro
		 */
		return Message[ioctl_param];
		break;
	}

	return SUCCESS;
}

Veja que para realizar o switch utilizamos o parâmetro ioctl_num. Os valores contidos neste parâmetros são gerados através dos seguintes macros (_IO, _IOR, _IOW ou _IOWR). Estes valores devem ser definidos em um arquivo compartilhado entre o módulo é a aplicação que fará a chamada iocrl. No nosso exemplo temos o seguinte header:

/*  chardev.h - header para o módulo é o programa
 *  que utilizará a chamada ioctl
 */

#ifndef CHARDEV_H
#define CHARDEV_H

#include <linux/ioctl.h>

/* Definição no major number do módulo, que deve ser
 * estática para que a chamada ioctl identifique
 * o módulo */
#define MAJOR_NUM 100


/* Define a função set_msg utilizada */
#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)
/* O macro _IOR cria um número do método que iremos
 * utilizar os parâmetros definidos.
 *
 * O primeiro argumento é para que o ioctl identifique
 * o módulo.
 *
 * O segundo parâmetro é para identificar o método
 * The second argument is the number of the command 
 * (there could be several with different meanings).
 *
 * O terceiro é o tipo do retorno iremos passar para
 * o módulo.
 */

/* Pega uma mensagem do módulo */
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)
 /* O terceiro parâmetro aqui receberá o retorno
  * da chamada.
  */


/* Pega o caractere na posição n */
#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)
 /* Este é um exemplo de como passar e pegar um parâmetro
  * para o módulo.
  * Aqui passamos a posição do caractere que queremos
  * e recebemos ele. */

#endif

Agora escrevemos o programa que irá se comunicar com o módulo através da chamada ioctrl. Eis um trecho do arquivo ioctl.c :

/* Função que recebe uma mensagem do módulo
 */
ioctl_get_msg(int file_desc)
{
	int ret_val;
	char message[100];

        /* Chama a função ioctl passando o parâmetro definido
         * no arquivo header.
         */
	ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

	if (ret_val < 0) {
		printf("ioctl_get_msg failed:%d\n", ret_val);
		exit(-1);
	}

	printf("get_msg message:%s\n", message);
}

Essa função retorna a mensagem gerada pelo módulo (veja que usando o método ioctl podemos interagir com o módulo em qualquer momento, diferente dos outros métodos de debug).

Código-fonte

editar

O código completo da aplicação pode ser encontrada aqui, e em nossa implementação obtemos a quantidade de caracteres que já foram escritos no módulo. Isso pode ser interessante para se criar uma ferramenta que gera estatísticas em tempo real do status do módulo.

Procedimentos para testes

editar
  • Configurar o ambiente para desenvolvimento de módulos. (passos aqui)
  • Extrair o conteúdo do pacote 'ioctl.tar.gz' para uma pasta
  • Dentro da pasta executar o comando 'make'
  • Executar os comandos
    • insmod ioctl_dev.ko
    • mknod ioctl_dev c 100 0
    • gcc -o ioctl ioctl.c
    • ./ioctl

Considerações finais

editar

O desenvolvimento deste trabalho foi prejudicado devido a quantidade materiais disponíveis, devido a esse fator demoramos a focar nosso trabalho em uma área. Depois de alguns teste escolhemos documento o método ioctl devido sua comunicação instantânea com o módulo, que permite o desenvolvimento de aplicações interessantes, como monitoramento de módulos.

Referências

editar

[1] The Linux Kernel Module Programming Guide http://tldp.org/LDP/lkmpg/2.4/html/index.html

[2] Talking to Device Files (writes and IOCTLs) http://tldp.org/LDP/lkmpg/2.4/html/x856.html#AEN915

[3] Linux Device Drivers, Third Edition http://lwn.net/Kernel/LDD3/