根据crash学习ARM64虚拟地址空间布局

2020-04-10 14:35:29 浏览数 (1)

我们先来看一个出错的现场:

代码语言:javascript复制
[   55.195101] Unable to handle kernel paging request at virtual address ffffdfc7be9c2100
[   55.195107] Mem abort info:
[   55.195109]   ESR = 0x96000004
[   55.195112]   Exception class = DABT (current EL), IL = 32 bits
[   55.195114]   SET = 0, FnV = 0
[   55.195117]   EA = 0, S1PTW = 0
[   55.195118] Data abort info:
[   55.195120]   ISV = 0, ISS = 0x00000004
[   55.195122]   CM = 0, WnR = 0
[   55.195125] [ffffdfc7be9c2100] address between user and kernel address ranges
[   55.195128] Internal error: Oops: 96000004 [#1] PREEMPT SMP
.............

可以看到出错提示是:Unable to hanle kernel paging requeset at virtual address ffffdfc7be9c2100。为什么0xffffdfc7be9c2100这个地址是非法的? 接着再看“address between user and kernel address ranges”

为什么这个地址ffffdfc7be9c2100会出现异常呢? 这就得说下ARM64的虚拟地址空间布局了。再说ARM64之前,需要说下32位的虚拟地址空间。

在32位机器上,整个4G地址空间被分为2份,用户空间占用0-3G, 内核空间占有3G-4G的1G空间。那到64位机器上,虚拟地址空间应该如何分布?

在ARM64上目前还不完全支持64位的虚拟地址,那是如何分配的呢?ARM支持多种位数的虚拟地址宽度,如下图是支持48位的虚拟地址宽度

用户空间的范围是0x0000 0000 0000 0000 到 0x0000 FFFF FFFF FFFF,高16位是全0。内核空间的地址范围是0xFFFF 0000 0000 0000 到 0xFFFF FFFF FFFF FFFF,高16位全位1。

那中间[0x0000 FFFF FFFF FFFF FFFF 0xFFFF 0000 0000 0000 ]就属于非法区域了。如果运行过程中,CPU访问到这块区域,就会触发mem_abort异常。

那我们使用的模拟器平台上使用的多少位地址宽度呢? 我们的模拟器平台使用的地址位宽度为39位。

代码语言:javascript复制
/*
 * TASK_SIZE - the maximum size of a user space task.
 * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area.
 */
#ifdef CONFIG_COMPAT
#ifdef CONFIG_ARM64_64K_PAGES
/*
 * With CONFIG_ARM64_64K_PAGES enabled, the last page is occupied
 * by the compat vectors page.
 */
#define TASK_SIZE_32        UL(0x100000000)
#else
#define TASK_SIZE_32        (UL(0x100000000) - PAGE_SIZE)
#endif /* CONFIG_ARM64_64K_PAGES */
#define TASK_SIZE        (test_thread_flag(TIF_32BIT) ? 
                TASK_SIZE_32 : TASK_SIZE_64)
#define TASK_SIZE_OF(tsk)    (test_tsk_thread_flag(tsk, TIF_32BIT) ? 
                TASK_SIZE_32 : TASK_SIZE_64)
#else
#define TASK_SIZE        TASK_SIZE_64
#endif /* CONFIG_COMPAT */
 
 
#define VA_BITS            (CONFIG_ARM64_VA_BITS)
#define TASK_SIZE_64        (UL(1) << VA_BITS)

因为用户虚拟地址空间各个进程都是相互隔离的,每个进程感觉自己都能看到整个虚拟地址空间,大小用TASK_SIZE表示

  • 32位用户空间程序:TASK_SIZE=TASK_SIZE_32=0x100000000=4G (64位内核运行32位应用程序时)
  • 64位用户空间程序:TASK_SIZE=TASK_SIZE_64=1<<39

如果地址宽度是39位的话,虚拟地址空间分布如下:

这下清楚了ARM64位虚拟地址空间布局后,我们再回过头去看看我们出错的地址。CPU要访问的地址是ffffdfc7be9c2100,此地址刚好落在在非法区域,所以导致出错,此问题可能存在bit位翻转f→d

再来看下代码是如何判断非法区域的访问的

代码语言:javascript复制
129/*
130 * Dump out the page tables associated with 'addr' in the currently active mm.
131 */
132void show_pte(unsigned long addr)
133{
134 struct mm_struct *mm;
135 pgd_t *pgdp;
136 pgd_t pgd;
137
138 if (addr < TASK_SIZE) {
139     /* TTBR0 */
140     mm = current->active_mm;
141     if (mm == &init_mm) {
142         pr_alert("[6lx] user address but active_mm is swappern",
143              addr);
144         return;
145     }
146 } else if (addr >= VA_START) {
147     /* TTBR1 */
148     mm = &init_mm;
149 } else {
150     pr_alert("[6lx] address between user and kernel address rangesn",
151          addr);
152     return;
153 }

如果访问的地址小于是TASK_SIZE就是用户空间的地址,需要设置ttbr0。

如果访问的地址大于VA_START,就是内核区域的地址,需要设置ttbr1。

如果访问的地址落在非法区域,就会打印上述的错误。

0 人点赞