如何将虚拟地址从用户 space 转换为物理地址?(在 linux 设备驱动程序中)
How can I convert virtual address from user space to physical address?(in linux device driver)
我正在测试一个简单的字符设备驱动程序和一个使用该驱动程序的应用程序。所以我想传递一个数组的地址,在驱动中看到数组地址和第一个数组元素值作为第一步(顺便说一下,第一个元素本身就是一个指针)。
app.c
uint64_t __attribute__(( aligned(64) )) args[32]; // set to enough size
printf("args = %p\n", args);
printf("app : args[0] = %p\n", ((uint64_t *)args)[0]);
ioctl(fd, CallSetBareMetalMode, uint64_t)args);
driver.c
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case CallSetBareMetalMode:
printk("driver:cmd = %x, arg = %p\n", cmd, (uint64_t *)arg); // line 177
printk("driver:arg[0] = %llx\n", ((uint64_t *)arg)[0]);
执行后打印出来是这样的
args = 0x442f40
app : args[0] = 443040
[12603.293899] driver:cmd = 40086142, arg = 0000000030dec968
[12603.294509] Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000442f40
Q1 : 这不是我的主要问题,但为什么驱动程序中的args值打印为0000000030dec968?
但是如果我在第 177 行更改打印,
printk("driver:cmd = %x, arg = %lx\n", cmd, (uint64_t *)arg); // line 177
对于打印,arg 至少打印正确。
args = 0x442f40
app : args[0] = 443040
[13204.162435] driver:cmd = 40086142, arg = 442f40
[13204.162886] Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000442f40
现在,我发现我应该使用 copy_from_user 函数来使用用户 space 的地址。所以我将第 178 行更改为
copy_from_user(&value, (void __user *)arg, 8);
printk("value (arg[0]) = %llx\n", value);
并且驱动程序打印
[13812.691957] driver:cmd = 40086142, arg = 442f40
[13812.693277] value (arg[0]) = 443040
ok,获取用户space的虚拟地址。现在回答我真正的问题..
Q2 : 我必须将虚拟地址从用户 space 更改为“物理地址”并将此值写入寄存器或内存。如何将此用户 space 虚拟地址更改为物理地址? (记得我在内核 space 中,因为我在驱动程序中,对吧?)并且如果我使用 copy_to_user 函数将这个转换后的物理地址写入内存位置(当然是在用户虚拟地址处,只有写入的数据是物理地址),它会是与原始应用程序共享的同一物理页面吗?非常感谢用简单的代码进行解释。
添加
我把77写到test_val,然后把test_val的地址传给args[2]。然后我像以前一样将 args 的地址传递给驱动程序。
app.c
uint64_t __attribute__(( aligned(64) )) args[32];
uint64_t test_val = 77;
args[2] = (uint64_t) &test_val; // let's see it's changed to 78
printf("app : args[2] = %p, *args[2] = %lld\n", args[2], *(uint64_t *)args[2]);
ioctl(fd, CallSetBareMetalMode, (uint64_t)args);
driver.c
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case CallSetBareMetalMode:
printk("driver:cmd = %x, arg = %lx\n", cmd, arg);
copy_from_user(&args, (void __user *)arg, 8*3);
printk("args[2] = %llx\n", args[2]);
offs = args[2] % 4096;
down_read(¤t->mm->mmap_sem);
res = get_user_pages( (unsigned long)args[2], 1, 1, &pages, NULL);
printk("get_user_pages done! args[2] = %px\n", args[2]);
if (res) {
printk(KERN_INFO "Got mmaped.\n");
kvpaddr = kmap(pages);
printk("kmap done!\n");
printk(KERN_INFO "kvpaddr = %px, ofs = %x\n", kvpaddr, offs);
printk("xx = %llx\n", ((unsigned long long int)(kvpaddr)+offs));
*(uint64_t *)((unsigned long long int)(kvpaddr)+offs) = 78;
printk(KERN_INFO "changed value : %lld\n",\
*(uint64_t *)((unsigned int)(kvpaddr)+offs));
put_page(pages); //page_cache_release(page);
printk("put_page done!\n");
}
else {
printk("get_user_pages failed!\n");
}
up_read(¤t->mm->mmap_sem);
运行输出
args = 0x442f40
app : args[0] = 0x443040
app : args[2] = 0xffffd2e44d98, *args[2] = 77
[85194.544029] driver:cmd = 40086142, arg = 442f40
[85194.544822] args[2] = ffffd2e44d98
[85194.545613] get_user_pages done! args[2] = 0000ffffd2e44d98
[85194.546004] Got mmaped.
[85194.546248] kmap done!
[85194.546536] kvpaddr = ffff00001f7c0000, ofs = d98
[85194.546976] kvaddr = ffff00001f7c0d98
[85194.548645] Unable to handle kernel paging request at virtual address 000000001f7c0d98
[85194.549245] Mem abort info:
[85194.549513] ESR = 0x96000006
[85194.549929] EC = 0x25: DABT (current EL), IL = 32 bits
[85194.550364] SET = 0, FnV = 0
[85194.550719] EA = 0, S1PTW = 0
[85194.551008] Data abort info:
[85194.551555] ISV = 0, ISS = 0x00000006
[85194.551938] CM = 0, WnR = 0
[85194.552609] user pgtable: 4k pages, 48-bit VAs, pgdp=00000000568fb000
[85194.553441] [000000001f7c0d98] pgd=0000000056209003, pud=0000000047b97003, pmd=0000000000000000
[85194.555783] Internal error: Oops: 96000006 [#16] SMP
[85194.556761] Modules linked in: chr_drv_ex1(OE) nls_iso8859_1 dm_multipath scsi_dh_rdac scsi_dh_emc scsi_dh_alua qemu_fw_cfg sch_fq_codel ppdev lp parport drm ip_tables x_tables autofs4 btrfs zstd_compress raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor xor_neon raid6_pq libcrc32c raid1 raid0 multipath linear crct10dif_ce ghash_ce sm4_ce sm4_generic sm3_ce sm3_generic sha3_ce sha3_generic sha512_ce sha512_arm64 sha2_ce sha256_arm64 sha1_ce virtio_net net_failover virtio_blk failover aes_neon_bs aes_neon_blk aes_ce_blk crypto_simd cryptd aes_ce_cipher [last unloaded: chr_drv_ex1]
[85194.563741] CPU: 2 PID: 4258 Comm: test_axpu_app Tainted: G D W OE 5.4.0-77-generic #86-Ubuntu
[85194.564638] Hardware name: QEMU QEMU Ab21q Virtual Machine, BIOS 0.0.0 02/06/2015
[85194.565687] pstate: 60400005 (nZCv daif +PAN -UAO)
[85194.568143] pc : my_ioctl+0x310/0x370 [chr_drv_ex1]
[85194.568854] lr : my_ioctl+0x300/0x370 [chr_drv_ex1]
[85194.569473] sp : ffff80001372bd30
[85194.569838] x29: ffff80001372bd30 x28: ffff000009f7bc00
[85194.570354] x27: 0000000000000000 x26: 0000000000000000
[85194.570796] x25: 0000000056000000 x24: ffff00000d3c35e0
[85194.571308] x23: ffff000016875600 x22: 0000000000000d98
[85194.571870] x21: 000000001f7c0d98 x20: 0000000000442f40
[85194.572273] x19: ffff00001f7c0000 x18: 0000000000000010
[85194.572669] x17: 0000000000000000 x16: 0000000000000000
[85194.573126] x15: ffff000009f7c128 x14: ffffffffffffffff
[85194.573632] x13: ffff80009372ba77 x12: ffff80001372ba7f
[85194.574127] x11: ffff800011b9e000 x10: 0000000000000000
[85194.574578] x9 : ffff800011db4000 x8 : 00000000000005f2
[85194.575193] x7 : 0000000000000017 x6 : ffff800011db39d4
[85194.575700] x5 : 0000000000000000 x4 : ffff00001feb5250
[85194.576139] x3 : ffff00001fec56c8 x2 : 0000000000000000
[85194.576550] x1 : 0000000000000000 x0 : ffff80000924b220
[85194.577379] Call trace:
[85194.577805] my_ioctl+0x310/0x370 [chr_drv_ex1]
[85194.579423] do_vfs_ioctl+0xc64/0xe60
[85194.579846] ksys_ioctl+0x88/0xb8
[85194.580116] __arm64_sys_ioctl+0x2c/0x228
[85194.580479] el0_svc_common.constprop.0+0xe4/0x1f0
[85194.580929] el0_svc_handler+0x38/0xa8
[85194.581293] el0_svc+0x10/0x2c8
[85194.582065] Code: d28009c0 f8336ac0 b0000000 91088000 (f94002a1)
[85194.583646] ---[ end trace bd1ac75ca265aec2 ]---
[85194.590696] Device File closed..
Segmentation fault (core dumped)
我以为我把用户虚拟地址改成了内核虚拟地址,但是写入内核虚拟地址导致了trap。有人可以帮我吗?
首先使用 virt_to_phys()
只应该与内核 lowmem
(直接映射的 RAM 区域)区域一起使用,因此您无法获取用户的物理地址 space 它甚至是不允许的即使使用像 vmalloc 这样的内核 space 区域。只允许 lowmem
区域,因为它位于 PAGE_OFFSET
.
的固定偏移处
如果我是对的,您可以尝试直接写入物理内存,并希望您的结果将在用户 space 应用程序中看到。如果这是您想要的,则没有办法做到这一点(至少是一种简单的方法)。
破解方法如下:您需要查看应用程序的页表并记录该地址在物理内存中的位置。在 x86 上,页表地址存储在 CR3
寄存器中。下一个问题是 CPU 已经设置为保护模式,CR0
中设置了 PG
标志(即;使用分页),这使得 MMU
将地址读取为虚拟地址并将其本身转换为物理(你不能干涉)。因此,要访问物理内存,您必须切换到禁用分页的实模式(如果您将使用实模式,则必须如此)。这可能意味着您将不得不调用 BIOS 例程来为您完成艰苦的工作。
我正在测试一个简单的字符设备驱动程序和一个使用该驱动程序的应用程序。所以我想传递一个数组的地址,在驱动中看到数组地址和第一个数组元素值作为第一步(顺便说一下,第一个元素本身就是一个指针)。
app.c
uint64_t __attribute__(( aligned(64) )) args[32]; // set to enough size
printf("args = %p\n", args);
printf("app : args[0] = %p\n", ((uint64_t *)args)[0]);
ioctl(fd, CallSetBareMetalMode, uint64_t)args);
driver.c
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case CallSetBareMetalMode:
printk("driver:cmd = %x, arg = %p\n", cmd, (uint64_t *)arg); // line 177
printk("driver:arg[0] = %llx\n", ((uint64_t *)arg)[0]);
执行后打印出来是这样的
args = 0x442f40
app : args[0] = 443040
[12603.293899] driver:cmd = 40086142, arg = 0000000030dec968
[12603.294509] Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000442f40
Q1 : 这不是我的主要问题,但为什么驱动程序中的args值打印为0000000030dec968?
但是如果我在第 177 行更改打印,
printk("driver:cmd = %x, arg = %lx\n", cmd, (uint64_t *)arg); // line 177
对于打印,arg 至少打印正确。
args = 0x442f40
app : args[0] = 443040
[13204.162435] driver:cmd = 40086142, arg = 442f40
[13204.162886] Unable to handle kernel access to user memory outside uaccess routines at virtual address 0000000000442f40
现在,我发现我应该使用 copy_from_user 函数来使用用户 space 的地址。所以我将第 178 行更改为
copy_from_user(&value, (void __user *)arg, 8);
printk("value (arg[0]) = %llx\n", value);
并且驱动程序打印
[13812.691957] driver:cmd = 40086142, arg = 442f40
[13812.693277] value (arg[0]) = 443040
ok,获取用户space的虚拟地址。现在回答我真正的问题..
Q2 : 我必须将虚拟地址从用户 space 更改为“物理地址”并将此值写入寄存器或内存。如何将此用户 space 虚拟地址更改为物理地址? (记得我在内核 space 中,因为我在驱动程序中,对吧?)并且如果我使用 copy_to_user 函数将这个转换后的物理地址写入内存位置(当然是在用户虚拟地址处,只有写入的数据是物理地址),它会是与原始应用程序共享的同一物理页面吗?非常感谢用简单的代码进行解释。
添加
我把77写到test_val,然后把test_val的地址传给args[2]。然后我像以前一样将 args 的地址传递给驱动程序。
app.c
uint64_t __attribute__(( aligned(64) )) args[32];
uint64_t test_val = 77;
args[2] = (uint64_t) &test_val; // let's see it's changed to 78
printf("app : args[2] = %p, *args[2] = %lld\n", args[2], *(uint64_t *)args[2]);
ioctl(fd, CallSetBareMetalMode, (uint64_t)args);
driver.c
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case CallSetBareMetalMode:
printk("driver:cmd = %x, arg = %lx\n", cmd, arg);
copy_from_user(&args, (void __user *)arg, 8*3);
printk("args[2] = %llx\n", args[2]);
offs = args[2] % 4096;
down_read(¤t->mm->mmap_sem);
res = get_user_pages( (unsigned long)args[2], 1, 1, &pages, NULL);
printk("get_user_pages done! args[2] = %px\n", args[2]);
if (res) {
printk(KERN_INFO "Got mmaped.\n");
kvpaddr = kmap(pages);
printk("kmap done!\n");
printk(KERN_INFO "kvpaddr = %px, ofs = %x\n", kvpaddr, offs);
printk("xx = %llx\n", ((unsigned long long int)(kvpaddr)+offs));
*(uint64_t *)((unsigned long long int)(kvpaddr)+offs) = 78;
printk(KERN_INFO "changed value : %lld\n",\
*(uint64_t *)((unsigned int)(kvpaddr)+offs));
put_page(pages); //page_cache_release(page);
printk("put_page done!\n");
}
else {
printk("get_user_pages failed!\n");
}
up_read(¤t->mm->mmap_sem);
运行输出
args = 0x442f40
app : args[0] = 0x443040
app : args[2] = 0xffffd2e44d98, *args[2] = 77
[85194.544029] driver:cmd = 40086142, arg = 442f40
[85194.544822] args[2] = ffffd2e44d98
[85194.545613] get_user_pages done! args[2] = 0000ffffd2e44d98
[85194.546004] Got mmaped.
[85194.546248] kmap done!
[85194.546536] kvpaddr = ffff00001f7c0000, ofs = d98
[85194.546976] kvaddr = ffff00001f7c0d98
[85194.548645] Unable to handle kernel paging request at virtual address 000000001f7c0d98
[85194.549245] Mem abort info:
[85194.549513] ESR = 0x96000006
[85194.549929] EC = 0x25: DABT (current EL), IL = 32 bits
[85194.550364] SET = 0, FnV = 0
[85194.550719] EA = 0, S1PTW = 0
[85194.551008] Data abort info:
[85194.551555] ISV = 0, ISS = 0x00000006
[85194.551938] CM = 0, WnR = 0
[85194.552609] user pgtable: 4k pages, 48-bit VAs, pgdp=00000000568fb000
[85194.553441] [000000001f7c0d98] pgd=0000000056209003, pud=0000000047b97003, pmd=0000000000000000
[85194.555783] Internal error: Oops: 96000006 [#16] SMP
[85194.556761] Modules linked in: chr_drv_ex1(OE) nls_iso8859_1 dm_multipath scsi_dh_rdac scsi_dh_emc scsi_dh_alua qemu_fw_cfg sch_fq_codel ppdev lp parport drm ip_tables x_tables autofs4 btrfs zstd_compress raid10 raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor xor_neon raid6_pq libcrc32c raid1 raid0 multipath linear crct10dif_ce ghash_ce sm4_ce sm4_generic sm3_ce sm3_generic sha3_ce sha3_generic sha512_ce sha512_arm64 sha2_ce sha256_arm64 sha1_ce virtio_net net_failover virtio_blk failover aes_neon_bs aes_neon_blk aes_ce_blk crypto_simd cryptd aes_ce_cipher [last unloaded: chr_drv_ex1]
[85194.563741] CPU: 2 PID: 4258 Comm: test_axpu_app Tainted: G D W OE 5.4.0-77-generic #86-Ubuntu
[85194.564638] Hardware name: QEMU QEMU Ab21q Virtual Machine, BIOS 0.0.0 02/06/2015
[85194.565687] pstate: 60400005 (nZCv daif +PAN -UAO)
[85194.568143] pc : my_ioctl+0x310/0x370 [chr_drv_ex1]
[85194.568854] lr : my_ioctl+0x300/0x370 [chr_drv_ex1]
[85194.569473] sp : ffff80001372bd30
[85194.569838] x29: ffff80001372bd30 x28: ffff000009f7bc00
[85194.570354] x27: 0000000000000000 x26: 0000000000000000
[85194.570796] x25: 0000000056000000 x24: ffff00000d3c35e0
[85194.571308] x23: ffff000016875600 x22: 0000000000000d98
[85194.571870] x21: 000000001f7c0d98 x20: 0000000000442f40
[85194.572273] x19: ffff00001f7c0000 x18: 0000000000000010
[85194.572669] x17: 0000000000000000 x16: 0000000000000000
[85194.573126] x15: ffff000009f7c128 x14: ffffffffffffffff
[85194.573632] x13: ffff80009372ba77 x12: ffff80001372ba7f
[85194.574127] x11: ffff800011b9e000 x10: 0000000000000000
[85194.574578] x9 : ffff800011db4000 x8 : 00000000000005f2
[85194.575193] x7 : 0000000000000017 x6 : ffff800011db39d4
[85194.575700] x5 : 0000000000000000 x4 : ffff00001feb5250
[85194.576139] x3 : ffff00001fec56c8 x2 : 0000000000000000
[85194.576550] x1 : 0000000000000000 x0 : ffff80000924b220
[85194.577379] Call trace:
[85194.577805] my_ioctl+0x310/0x370 [chr_drv_ex1]
[85194.579423] do_vfs_ioctl+0xc64/0xe60
[85194.579846] ksys_ioctl+0x88/0xb8
[85194.580116] __arm64_sys_ioctl+0x2c/0x228
[85194.580479] el0_svc_common.constprop.0+0xe4/0x1f0
[85194.580929] el0_svc_handler+0x38/0xa8
[85194.581293] el0_svc+0x10/0x2c8
[85194.582065] Code: d28009c0 f8336ac0 b0000000 91088000 (f94002a1)
[85194.583646] ---[ end trace bd1ac75ca265aec2 ]---
[85194.590696] Device File closed..
Segmentation fault (core dumped)
我以为我把用户虚拟地址改成了内核虚拟地址,但是写入内核虚拟地址导致了trap。有人可以帮我吗?
首先使用 virt_to_phys()
只应该与内核 lowmem
(直接映射的 RAM 区域)区域一起使用,因此您无法获取用户的物理地址 space 它甚至是不允许的即使使用像 vmalloc 这样的内核 space 区域。只允许 lowmem
区域,因为它位于 PAGE_OFFSET
.
如果我是对的,您可以尝试直接写入物理内存,并希望您的结果将在用户 space 应用程序中看到。如果这是您想要的,则没有办法做到这一点(至少是一种简单的方法)。
破解方法如下:您需要查看应用程序的页表并记录该地址在物理内存中的位置。在 x86 上,页表地址存储在 CR3
寄存器中。下一个问题是 CPU 已经设置为保护模式,CR0
中设置了 PG
标志(即;使用分页),这使得 MMU
将地址读取为虚拟地址并将其本身转换为物理(你不能干涉)。因此,要访问物理内存,您必须切换到禁用分页的实模式(如果您将使用实模式,则必须如此)。这可能意味着您将不得不调用 BIOS 例程来为您完成艰苦的工作。