Linux kernel 执行读取硬件寄存器的函数时内核OOP

Linux kernel 执行读取硬件寄存器的函数时内核OOP,linux-kernel,linux-device-driver,Linux Kernel,Linux Device Driver,我在分析这段导致问题的代码时引用了帮助。对于每个人来说,我正在为ahci驱动程序开发一个角色驱动程序,它将充当从用户空间直接到硬件的通道。为此,我正在相应地修改ahci驱动程序 我从小事做起。我想查看VM上AHCI HBA的HBA端口0的端口寄存器。我的角色驱动程序ioctl代码: switch (cmd) { case AHCIP_GPORT_REG: pPciDev = pci_get_device(0x8086, 0x2829, NULL); if

我在分析这段导致问题的代码时引用了帮助。对于每个人来说,我正在为ahci驱动程序开发一个角色驱动程序,它将充当从用户空间直接到硬件的通道。为此,我正在相应地修改ahci驱动程序

我从小事做起。我想查看VM上AHCI HBA的HBA端口0的端口寄存器。我的角色驱动程序ioctl代码:

switch (cmd) {
    case AHCIP_GPORT_REG:
        pPciDev = pci_get_device(0x8086, 0x2829, NULL);

        if (pPciDev) {
            /* This will set ret to the value that it needs to be.  This
             * is true of __put_user() too */
            if ((ret = __get_user(off, (u32*)obj))) {
                printk(KERN_INFO "unable to read from user space\n");
                goto ioctl_quick_out;
            }

            reg = get_port_reg(&pPciDev->dev, off);
            if ((ret = __put_user(reg, (u32*)obj)))
            {
                printk(KERN_INFO "Unable to write to user space\n");
            }

            pci_dev_put(pPciDev);
        }

        // This break wasn't in the code when it crashed
        break;

    default:
        // POSIX compliance with this one (REF of LDD3)
        ret = -ENOTTY;
}
此字符驱动程序调用的ahci.c修改版本中的代码:

u32 get_port_reg(struct device *dev, u32 off)
{
    struct Scsi_Host *shost = class_to_shost(dev);
    struct ata_port *ap = ata_shost_to_port(shost);
    void __iomem *port_mmio = ahci_port_base(ap);

    return ioread32(port_mmio + off);
}
EXPORT_SYMBOL(get_port_reg);
这导致的内核OOP发生在这里:

PID: 3357   TASK: ffff88011c9b7500  CPU: 0   COMMAND: "peek"
 #0 [ffff8800abfc79f0] machine_kexec at ffffffff8103b5bb
 #1 [ffff8800abfc7a50] crash_kexec at ffffffff810c9852
 #2 [ffff8800abfc7b20] oops_end at ffffffff8152e0f0
 #3 [ffff8800abfc7b50] no_context at ffffffff8104c80b
 #4 [ffff8800abfc7ba0] __bad_area_nosemaphore at ffffffff8104ca95
 #5 [ffff8800abfc7bf0] bad_area at ffffffff8104cbbe
 #6 [ffff8800abfc7c20] __do_page_fault at ffffffff8104d36f
 #7 [ffff8800abfc7d40] do_page_fault at ffffffff8153003e
 #8 [ffff8800abfc7d70] page_fault at ffffffff8152d3f5
    [exception RIP: get_port_reg+18]
    RIP: ffffffffa03c4cd2  RSP: ffff8800abfc7e28  RFLAGS: 00010246
    RAX: 0000000000020101  RBX: 00007fff17273960  RCX: ffffffff812b0710
    RDX: ffff88011ddd5000  RSI: 0000000000000000  RDI: ffff88011ddd5090
    RBP: ffff8800abfc7e28   R8: 0000000000000000   R9: 0000000000000000
    R10: 00000000000007d5  R11: 0000000000000006  R12: ffff88011ddd5000
    R13: 0000000000000000  R14: 0000000000000000  R15: 0000000000000000
    ORIG_RAX: ffffffffffffffff  CS: 0010  SS: 0018
如您所见,指令指针是
get\u port\u reg+18
。由于这个函数非常小,下面是完整的反汇编

crash> dis get_port_reg
0xffffffffa03c4cc0 <get_port_reg>:      push   %rbp
0xffffffffa03c4cc1 <get_port_reg+1>:    mov    %rsp,%rbp
0xffffffffa03c4cc4 <get_port_reg+4>:    nopl   0x0(%rax,%rax,1)
0xffffffffa03c4cc9 <get_port_reg+9>:    mov    0x240(%rdi),%rax
0xffffffffa03c4cd0 <get_port_reg+16>:   mov    %esi,%esi
0xffffffffa03c4cd2 <get_port_reg+18>:   mov    0x2838(%rax),%rdx
0xffffffffa03c4cd9 <get_port_reg+25>:   mov    0x28(%rax),%eax
0xffffffffa03c4cdc <get_port_reg+28>:   mov    0x10(%rdx),%rdx
0xffffffffa03c4ce0 <get_port_reg+32>:   shl    $0x7,%eax
0xffffffffa03c4ce3 <get_port_reg+35>:   mov    %eax,%eax
0xffffffffa03c4ce5 <get_port_reg+37>:   add    0x28(%rdx),%rax
0xffffffffa03c4ce9 <get_port_reg+41>:   lea    0x100(%rax,%rsi,1),%rdi
0xffffffffa03c4cf1 <get_port_reg+49>:   callq  0xffffffff8129dde0 <ioread32>
0xffffffffa03c4cf6 <get_port_reg+54>:   leaveq 
0xffffffffa03c4cf7 <get_port_reg+55>:   retq   
0xffffffffa03c4cf8 <get_port_reg+56>:   nopl   0x0(%rax,%rax,1)
crash>dis-get\u-port\u-reg
0xFFFF03C4CC0:推送%rbp
0xFFFFFF03C4CC1:mov%rsp,%rbp
0xFFFFFF03C4CC4:nopl 0x0(%rax,%rax,1)
0xFFFFFF03C4CC9:mov 0x240(%rdi),%rax
0xFFFFFF03C4CD0:mov%esi,%esi
0xFFFFFF03C4CD2:mov 0x2838(%rax),%rdx
0xFFFFFF03C4CD9:mov 0x28(%rax),%eax
0xFFFFFF03C4CDC:mov 0x10(%rdx),%rdx
0xFFFFFF03C4CE0:shl$0x7,%eax
0xFFFFFF03C4CE3:mov%eax,%eax
0xFFFFFF03C4CE5:添加0x28(%rdx),%rax
0xFFFFFF03C4CE9:lea 0x100(%rax,%rsi,1),%rdi
0xFFFFFF03C4CF1:callq 0xffffffff8129dde0
0xFFFFFF03C4CF6:LEVEQ
0xFFFFFF03C4CF7:retq
0xFFFFFF03C4CF8:nopl 0x0(%rax,%rax,1)
你可能已经猜到了,我是个装配新手。哪一行代码是
get\u port\u reg+18
?我很困惑,因为我在该函数的每一行调用函数,但我看到的唯一调用是
ioread32()

为了便于参考,我在
ahci\u show\u port\u cmd()之后对我的函数
get\u port\u reg
进行了建模。我想不出任何其他方法来获得操作所需的
struct pci_dev
结构。我是否滥用了
get\u pci\u device()
pci\u dev\u put()
?这根本不是问题吗

谢谢你的帮助

安迪

我将发布我自己的答案。我的问题的两位评论员让我走上了解决这一问题的正确道路。正如我提到的,我的方法是做一些我在ahci驱动程序(ahci.c)中看到的事情。基本上,这个假设很简单,需要一个
struct设备*
,从中可以得到所需的
ata\u端口
信息。我在ahci.c中看到,作者完成了
structdevice*=&pdev->dev偶尔。换句话说,我认为
struct pci_dev
的开发人员正在满足我的需要。我显然不知道“类类型”或类似的东西(见@myaut的第一条评论)@alexhoppus基本上根据我发布的代码和反汇编得出了相同/相似的结论

我采用的修复方法确实很有效,如下所示:

/* ioctl code in character driver */
switch (cmd) {
    case AHCIP_GPORT_REG:
        pPciDev = pci_get_device(0x8086, 0x2829, NULL);

        if (pPciDev) {
            struct ata_host *pHost = NULL;
            struct ata_port *pPort = NULL;
            printk(KERN_INFO "found the PCI device\n");
            /* Get the devices driver data */
            pHost = pci_get_drvdata(pPciDev);
            if (!pHost) {
                ret = -EFAULT;
                goto ioctl_valid_pci_dev_out;
            }

            /* for this test, we'll use just port 0 */
            pPort = pHost->ports[0];
            if (!pPort) {
                ret = -EFAULT;
                goto ioctl_valid_pci_dev_out;
            }

            /* This will set ret to the value that it needs to be.  This
             * is true of __put_user() too */
            if ((ret = __get_user(off, (u32*)obj))) {
                printk(KERN_INFO "unable to read from user space\n");
                goto ioctl_valid_pci_dev_out;
            }

            reg = get_port_reg(pPort, off);
            if ((ret = __put_user(reg, (u32*)obj)))
            {
                printk(KERN_INFO "Unable to write to user space\n");
            }
        }

        break;

    default:
        // POSIX compliance with this one (REF of LDD3)
        ret = -ENOTTY;
}
ahci驱动程序也因此进行了修改

u32 get_port_reg(struct ata_port* pPort, u32 off)
{
    void __iomem *port_mmio = ahci_port_base(pPort);

    return ioread32(port_mmio + off);
}
EXPORT_SYMBOL(get_port_reg);
虽然这已经解决了我的问题,但如果有人能向我解释一下
(struct pci_dev)device.dev.p->driver_data中的内容,我将不胜感激。我可以使用Linux交叉引用工具来查看数据类型。什么应该存储在
struct device_private`?我现在用它来获取我需要的数据。我真的很感激有人评论这个答案来解释那个答案


多亏了@myaut和@alexhoppus

pci\u get\u device()
可能会返回一个pci设备,虽然您需要相应的SCSI主机设备,但它们有不同的类。请看
ata\U pci\U remove\U one
:它显示了如何从pci设备获取
ata\U主机
,从中获取
ata\U端口
应该很简单。问题似乎就在这里-ata\U shost\U to\U端口。我可以从技术角度告诉您,不需要理解代码:函数类\u to \u shost基本上是的容器,因此它假设结构设备(dev)嵌入到结构Scsi_主机中,因此它将dev适当地转换为Scsi_主机。接下来,代码尝试取消对shost的引用以获取ata_端口(*(结构ata_端口**)&主机->主机数据[0])。然后砰的一声。。。看来这里发生了翻页错误。这意味着可能存在一个垃圾箱而不是结构Scsi_主机。。。