为啥 ioctl 调用没有传递给 sys_ioctl?
Posted
技术标签:
【中文标题】为啥 ioctl 调用没有传递给 sys_ioctl?【英文标题】:Why is ioctl call not passed to sys_ioctl?为什么 ioctl 调用没有传递给 sys_ioctl? 【发布时间】:2017-02-16 12:52:11 【问题描述】:我有内核模块(4.4.32 内核),它通过将 ioctl 处理程序分配给 struct file_operations
的 unlocked_ioctl
指针来实现 ioctl 调用。一切正常,但我得到了程序(仅限二进制),编译为 2.6 或 2.4 内核,如果我在 4.4.32 上启动该程序,内核不会为我的模块注册对 ioctl 的调用。
因为这个程序是在旧内核上编译的,所以它使用旧的 ioctl 接口,即file_operations
结构中的ioctl
指针,而不是unlocked_ioctl
。
旧版程序创建了一个控制台用于与用户进行可视化交互,并且必须从根控制台以 root 身份运行。
我对该程序进行了 strace 并检查了该程序是否收到了第二个 ioctl 的 ENOTTY
,因此我编写了测试程序,它对内核模块进行与故障程序相同的 ioctl 调用。
我已验证 strace 记录的跟踪对于这些 ioctl 的两个程序是相同的,即它们以相同的参数以相同的顺序调用。
我的测试程序的相关部分是:
/*--------------------------- ((( STEP 1 ))) ---------------------------*/
hsdfd1 = open(PCIHSD0, O_RDWR);
if (hsdfd1 < 0)
fprintf(stderr, "Error on OPEN, can't open [%s] [%s]", PCIHSD0, strerror(errno));
exit(1);
/*--------------------------- ((( STEP 2 ))) ---------------------------*/
uint8_t xsts;
err = ioctl(hsdfd1, HSDGETXSTS, &xsts);
if (err < 0)
fprintf(stderr, "Error HSDGETXSTS [%s]", strerror(errno));
close(hsdfd1);
exit(2);
/*--------------------------- ((( STEP 3 ))) ---------------------------*/
hsdfd2 = open(PCIHSD0c, O_NDELAY, O_RDONLY);
if (hsdfd2 < 0)
fprintf(stderr, "Error on OPEN, can't open [%s] [%s]", PCIHSD0c, strerror(errno));
close(hsdfd1);
exit(3);
/*--------------------------- ((( STEP 4 ))) ---------------------------*/
err = ioctl(hsdfd2, PCIHSD_DIAG_SETALLOWDC, 0x1);
if (err < 0)
fprintf(stderr, "Error PCIHSD_DIAG_SETALLOWDC [%s]", strerror(errno));
err = 4;
goto exit;
痕迹:
我的测试程序:
execve("./hsddebug", ["./hsddebug"], [/* 23 vars */]) = 0
brk(0) = 0xb89000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9231c6b000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, st_mode=S_IFREG|0644, st_size=105359, ...) = 0
mmap(NULL, 105359, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9231c51000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, st_mode=S_IFREG|0755, st_size=1738176, ...) = 0
mmap(NULL, 3844640, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f92316a2000
mprotect(0x7f9231843000, 2097152, PROT_NONE) = 0
mmap(0x7f9231a43000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a1000) = 0x7f9231a43000
mmap(0x7f9231a49000, 14880, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f9231a49000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9231c50000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9231c4f000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9231c4e000
arch_prctl(ARCH_SET_FS, 0x7f9231c4f700) = 0
mprotect(0x7f9231a43000, 16384, PROT_READ) = 0
mprotect(0x7f9231c6d000, 4096, PROT_READ) = 0
munmap(0x7f9231c51000, 105359) = 0
rt_sigaction(SIGINT, 0x400826, [INT], SA_RESTORER|SA_RESTART, 0x7f92316d70e0, SIG_DFL, [], 0, 8) = 0
open("/dev/pcihsd0", O_RDWR) = 3
ioctl(3, PHN_GETREG or RTC_PIE_ON, 0x7ffec60e3643) = 0
open("/dev/pcihsd0c", O_RDONLY|O_NONBLOCK) = 4
ioctl(4, 0x70c0, 0x1) = 0
fstat(1, st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9231c6a000
write(1, "\n", 1) = 1
write(1, "OK\n", 3) = 3
close(3) = 0
close(4) = 0
exit_group(0) = ?
+++ exited with 0 +++
故障程序:
execve("./pcihsd", ["./pcihsd"], [/* 18 vars */]) = 0
uname(sys="Linux", node="debian", ...) = 0
brk(0) = 0x83bb000
brk(0x83dc000) = 0x83dc000
rt_sigaction(SIGINT, 0x804848f, [INT], SA_RESTORER|SA_RESTART, 0x806ab28, SIG_DFL, [], 0, 8) = 0
rt_sigaction(SIGQUIT, 0x804842b, [QUIT], SA_RESTORER|SA_RESTART, 0x806ab28, SIG_DFL, [], 0, 8) = 0
open("PCIHSD.hlp", O_RDONLY) = 3
old_mmap(NULL, 266240, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff76d7000
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
access("/root/.terminfo/l/linux-fk", R_OK) = -1 ENOENT (No such file or directory)
access("/usr/share/terminfo/l/linux-fk", R_OK) = 0
open("/usr/share/terminfo/l/linux-fk", O_RDONLY) = 4
read(4, "\32\1/\0\35\0\20\0\1a\3", 12) = 12
read(4, "linux-fk|linux console with sF9 "..., 47) = 47
read(4, "\0\1\0\0\1\1\0\0\0\0\0\0\0\1\1\0\0\0\0\0\1\0\0\0\0\0\0\1\1", 29) = 29
read(4, "\377\377\10\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\10\0@\0\22\0", 32) = 32
read(4, "\377\377\0\0\2\0\4\0\25\0\32\0!\0%\0)\0\377\3774\0E\0G\0K\0W\0\377\377"..., 762) = 762
read(4, "\7\0\r\0\33[%i%p1%d;%p2%dr\0\33[3g\0\33[H\33[J"..., 865) = 865
read(4, "", 1) = 0
read(4, "", 10) = 0
close(4) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
ioctl(1, TIOCGWINSZ, ws_row=64, ws_col=160, ws_xpixel=0, ws_ypixel=0) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
brk(0x83fd000) = 0x83fd000
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig icanon echo ...) = 0
ioctl(1, SNDCTL_TMR_STOP or SNDRV_TIMER_IOCTL_GINFO or TCSETSW, B38400 opost isig -icanon echo ...) = 0
ioctl(1, SNDCTL_TMR_STOP or SNDRV_TIMER_IOCTL_GINFO or TCSETSW, B38400 opost isig -icanon -echo ...) = 0
rt_sigaction(SIGTSTP, NULL, SIG_DFL, [], 0, 8) = 0
rt_sigaction(SIGTSTP, 0x805d130, [], SA_RESTORER|SA_RESTART, 0x806ab28, NULL, 8) = 0
rt_sigaction(SIGINT, NULL, 0x804848f, [INT], SA_RESTORER|SA_RESTART, 0x806ab28, 8) = 0
rt_sigaction(SIGTERM, NULL, SIG_DFL, [], 0, 8) = 0
rt_sigaction(SIGTERM, 0x805d310, [], SA_RESTORER|SA_RESTART, 0x806ab28, NULL, 8) = 0
rt_sigaction(SIGWINCH, NULL, SIG_DFL, [], 0, 8) = 0
rt_sigaction(SIGWINCH, 0x805d410, [], SA_RESTORER, 0x806ab28, NULL, 8) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, B38400 opost isig -icanon -echo ...) = 0
ioctl(1, SNDCTL_TMR_STOP or SNDRV_TIMER_IOCTL_GINFO or TCSETSW, B38400 opost isig -icanon -echo ...) = 0
write(1, "\33[1;64r\33[0;10m\33[4l\33[?7h\33[?25h\33[?"..., 34) = 34
rt_sigaction(SIGTSTP, SIG_IGN, [], SA_RESTORER|SA_RESTART, 0x806ab28, 0x805d130, [], SA_RESTORER|SA_RESTART, 0x806ab28, 8) = 0
write(1, "\33[H\33[J\33[24d", 11) = 11
rt_sigaction(SIGTSTP, 0x805d130, [], SA_RESTORER|SA_RESTART, 0x806ab28, NULL, 8) = 0
write(1, "\33[?25l\33[?1c", 11) = 11
open("PCIHSD.dft", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/dev/pcihsd0", O_RDWR) = 4
ioctl(4, PHN_GETREG or RTC_PIE_ON, 0x80cd480) = 0
rt_sigaction(SIGALRM, 0x804950f, [], SA_RESTORER|SA_INTERRUPT|SA_NODEFER|SA_RESETHAND, 0x806ab28, SIG_DFL, [], 0, 8) = 0
open("/dev/pcihsd0c", O_RDONLY|O_NONBLOCK) = 5
ioctl(5, 0x70c0, 0x1) = -1 ENOTTY (Inappropriate ioctl for device)
close(4) = 0
close(5) = 0
如您所见,在这两种情况下,相关的 ioctl 调用都是相同的,即:
open("/dev/pcihsd0", O_RDWR) = descriptor1
ioctl(descriptor1, PHN_GETREG or RTC_PIE_ON, 0x7ffec60e3643) = 0
open("/dev/pcihsd0c", O_RDONLY|O_NONBLOCK) = descriptor2
ioctl(descriptor2, 0x70c0, 0x1) = 0/ENOTTY ???
问题1:
对 ioctl 的第二次调用(使用 cmd 0x70c0)在(旧)故障程序执行时没有调用内核的 sys_ioctl/vfs_ioctl 函数可能是什么原因(我在运行内核上设置了断点- 即使正确记录了对第一个 ioctl 的调用并且两个程序都命中了断点,内核也不会记录该调用?
问题 2:
在哪里插入断点来调试呢?为什么我在故障程序案例中根本没有看到sys_ioctl
被调用?
编辑:
感谢 Wumpus Q. Wumbley 对问题 1 的回答。
问题 2 的答案是:
当驱动程序实现compat_ioctl
时,调用compat_SyS_ioctl
而不是compat_ioctl
。从do_syscall32_irqs_on
/do_syscall_32_irqs_off
调用,从entry_INT80_compat
调用。
此外,struct file_operation
的 read
处理程序从 entry_INT80_compat
调用 sys32_pread
/SyS_pread64
。
【问题讨论】:
unlocked_ioctl
是为了内核驱动程序兼容性而创建的,即避免破坏不支持可重入 ioctl 的潜水员。我不相信用户空间需要知道使用哪个。
【参考方案1】:
从strace
结果中的不同指针值中,我可以看到有效的是64 位程序,而给出ENOTTY
的是32 位程序。
您需要定义一个compat_ioctl
以使您的驱动程序支持 32 位程序。
【讨论】:
但是如何向 ioctl 解释相同的参数值?正如我们所看到的,正确的值已经被传递了,这里实际失败的是什么? 如果有帮助,您可以想象ioctl
确实是 2 个不同的系统调用。当一个 32 位进程在 64 位内核上调用 ioctl
时,它实际上是在调用 compat_ioctl
。由于void *
参数的通用性质,它必须在每个驱动程序中单独实现。内核无法将值从 32 位用户空间格式转换为适合驱动程序的 64 位格式,因为它不知道它是否是指向 int
、short
、char[]
的指针...只有司机知道。其他系统调用没有这个问题(例如read
使用void *
,但它总是指向一个不透明的字节数组)。
compat_ioctl 是从 sys_ioctl 调用的,不是吗?问题是,如果未启用 compat_ioctl(我在 sys_ioctl 上设置了断点),则内核中看不到对 sys_ioctl 的调用。那么在这种情况下叫什么?
我不知道细节,也找不到任何针对驱动程序作者的文档来解释应该如何实现compat_ioctl
。也许如果你发布一个标题为How do I add a compat_ioctl to a driver?
的新问题,你可以让更有资格回答的人来回答
向该驱动程序添加 compat_ioctl 功能没有问题。我已经添加了它,它现在可以工作了。我不明白为什么在不使用 comapat_ioctl 时我没有看到对 sys_ioctl 的调用。让我再研究一下,也许我会找到答案。以上是关于为啥 ioctl 调用没有传递给 sys_ioctl?的主要内容,如果未能解决你的问题,请参考以下文章
Linux - 带有 FIONREAD 的 ioctl 始终为 0
为啥在 Promise.all() 之后不调用 onRejected,其中包含在数组中的 Promise.reject() 传递给 Promise.all()?