代码之家  ›  专栏  ›  技术社区  ›  md.jamal

compat\u ioctl对于无符号long long数据类型工作不正常

  •  0
  • md.jamal  · 技术社区  · 5 年前

    文件操作中有一个函数指针.compat\u ioctl,它允许32位进程在64位机器中使用ioctl。

    以下是我的驱动程序代码:

    #ifndef __IOCTL_CMD_H
    #define __IOCTL_CMD_H
    
    #define MSG_MAGIC_NUMBER    0x21
    
    #define MSG_IOCTL_GET_LENGTH    _IOR(MSG_MAGIC_NUMBER, 1, unsigned int)
    
    #define MSG_IOCTL_CLEAR_BUFFER  _IO(MSG_MAGIC_NUMBER, 2)
    
    #define MSG_IOCTL_FILL_BUFFER   _IOW(MSG_MAGIC_NUMBER, 3, unsigned char)
    
    #define MSG_GET_ADDRESS     _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)
    
    #define MSG_IOCTL_MAX_CMDS      4
    
    #endif
    
    
    long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
        unsigned char ch;
        int retval = 0;
        long size = _IOC_SIZE(cmd);
    
        pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);
    
        if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
        if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;
    
        //access_ok is kernel-oriented, so the concept of read and write is reversed
    
        retval = access_ok((void __user *)arg, size);
    
        pr_info("access_ok returned:%d\n", retval);
        if (!retval)
            return -EFAULT;
    
        switch(cmd)
        {
            //Get Length of buffer
            case MSG_IOCTL_GET_LENGTH:
                pr_info("Get Buffer Length\n");
                put_user(MAX_SIZE, (unsigned int *)arg);
                break;
            //clear buffer
            case MSG_IOCTL_CLEAR_BUFFER:
                pr_info("Clear buffer\n");
                memset(kernel_buffer, 0, sizeof(kernel_buffer));
                break;
            //fill character
            case MSG_IOCTL_FILL_BUFFER:
                get_user(ch, (unsigned char *)arg);
                pr_info("Fill Character:%c\n", ch);
                memset(kernel_buffer, ch, sizeof(kernel_buffer));
                buffer_index = sizeof(kernel_buffer);
                break;
            //address of kernel buffer
            case MSG_GET_ADDRESS:
                put_user(0x12345678, (unsigned long*)arg);
                pr_info("MSG_GET_ADDRESS\n");
                break;
            default:
                pr_info("Unknown Command:%u\n", cmd);
                return -ENOTTY;
        }
        return 0;
    }
    
    
    long device_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
        unsigned char ch;
        int retval = 0;
        long size = _IOC_SIZE(cmd);
    
        pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);
    
        if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
        if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;
    
        //access_ok is kernel-oriented, so the concept of read and write is reversed
    
        retval = access_ok((void __user *)arg, size);
    
        pr_info("access_ok returned:%d\n", retval);
        if (!retval)
            return -EFAULT;
    
        switch(cmd)
        {
            //Get Length of buffer
            case MSG_IOCTL_GET_LENGTH:
                pr_info("Get Buffer Length\n");
                put_user(MAX_SIZE, (unsigned int *)arg);
                break;
            //clear buffer
            case MSG_IOCTL_CLEAR_BUFFER:
                pr_info("Clear buffer\n");
                memset(kernel_buffer, 0, sizeof(kernel_buffer));
                break;
            //fill character
            case MSG_IOCTL_FILL_BUFFER:
                get_user(ch, (unsigned char *)arg);
                pr_info("Fill Character:%c\n", ch);
                memset(kernel_buffer, ch, sizeof(kernel_buffer));
                buffer_index = sizeof(kernel_buffer);
                break;
            //address of kernel buffer
            case MSG_GET_ADDRESS:
                put_user(0x12345678, (unsigned long*)arg);
                pr_info("MSG_GET_ADDRESS\n");
                break;
            default:
                pr_info("Unknown Command:%u\n", cmd);
                return -ENOTTY;
        }
        return 0;
    }
    
    
    
    
    struct file_operations device_fops = {
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
        .llseek = device_lseek,
        .unlocked_ioctl = device_ioctl,
        .compat_ioctl = device_compat_ioctl
    };
    

    当我从用户空间(32位进程)调用下面的代码时,它在compat\u ioctl定义中以未知ioctl失败。

    int main(int argc, char *argv[])
    {
        char buffer[1024];  
        int fd;
        unsigned int length;
        int i = 0;
        unsigned long addr;
    
        fd = open("/dev/msg", O_RDWR);
        if (fd < 0) {
            perror("fd failed");
            exit(2);
        }
    
        printf("Size:%d\n", _IOC_SIZE(MSG_GET_ADDRESS));
        printf("cmd:%u\n", MSG_GET_ADDRESS);
    
        ioctl(fd, MSG_GET_ADDRESS, &addr);
        perror("ioctl");
        getchar();
        printf("address:%x\n", addr);
    
        close(fd);
    }
    

    0 回复  |  直到 5 年前
        1
  •  1
  •   Ian Abbott    5 年前

    你的 MSG_GET_ADDRESS ioctl请求代码定义为:

    #define MSG_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)
    

    第三个参数的大小被编码到ioctl请求代码中。可以使用 _IOC_SIZE(req) 宏。

    消息\u获取\u地址 与64位进程/内核相比,32位进程/内核将有所不同。特别是,编码的大小将不同。

    在32位进程/内核上, _IOC_SIZE(MSG_GET_ADDRESS) 四岁。在64位进程/内核上, _IOC\大小(消息\获取\地址) sizeof(unsigned long) 32位和64位系统上的值。

    在支持32位兼容性的64位内核上运行32位进程时,32位进程将调用 ioctl() 消息\u获取\u地址 请求代码。但是,你的司机 device_compat_ioctl() 正在寻找64位版本的 消息\u获取\u地址 请求代码。

    一个解决方案是在驱动程序中定义一个32位版本的ioctl请求代码来镜像“官方” 消息\u获取\u地址

    #define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)
    

    请注意,此请求代码不需要位于用户模式标头中,因为它仅用于内核模式。但是,如果更方便的话,可以将其包含在用户模式标头中,但要包装在 #ifdef __KERNEL__ #endif 一对:

    #ifdef __KERNEL__
    #define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)
    #endif
    

    现在,你的 device_compat_ioctl 函数应更改为处理 MSG32_GET_ADDRESS 消息\u获取\u地址 请求代码:

            //address of kernel buffer
            case MSG32_GET_ADDRESS:
                put_user(0x12345678, (compat_ulong_t*)arg);
                pr_info("MSG_GET_ADDRESS\n");
                break;
    

    注: 根据代码中的注释 消息\u获取\u地址 实际上应该获取内核缓冲区的地址。我不知道您的用户空间代码打算用它做什么,但是请注意,64位内核地址不适合32位 unsigned long (或32位 compat_ulong_t 类型)。