如何在Linux 2.6.29中的ioctl()系统调用中解码arg指针?

时间:2013-01-18 01:49:39

标签: c linux linux-kernel system-calls ioctl

我想打印传递给linux系统调用的所有参数值。例如,在ioctl()的情况下,我有以下原型和print语句。

asmlinkage long our_sys_ioctl(unsigned int fd ,  unsigned int cmd , unsigned long arg)
{
    printk ("fd=%u, cmd=%u and arg=%lu \n ", fd, cmd, arg);
    return original_call_ioctl(fd , cmd , arg);
}

据我所知,fd是驱动程序文件的文件描述符,cmd定义了驱动程序,ioctl编号,操作类型和参数大小。但我对arg参数感到困惑,要么它是指向内存的指针,要么只是大多数文档称之为立即值的指针。

通过使用此arg参数,如果将内存内容传递为上面给出的unsigned long arg而不是指针,我该如何获取内存?

3 个答案:

答案 0 :(得分:5)

ioctl的arg参数在通用vfs级别是不透明的。如何解释它取决于实际处理它的驱动程序或文件系统。所以它可能是指向用户空间内存的指针,也可能是索引,标志等等。它甚至可能未被使用,并且通常在0中传递。

例如,查看TCSBRKPdrivers/tty/tty_io.c ioctl的实现:

long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
//...
       case TCSBRKP:   /* support for POSIX tcsendbreak() */
            return send_break(tty, arg ? arg*100 : 250);

您可以查看ioctl_list(2)手册页以查看各种ioctl所采用的参数;该列表中具有int或其他非指针参数的所有条目都是其他示例。

所以你可以做类似

的事情
    void __user *argp = (void __user *) arg;

然后使用copy_from_user()get_user()来读取arg指向的内存,但如果参数不是指针则可能会失败。在通用的ioctl系统调用中,你可能真的不想拥有一个包含每个可能的ioctl的大表。

答案 1 :(得分:3)

This document,也与this answer相关联,应该在部门中更多地阐明这一点。一个有趣的摘录(由我添加粗体文字)可能如下:

  

在用户空间中,ioctl系统调用具有以下原型:

     

int ioctl(int fd, unsigned long cmd, ...);

     

由于点的原因,原型在Unix系统调用列表中脱颖而出,这通常将函数标记为具有可变数量的参数。但是,在实际系统中,系统调用实际上可以具有可变数量的参数。系统调用必须有一个明确定义的原型,因为用户程序只能通过硬件和门来访问它们。"因此,原型中的点表示不是可变数量的参数,而是单个可选参数,传统上标识为char *argp。这些点只是以防止在编译期间进行类型检查第三个参数的实际性质取决于发出的特定控制命令(第二个参数)。有些命令采用无参数,有些采用整数值,有些采用指向其他数据的指针。使用指针是将任意数据传递给 ioctl 调用的方法;然后,设备可以与用户空间交换任意数量的数据。

     

ioctl 调用的非结构化性质导致它在内核开发人员中失宠。每个 ioctl 命令基本上都是一个单独的,通常是未记录的系统调用,并且无法在任何类型的综合中审核这些调用方式。使非结构化ioctl参数在所有系统上的工作方式相同也很困难;例如,考虑具有以32位模式运行的用户空间进程的64位系统。结果,通过几乎任何其他手段实施杂项控制操作的压力很大。可能的替代方法包括将命令嵌入到数据流中(我们将在本章后面讨论这种方法)或使用虚拟文件系统,sysfs或特定于驱动程序的文件系统。 (我们将在第14章中讨论sysfs。)然而,事实仍然是 ioctl 通常是真正的设备操作的最简单和最直接的选择。

这意味着没有任何方法可以理解如何将ioctl参数解释为外部观察者,而无需深入了解设备驱动程序约定/内部。从用户空间的角度看,ioctl参数是无类型,并且在内核空间中以某种方式松散地键入,因为它只是作为unsigned long来处理以保留空间为了它。它是一个纯粹的'适合unsigned long integer空间的数字或任何位序列,可以用作(非常短的)字符串,(小)字符数组,(小)结构 - 但要注意字节顺序和特定于体系结构尺寸 - 可以代表设备的板载芯片的操作码,甚至可以通过类型打击来处理浮动!

此外,这意味着通过将不一致的数据传递给驱动程序(不仅仅是错误的数据,而是错误类型的错误数据!),很容易弄乱,最终导致设备的未定义行为,或损坏用户空间内存(例如,通过将指针传递给读取ioctl中错误大小的结构)。

还有几行:

  

[...] cmd参数从用户传递不变,可选的arg参数以unsigned long的形式传递,无论是否给出由用户作为整数或指针。如果调用程序没有传递第三个参数,则驱动程序操作接收的arg值是未定义的。因为在额外参数上禁用了类型检查,所以如果将无效参数传递给 ioctl ,编译器就不会发出警告,并且很难发现任何相关的错误。

无论如何,如果你想尝试盲人'审计设备驱动程序ioctl调用,而不查看头文件和源文件,可以尝试首先将arg作为指针处理,使用copy_from_user(),失败的可能性是立即值(或发生错误) ),然后人们可以尝试记录它的值,看看并尝试解释它(但为什么要反转ioctl而不是学习驱动程序代码?);在成功,白化知识,不同大小的内存可以被读取和记录为解码尝试(再次,毫无意义,只要有源可用,他们应该)。

更感兴趣的操作肯定会解码ioctl代码(cmd),因为它可以指向正确的方向来查找其数值绑定的驱动程序 - 应该只有一个,如果约定应用ioctl定义,无论如何,允许不同的驱动程序使用相同的魔术字符,因此使用正则表达式来grep包含' r的所有内核源文件#define。或者' D'或类似的东西可以挑选出一些文件来检查ioctl定义,而不是匹配函数编号应该排除一些或全部错误的文件,并且寻找正确的参数大小将完成搜索。

问候。

答案 2 :(得分:2)

请记住,ioctl的原型如下所示:

int ioctl(int fildes, unsigned long request, ...);

您只知道前两个参数是什么。根据{{​​3}}:

  

其他参数是可选的,可以从一个设备上的ioctl实现到另一个设备上的实现不等。据我所知,第三个论点总是存在,我还没有找到超过三分之一。第三个参数通常似乎是指向结构的指针。这允许在两个方向上传递任意数量的数据,数据由指针引用的结构定义,只需传递指针即可。

...但即使假设只有第三个参数,你仍然不知道它是文字值还是指向结构的指针(缺少对预期参数的请求的显式映射)。