ARM深入理解-hypervisor调试方法一(异常寄存器分析)

简介: ARM深入理解-hypervisor调试方法一(异常寄存器分析)

我们在编写裸机程序(baremetal)、虚拟化管理程序(hypervisor)和操作系统(OS)时,Debug分析程序是必不可少的。不像linux内核,有大量的调试方法,很多裸机程序、hypervisor没有完善的调试分析方法。


异常相关寄存器


但也不是无计可施,在硬件上,ARM架构为程序的异常行为提供了详细的寄存器:

  • ESR_ELx寄存器(x=1,2,3
    保存发生异常时的特征,比如异常分类(ESR_ELx.EC)、异常具体原因(ESR_ELx.ISS)等。
  • ELR_ELx寄存器(x=1,2,3
    保存发生异常时,保存要返回的地址,一般情况下就是发生异常时的指令地址。
  • FAR_ELx寄存器(x=1,2,3
    保存错误地址。
  • HPFAR_EL2寄存器
    保存stage-2阶段的地址转换发生的错误IPA地址。

ARM从硬件架构上设计了4层异常级:EL0EL1EL2EL3。不同特权等级的程序,运行在不同的异常级上。本文从hypervisor虚拟机管理程序的角度,讲解如何利用这些寄存器,对程序的异常情况进行分析。


hypervisor本身的abort异常


我们以meta-hypervisor出现的具体异常为例:

esr_el2 = 0x97010046
elr_el2 = 0xfd8000005880
far_el2 = 0xfd8000005880

在这儿,esr_el2的值为0x97010046,对应的位域为:

EC IL ISS
100101 1 1_0000_0001_0000_0000_0100_0110
  • EC = 100101:
    说明是数据abort异常,但是没有发生异常级改变(EL2);或者,当支持嵌套虚拟化时与VNCR_EL2相关的访问产生的数据abort异常
  • ISS编码(数据abort异常的具体原因)
ISV SAS SSE SRT SF AR VNCR LST FnV EA CM S1PTW WnR DFSC
24 23-22 21 20-16 15 14 13 12-11 10 9 8 7 6 5-0
1 00 0 00001 0 0 0 00 0 0 0 0 1 000110
  • 通过上面各个位域的信息,综合得出:把W1寄存器中一个字节的数据写入内存时发生的错误
    我们再来看汇编代码中,0xfd8000005880地址处的内容:
void *memset(void *dest, uint32_t c, uint32_t count)
{
    // ......省略
        *d = c;
    fd8000005874:   b94007e0    ldr w0, [sp, #4]
    fd8000005878:   12001c01    and w1, w0, #0xff
    fd800000587c:   f9400fe0    ldr x0, [sp, #24]
    fd8000005880:   39000001    strb    w1, [x0]
        d++;
    fd8000005884:   f9400fe0    ldr x0, [sp, #24]
    fd8000005888:   91000400    add x0, x0, #0x1
    fd800000588c:   f9000fe0    str x0, [sp, #24]
}
  • 代码中,fd8000005880: 39000001 strb w1, [x0]确实是往x0寄存器中的地址写入一个字节。这正好与我们对异常原因分析的结果相同。说明异常正是memset函数发生的错误。
  • ISV: 1,   说明23-14位保存着合法指令的异常信息
  • SAS: 00,  说明访问字节数据时产生的错误
  • SSE: 0,   字节访问不要求符号扩展
  • SRT: 00001,错误指令的Wt/Xt/Rt操作数的寄存器编号
  • SF:  0,   指令访问的是32位通用寄存器
  • AR:  0,   指令没有aquire/release语义
  • VNCR:0,   保留
  • LST: 00,  产生abort异常的指令未指定
  • FnV: 0,   FAR寄存器是合法的
  • EA:  0,   表示不是外部abort
  • CM:  0,   表示错误不是由cache维护指令产生的
  • S1PTW: 0, 表示不是stage-2错误
  • WnR: 0,   表示写内存
  • DFSC:000110,L2地址翻译错误

如果memset函数只有一处调用的话,Bug原因结合代码就很容易分析出来了。但是,我们自己编写的hypervisor中有很多处调用memset函数的地方。所以,就文中的bug示例而言,目前还不能分析出原因。所以,我们需要使用qemu模拟器,通过gdb进行单步调试,看看出问题的代码位置(参见下一篇《QEMU+GDB调试ARM程序》)。


Guest OS的abort异常


我们设计的hypervisor支持Guest OS触发的4类异常,具体定义如下:

abort_handler_t abort_handlers[64] =
{
    [ESR_EC_DALEL] = aborts_data_lower,
    [ESR_EC_SMC64] = smc64_handler,
    [ESR_EC_SYSRG] = sysreg_handler,
    [ESR_EC_HVC64] = hvc64_handler
};
  • ESR_EC_HVC64 = 0x16:用于处理Guest OS发起的HVC调用,我们设计使用HVC指令在VM之间建立通信。
  • ESR_EC_SMC64 = 0x17:用于处理Guest OS发起的SMC调用,我们知道ARM规定了PSCI规范,通过将电源管理等代码在ATF代码中实现,这样就实现了资源的安全管理。PSCI规范的底层就是通过SMC指令实现的。hypervisor需要将Guest OS发起的虚拟核的PSCI调用转发给物理核。
  • ESR_EC_SYSRG = 0x18:模拟寄存器和外设。因为Guest OS需要访问一些特殊寄存器和外设,而外设有时候需要多个VM共享,hypervisor对其进行模拟。
  • ESR_EC_DALEL = 0x24:用于处理Guest OS发生的abort异常。比如,Guest OS访问我们未指定的物理内存。

对于ESR_EL2寄存器的分析,与前面的一段一样,不在具体详述。

HPFAR_EL2寄存器保存着出错的IPA地址,通过该地址,我们就可以知道,Guest OS访问哪块内存出错,就能解决某些bug了。

相关文章
|
4月前
|
边缘计算 编译器 数据中心
X86架构与Arm架构的主要区别分析
X86架构与Arm架构的主要区别分析
472 0
|
8月前
|
存储 编译器 内存技术
「Arm Arch」 ISA 寄存器
「Arm Arch」 ISA 寄存器
|
8月前
|
存储 传感器 缓存
「Arm Arch」 调试微架构
「Arm Arch」 调试微架构
|
9月前
|
传感器 资源调度 安全
ARM异常
ARM异常
66 0
|
11月前
|
NoSQL 网络协议 数据可视化
ARM深入理解-hypervisor调试方法二(QEMU+GDB调试ARM程序)
ARM深入理解-hypervisor调试方法二(QEMU+GDB调试ARM程序)
|
11月前
|
人工智能 安全
ARM深入理解-5.3-通往内核的大门(异常向量表_AArch32)
ARM深入理解-5.3-通往内核的大门(异常向量表_AArch32)
|
11月前
|
存储 人工智能
ARM深入理解-5.2-通往内核的大门(异常向量表_AArch64)
ARM深入理解-5.2-通往内核的大门(异常向量表_AArch64)
|
Java
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(二)
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(二)
175 0
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(二)
|
存储
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(一)
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(一)
518 0
【嵌入式开发】ARM 异常向量表 ( 异常概念 | 异常处理流程 | 异常向量 | 汇编代码 )(一)
|
Java
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(二)
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(二)
544 0
【嵌入式开发】ARM 关闭中断 ( CPRS 中断控制位 | 中断使能寄存器 | 中断屏蔽寄存器 | 关闭中断 | 汇编代码编写 )(二)