ARMv8-A硬件观察点深度解析:DBGWVR/DBGWCR寄存器配置与实战应用

发布时间:2026/7/19 8:40:38

ARMv8-A硬件观察点深度解析:DBGWVR/DBGWCR寄存器配置与实战应用 1. ARM调试架构与观察点核心价值在嵌入式系统开发尤其是驱动、内核和实时系统调试中最让人头疼的往往不是代码逻辑错误而是那些“幽灵”般的数据问题——某个全局变量在某个难以复现的时刻被意外修改或者一个共享缓冲区在多线程访问下出现数据错乱。传统的软件断点如GDB的break和打印日志在面对这类问题时要么会严重干扰程序时序要么因为日志量巨大而难以定位。这时硬件观察点Watchpoint就成了我们手中的“手术刀”。硬件观察点的本质是让CPU的调试单元替你“盯梢”特定内存地址。当程序运行到对该地址进行读、写或读写访问时CPU会立即暂停执行触发调试异常并将控制权交给调试器。整个过程由硬件完成对软件执行流的影响微乎其微是定位数据竞争、内存越界、变量篡改等疑难杂症的终极利器。在ARMv8-A架构中这套机制的核心就是一组名为DBGWVRDebug Watchpoint Value Register和DBGWCRDebug Watchpoint Control Register的寄存器。我处理过不少由内存踩踏引发的系统级宕机最终都是靠精确配置观察点才锁定到那行“罪魁祸首”的代码。理解并熟练运用这些寄存器是从“会用调试器”到“精通底层调试”的关键一步。本文将以德州仪器TIAM62L处理器的技术手册为蓝本深入解析DBGWVR/DBGWCR的每一位含义并通过实际场景演示如何配置它们来解决真实问题。无论你是正在开发ARMv8-A平台驱动还是在进行系统级性能剖析这篇文章都能为你提供可直接落地的配置指南和避坑经验。2. 调试寄存器基础从概念到物理寻址在深入位域细节之前我们必须先建立两个关键认知调试寄存器的访问方式和它们在系统中的组织。这直接决定了你能否在代码或调试脚本中正确操作它们。2.1 调试寄存器的访问方式ARMv8-A架构提供了两种主要方式来访问调试寄存器系统寄存器访问和内存映射访问。你提供的技术手册片段其寄存器名称如COMPUTE_CLUSTER_ARM_COREPACK_0_APBADDR_DBG_CPU1_DBGWVR0_EL1_31_0明确指向了后者——内存映射访问。系统寄存器访问MSR/MRS指令这是最直接、在异常级别EL1/EL2/EL3运行的软件如操作系统内核中常用的方式。例如在ARM64汇编中你可以使用MRS X0, DBGWVR0_EL1来读取观察点0的值寄存器到通用寄存器X0中使用MSR DBGWCR0_EL1, X1来将X1的值写入控制寄存器。这种方式速度快但通常需要在内核态或更高特权级下执行。内存映射访问Memory-Mapped I/O这是外部调试器如JTAG/SWD适配器或系统内其他主机如另一个Cortex-A核或一个Cortex-M协处理器访问调试寄存器的标准方式。芯片设计厂商如TI会将调试寄存器组映射到系统总线的一个物理地址区域APB总线即Advanced Peripheral Bus。你提供的地址0007 3011 0800h就是CPU1的DBGWVR0_EL1低32位寄存器在该AM62L芯片上的具体物理位置。关键理解DBGWVR0_EL1这个架构定义的寄存器在芯片内部可以通过两种“窗口”看到和操作。一个是CPU核内部的、通过专用指令访问的“系统寄存器窗口”另一个是映射到总线上的、可以通过加载/存储指令访问的“内存窗口”。它们操作的是同一个硬件实体。调试器通常使用内存映射方式因为它不依赖目标CPU的执行状态即使CPU挂起也能访问。2.2 寄存器实例与地址解码让我们拆解你提供的这个长名称COMPUTE_CLUSTER_ARM_COREPACK_0_APBADDR_DBG_CPU1_DBGWVR0_EL1_31_0。COMPUTE_CLUSTER_ARM_COREPACK_0: 指代芯片内的一个ARM核心簇Cluster。APBADDR_DBG: 表示这是通过APB总线访问的调试Debug模块。CPU1: 指明这是该簇内的第2个CPU通常从CPU0开始编号。DBGWVR0_EL1: 这是ARM架构定义的寄存器名即“调试观察点值寄存器0异常级别1可访问”。_31_0: 表示这个寄存器实例只对应该64位寄存器的低32位。对于DBGWVR这类64位寄存器TI的IP设计可能将其拆分为两个32位的内存映射寄存器_31_0和_63_32以便于32位总线访问。地址偏移规律手册中给出了连续的偏移地址DBGWVR0_EL1[31:0]: Offset 0x800DBGWVR0_EL1[63:32]: Offset 0x804DBGWCR0_EL1: Offset 0x808DBGWVR1_EL1[31:0]: Offset 0x810可以观察到每个完整的观察点寄存器对一个64位DBGWVR和一个32位DBGWCR占用0x1016字节的空间。DBGWVR被拆成两个32位寄存器中间间隔4字节。这是非常典型的内存映射布局在编写底层调试工具或脚本时我们可以根据基地址和这个偏移规律轻松计算出任何观察点寄存器的地址。2.3 可用资源探查ID_AA64DFR0_EL1在配置之前我们得先知道手头有多少“弹药”。ID_AA64DFR0_EL1AArch64调试特性寄存器0提供了这个信息。根据你提供的寄存器描述WRPS(位[23:20]): 值为0x3。这个字段表示“支持的观察点数量减1”。所以WRPS 3意味着该CPU核心支持4个硬件观察点编号0-3。这就是为什么手册中列出了DBGWVR0/1/2/3和DBGWCR0/1/2/3。BRPS(位[15:12]): 值为0x5。表示支持6个硬件断点Breakpoint。CTX_CMP(位[31:28]): 值为0x1。表示有2个断点是上下文感知的Context-aware通常用于基于ASID地址空间标识符或VMID虚拟机标识符的过滤在复杂的虚拟化或进程调试中很有用。了解这些限制至关重要。在资源紧张时例如你需要监视超过4个地址就需要制定策略比如动态替换观察点或者结合软件方法进行辅助。3. DBGWVR寄存器深度解析地址匹配的精确艺术DBGWVRDebug Watchpoint Value Register用于设置我们希望监视的内存地址。但它并不是简单地把一个完整的64位地址放进去就行了其中涉及地址对齐、有效位和ARM的一个特殊设计考量。3.1 地址值VA字段与地址对齐根据手册DBGWVR的VA字段存储的是地址的[48:2]位。这是一个非常关键的设计细节。为什么是[48:2]在ARMv8-A架构中如果实现支持的最大物理地址宽度是48位这是常见的配置那么有效的地址位就是48位。DBGWVR存储的是字对齐的地址。在AArch64状态下一个字Word是64位8字节因此地址的最低3位[2:0]用于字节寻址。观察点通常以字或双字为单位进行地址匹配通过DBGWCR.BAS进一步细化字节所以硬件要求你提供的地址必须是字对齐的即地址值低3位为0。VA字段存储的是右移3位除以8后的地址值或者说它忽略了地址的[2:0]位。如何计算VA值假设我们要监视的变量位于地址0x8000_1234。首先检查它是否8字节对齐0x8000_1234 % 8 4不对齐。我们需要使用DBGWCR中的MASK功能后面会讲或确保地址对齐。如果我们监视从0x8000_1230开始的8字节区域这个地址是8字节对齐的低3位为0。那么写入DBGWVR的VA值就是0x8000_1230 3 0x1000_0246。实际上在编程时我们通常直接写入对齐后的地址值0x8000_1230硬件会自动忽略低3位。3.2 被弃用的位[2]一个重要的兼容性说明册在VA字段描述中特别强调“ARM deprecates setting DBGWVR _EL1[2] 1.”这句话需要仔细理解。这里的[2]指的是DBGWVR寄存器本身的位[2]而不是地址的位[2]。由于VA字段存储的是地址位[48:2]那么DBGWVR寄存器的位[2]对应的是原始地址的位[5]因为地址位[2]对应DBGWVR的位[0]这里需要厘清。实际上更准确的解读是在ARMv8-A中DBGWVR是一个64位寄存器其位[2]有一个历史遗留的、与AArch32状态相关的特殊含义可能与双字地址选择有关。ARM明确表示在AArch64状态下设置该位为1的行为是“被弃用”的意味着未来的架构版本可能不支持或产生不可预期的行为。因此最佳实践是永远确保写入DBGWVR的值其位[2]为0。对于字对齐的地址低3位为0其右移3位后的值位[2]自然就是原始地址的位[5]这通常不会是1除非你监视一个非常特殊的地址区域。但为了安全在配置后读取回来验证一下是好的习惯。3.3 符号扩展位RESS与高位地址在DBGWVR的高32位寄存器_63_32描述中有一个RESS字段位[31:17]描述为“Reserved, Sign extended. Hardwired to the value of the sign bit, bit [48]”。这是为了处理有符号地址扩展。在64位系统中虚拟地址的高位比如位[63:49]应该是位[48]的符号扩展以确保地址 canonical form。当VA[48]即我们关心的最高地址位为0时表示一个用户空间常见的地址高16位全0此时RESS字段硬件强制为0软件应视其为RES0保留为0。当VA[48]为1时表示一个内核空间的高端地址高16位全1此时RESS字段硬件强制为全1软件应视其为RES1保留为1。对于软件编程者来说你通常不需要直接操作这个字段只需要写入完整的64位地址硬件和调试器会自动处理这部分。理解它的存在有助于你解读从寄存器中读回的值。4. DBGWCR寄存器深度解析构建触发条件如果说DBGWVR定义了“在哪里”监视那么DBGWCRDebug Watchpoint Control Register就定义了“在什么情况下”触发。它的每一个位域都是一个精细的过滤器。4.1 使能位E与字节地址选择BAS位[0]E (Enable)。这是总开关。1启用观察点0禁用。在修改其他配置位之前最好先禁用E0观察点配置完成后再启用避免中间状态产生意外的调试事件。位[12:5]BAS (Byte Address Select)。这是实现字节粒度监视的关键。它是一个8位的位图每一位对应DBGWVR所指向的字4字节或双字8字节内存区域中的一个字节。当DBGWVR[2]0即地址按双字对齐且遵循非弃用模式时BAS[7:0]全部有效BAS[0] 1: 监视DBGWVR指向的双字地址 0 字节。BAS[1] 1: 监视DBGWVR指向的双字地址 1 字节。...BAS[7] 1: 监视DBGWVR指向的双字地址 7 字节。当DBGWVR[2]1弃用模式时只使用BAS[3:0]对应一个字的4个字节。有效值规则ARM规定BAS的有效值必须是0b00000000不监视任何字节或者所有被设置为1的位必须是连续的。例如0b00001111监视低4字节、0b11110000监视高4字节、0b00111000监视中间3字节是合法的。而0b10101010间隔置位是非法的软件不能使用行为未定义。这个特性非常强大。例如你有一个32位的整数变量4字节你可以设置BAS0b1111来监视整个变量。如果你有一个结构体你只想监视其中的某个特定字节比如一个bool标志位你可以精确地设置对应的BAS位。4.2 加载/存储控制LSC与观察点类型WT位[4:3]LSC (Load/Store Control)。定义触发访问的类型。0b01: 仅当发生加载读操作时触发。0b10: 仅当发生存储写操作时触发。0b11: 无论是加载还是存储操作都触发。其他值保留行为视同观察点被禁用。位[20]WT (Watchpoint Type)。定义观察点类型。0:非链接数据地址匹配。这是最常用的模式观察点独立工作。1:链接数据地址匹配。此观察点与一个上下文匹配断点Context-matching Breakpoint链接。仅当链接的断点也匹配时观察点才可能触发。这用于实现更复杂的条件断点例如“当进程A执行到函数X时才监视变量Y”。位[19:16]LBN (Linked Breakpoint Number)。当WT1时此字段指定所链接的上下文匹配断点的索引号例如链接到断点0则填入0b0000。链接观察点是一个高级功能。假设你在调试一个多任务系统一个全局变量被多个任务访问。你只想在某个特定任务具有特定的上下文ID/ASID运行时监视该变量。你可以先设置一个上下文匹配断点使用DBGBCR寄存器来匹配该任务的上下文然后设置一个链接观察点指向该变量。这样只有当该任务在运行时访问变量才会触发调试事件避免了其他任务访问带来的干扰。4.3 特权与安全状态过滤PAC, HMC, SSC这三个字段共同决定了观察点在何种处理器状态下触发是实现安全、多权限级别调试的核心。位[2:1]PAC (Privilege of Access Control)。控制观察点触发的异常级别。需要结合HMC位来解释。简单来说它定义了在非安全状态Non-secure state下哪些异常级别EL0用户态, EL1内核态, EL2虚拟机监控态的访问会触发观察点。具体的编码表需要参考ARM架构手册通常0b00表示仅EL00b01表示仅EL10b11表示EL0或EL1等。位[13]HMC (Higher Mode Control)。更高模式控制。这个位决定了调试事件的触发是基于访问发生的模式还是基于调试器所处的模式视角。0:匹配模式。观察点是否触发取决于执行内存访问的代码所处的异常级别和安全状态是否与PAC/SSC设置匹配。这是最常用的模式。1:更高模式。观察点是否触发取决于调试异常的目标异常级别即调试器处理程序运行的级别通常是EL1或更高是否“高于或等于”执行访问的代码的级别。这用于实现诸如“只在用户态访问时触发内核态访问不触发”的效果而不管内核态代码本身是否在PAC允许的列表中。位[15:14]SSC (Security State Control)。控制观察点触发的安全状态。0b00: 仅非安全状态Non-secure下触发。0b01: 仅安全状态Secure下触发。0b11: 安全或非安全状态均可触发。0b10: 保留。配置示例假设你在开发一个安全固件运行在安全态EL1你只想监视非安全世界Normal World的用户程序EL0对某个共享缓冲区的写操作。你可以这样设置SSC0b00仅非安全PAC0b00仅EL0HMC0匹配模式。这样只有当非安全世界的用户态代码进行写操作时才会触发安全世界的访问或非安全世界内核的访问都会被过滤掉。4.4 地址掩码MASK实现地址范围监视位[28:24]MASK。这是观察点最强大的功能之一允许你监视一个连续的地址范围而不仅仅是一个点。它通过掩码来忽略地址中的若干低位从而实现范围匹配。MASK值为0b000000b11111分别对应忽略0到31个低位地址。当MASK N(N从3到31) 时硬件在比较地址时会忽略地址的[N-1:0]位。这意味着观察点将监视从DBGWVR中地址值开始大小为2^N 字节的一个对齐区域。限制手册明确指出“Only objects up to 2GB can be watched using a single mask”。即单个观察点通过掩码能监视的最大范围是2GB2^31字节对应MASK0b11111。工作原理假设DBGWVR 0x8000_0000MASK 0b01100十进制12。这意味着忽略地址的低12位[11:0]。那么任何访问地址在0x8000_0000到0x8000_0FFF4KB范围内的操作都会触发观察点。因为0x8000_0000和0x8000_0FFF在忽略低12位后都与DBGWVR中的值0x8000_0相同。计算示例如果你想监视一个大小为1KB0x400的缓冲区起始地址是0x9000_1234。首先找到大于等于缓冲区大小的最小2的幂次对齐掩码。1KB 1024 2^10。所以需要忽略至少10位地址MASK至少为10。但MASK值是从3开始的位数所以MASK字段值需要设置为10。其次起始地址必须与掩码指定的范围对齐。即起始地址 % (2^MASK) 0。0x9000_1234 % 0x400 0x234不对齐。你有两个选择选择一推荐调整监视的起始地址为向下对齐的0x9000_1000。这样你监视的范围是0x9000_1000到0x9000_13FF完全覆盖了你的缓冲区0x9000_1234到0x9000_1633。只要你的缓冲区完全落在这个4KB区域内就可以。选择二使用更大的掩码例如11对应2KB使起始地址0x9000_1234落在以0x9000_1000或0x9000_0800为起点的2KB区域内。但这会扩大监视范围可能引入误触发。假设我们选择对齐到0x9000_1000MASK10。那么写入DBGWVR的地址是0x9000_1000MASK字段写入0b01010十进制10。重要陷阱MASK字段的值0b00001和0b00010是保留的不能使用。这意味着你不能创建大小为2字节或4字节的掩码区域。最小的掩码区域是8字节MASK3。5. 实战配置从理论到代码理解了所有位域后我们来看一个完整的配置示例。假设在AM62L的Linux内核驱动开发中我们需要监视一个位于非安全态内核空间EL1的全局数组critical_buffer假设地址为0xFFFFFFC0_12345670大小为16字节的写操作。5.1 步骤一规划与计算目标监视地址0xFFFFFFC0_12345670开始的16字节区域的写操作。对齐检查16字节 2^4 字节。需要的MASK值为4。但MASK从3开始4对应忽略4位地址不对MASK值N表示忽略N个低位范围大小是2^N字节。要覆盖16字节需要2^N 16N4。我们取N4范围16字节。检查地址对齐0xFFFFFFC0_12345670 % 16 0。很好地址是16字节对齐的。BAS设置由于我们监视整个16字节区域且地址双字对齐低3位为0我们使用两个观察点来覆盖或者使用一个观察点加掩码。16字节正好是掩码N4的范围。我们使用一个观察点。BAS需要匹配这个16字节区域内的所有字节。由于掩码会忽略低4位BAS实际上用于选择在掩码后的“块”内的字节。当使用掩码时BAS通常设置为0b11111111监视掩码区域内所有可能的字节位置或者根据需求选择。为了监视所有写操作我们设BAS0xFF。LSC0b10仅存储/写操作。PAC/HMC/SSC非安全态EL1。假设我们使用匹配模式。SSC0b00非安全PAC需要设置为匹配EL1根据ARM手册可能是0b01HMC0。WT/LBN0非链接。E最后设置为1。5.2 步骤二编写配置函数伪代码/C语言以下是一个在Linux内核模块中通过内存映射I/O方式配置观察点的示例框架。请注意实际中更可能通过内核的调试基础设施或直接使用msr/mrs指令。#include linux/io.h void configure_watchpoint(void __iomem *debug_regs_base, int wp_idx, u64 addr, u8 mask, u8 bas, u8 lsc, u8 pac, u8 hmc, u8 ssc) { void __iomem *wvr_low, *wvr_high, *wcr; u32 reg_val; // 计算特定观察点寄存器的偏移 wvr_low debug_regs_base 0x800 (wp_idx * 0x10); wvr_high wvr_low 0x4; wcr wvr_low 0x8; // 1. 先禁用观察点 reg_val readl(wcr); reg_val ~(1 0); // 清除E位 writel(reg_val, wcr); // 2. 配置DBGWVR (64位地址确保位[2]为0) // 写入低32位 (VA[31:2]) writel((u32)(addr 2) ~0x3, wvr_low); // 右移2位得到VA[31:2]并确保写入的位[1:0]为0RES0 // 写入高32位 (VA[63:32])硬件处理符号扩展 writel((u32)(addr 34), wvr_high); // 注意这里简化了实际需根据VA[48]处理 // 3. 配置DBGWCR reg_val 0; reg_val | (mask 0x1F) 24; // MASK[28:24] // WT, LBN 默认为0 (非链接) reg_val | (ssc 0x3) 14; // SSC[15:14] reg_val | (hmc 0x1) 13; // HMC[13] reg_val | (bas 0xFF) 5; // BAS[12:5] reg_val | (lsc 0x3) 3; // LSC[4:3] reg_val | (pac 0x3) 1; // PAC[2:1] reg_val | 1 0; // E[0] 1 启用 writel(reg_val, wcr); pr_info(Watchpoint %d configured at addr 0x%llx, mask0x%x, BAS0x%x\n, wp_idx, addr, mask, bas); } // 调用示例 void setup_critical_buffer_watchpoint(void __iomem *dbg_base) { u64 addr 0xFFFFFFC012345670ULL; configure_watchpoint(dbg_base, 0, // 使用观察点0 addr, 4, // MASK4 (16字节范围) 0xFF, // BAS所有字节 0b10, // LSC仅存储 0b01, // PACEL1 (示例值需查手册) 0, // HMC匹配模式 0b00); // SSC非安全 }5.3 步骤三在调试器中使用GDB示例在用户空间调试中使用GDB更为常见。GDB的watch命令底层就是通过配置这些硬件观察点寄存器实现的。# 连接到目标板例如通过JTAG (gdb) target remote :3333 # 在地址0x12345678设置写观察点 (gdb) watch *(int *)0x12345678 Hardware watchpoint 1: *(int *)0x12345678 # 设置读观察点 (gdb) rwatch *(int *)0x12345678 # 设置读写观察点 (gdb) awatch *(int *)0x12345678 # 继续运行当访问发生时程序会暂停 (gdb) continue Continuing. Hardware watchpoint 1: *(int *)0x12345678 Old value 0 New value 42 0xffffffc010abcdef in some_function () at driver.c:100 # 查看当前所有观察点信息 (gdb) info watchpointsGDB会自动处理地址对齐、BAS、MASK等细节。但了解底层原理能帮助你在GDB命令失败例如“Cannot insert hardware breakpoint/watchpoint”时诊断问题比如可能是因为观察点数量用尽、地址不对齐、或者范围太大。6. 高级技巧与常见问题排查6.1 资源竞争与优化策略硬件观察点数量有限通常4个。在复杂调试场景中需要策略动态管理在调试脚本中根据代码执行阶段动态启用/禁用或重新配置观察点。范围与精度的权衡使用MASK监视一个范围而不是为多个相邻变量设置多个观察点。但要注意范围过大会增加误触发。结合软件断点在可能修改目标变量的函数入口设置软件断点当断点命中后再临时启用硬件观察点。这相当于“条件观察点”。利用链接断点通过WT和LBN将观察与断点链接实现“仅在特定上下文如某个函数内中监视变量”。6.2 观察点不触发的排查清单寄存器是否成功配置通过调试器读取DBGWVR和DBGWCR寄存器确认写入的值是否正确。特别是E位是否为1。地址对齐和MASK计算是否正确确认(地址 % (2^MASK)) 0。使用MASK0精确地址进行测试。访问类型匹配吗确认LSC设置与你预期的操作读、写、读写一致。用LSC0b11读写测试。权限/状态过滤是否太严格检查PAC、SSC、HMC设置。尝试将SSC设为0b11全状态PAC设为更宽泛的值HMC0排除权限问题。目标地址是否真的被访问确认你的代码执行路径确实访问了该内存地址。可能由于编译器优化变量被存储在寄存器中而未发生内存访问。观察点资源是否耗尽检查ID_AA64DFR0_EL1.WRPS确认支持的数量并检查是否其他观察点已被占用。调试功能全局启用了吗在ARM架构中调试可能被更高异常级别EL2, EL3或安全配置全局禁用。需要检查MDSCR_EL1Monitor Debug System Control Register等相关全局调试控制寄存器。6.3 性能影响考量硬件观察点由CPU的调试单元执行其性能开销远低于软件断点但并非为零。当观察点触发时会产生调试异常需要进行上下文保存和调试异常处理。如果观察点设置在一条被频繁访问例如在热循环中的内存地址上会导致程序频繁中断显著影响性能。在性能剖析时需谨慎使用或者结合采样式分析工具。6.4 多核环境下的注意事项在AM62L这样的多核系统中每个CPU核心都有自己独立的一套调试寄存器CPU0,CPU1等。这意味着你在CPU0上设置的观察点不会影响CPU1。如果你需要监视一个被多个核共享的变量必须在每个核心上都配置相同的观察点。这可以通过在SMP系统的IPI处理器间中断处理程序中或在内核调试框架中向所有在线CPU下发配置来实现。7. 超越基本观察点与调试框架的集成在实际的嵌入式Linux开发中我们很少直接裸操作这些寄存器。内核提供了如KGDB、KDB、Ftrace、Perf等强大的调试和追踪工具。perf工具中的perf mem和perf c2c子命令可以用于分析内存访问模式其底层可能利用性能监控单元PMU而非调试观察点但解决的问题域有重叠。了解DBGWVR/DBGWCR的真正价值在于构建自定义调试工具当你需要在内核启动早期、在KGDB可用之前进行调试时直接配置这些寄存器是唯一的方法。深度问题诊断当标准工具无法解释某些诡异的内存损坏时直接使用硬件观察点是最后的“杀手锏”。理解上层工具的局限明白GDB的watch命令可能失败的原因并知道如何通过调整命令例如观察指针指向的内容而非指针本身或直接检查硬件资源来解决问题。最后一个经常被忽视的细节是确保你的观察点配置在内存屏障之后生效。因为对调试寄存器的写入可能被CPU的写缓冲延迟在写入后立即执行一条DSB SY指令可以确保配置在下一条指令前被系统真正识别。在调试极其敏感的时间相关问题时这个步骤至关重要。

相关新闻