STM32 VCP驱动——只有优化后指针才会失效
STM32 VCP driver - pointer becomes invalid only with optimization
我正在使用 STM32F405 微控制器开发一个嵌入式项目,并且有一些非常令人困惑的行为。我正在将一个现有的(工作中的)项目从 STM32F1 移植到 STM32F4,但我已经为 VCP 添加了 ST 的标准外设库 USB 堆栈。
如果我用 -O0
优化编译程序,那么它会 运行 无限期地按预期运行。但是,如果我使用 -O2
进行编译,那么该项目将 运行 持续 10-15 分钟,但随后我会得到在 ST 的 VCP 驱动程序代码中发生堆栈溢出的情况。
实际错误表现为指针 (GREGS
) 变得无效,即使该指针之前已在同一函数中使用过。这个指针指向 USB 外围设备的硬件中断配置寄存器,所以实际数据并没有消失,但是当访问指针时,我得到一个错误,我可以用我的调试器看到指针无效。 (我从下面的 usb_dcd_int.c
中复制了实际函数,并指出了麻烦的行。)
static uint32_t DCD_HandleRxStatusQueueLevel_ISR(USB_OTG_CORE_HANDLE *pdev)
{
USB_OTG_GINTMSK_TypeDef int_mask;
USB_OTG_DRXSTS_TypeDef status;
USB_OTG_EP *ep;
/* Disable the Rx Status Queue Level interrupt */
int_mask.d32 = 0;
int_mask.b.rxstsqlvl = 1;
/*****************************************************************/
/*********** POINTER IS READ HERE - NO PROBLEMS ******************/
/*****************************************************************/
USB_OTG_MODIFY_REG32( &pdev->regs.GREGS->GINTMSK, int_mask.d32, 0);
/* Get the Status from the top of the FIFO */
status.d32 = USB_OTG_READ_REG32( &pdev->regs.GREGS->GRXSTSP );
ep = &pdev->dev.out_ep[status.b.epnum];
switch (status.b.pktsts)
{
case STS_GOUT_NAK:
break;
case STS_DATA_UPDT:
if (status.b.bcnt)
{
USB_OTG_ReadPacket(pdev,ep->xfer_buff, status.b.bcnt);
ep->xfer_buff += status.b.bcnt;
ep->xfer_count += status.b.bcnt;
}
break;
case STS_XFER_COMP:
break;
case STS_SETUP_COMP:
break;
case STS_SETUP_UPDT:
/* Copy the setup packet received in FIFO into the setup buffer in RAM */
USB_OTG_ReadPacket(pdev , pdev->dev.setup_packet, 8);
ep->xfer_count += status.b.bcnt;
break;
default:
break;
}
/* Enable the Rx Status Queue Level interrupt */
/*****************************************************************/
/************************* GREGS == :-( ************************/
/*****************************************************************/
USB_OTG_MODIFY_REG32( &pdev->regs.GREGS->GINTMSK, 0, int_mask.d32);
return 1;
}
我使用 vanilla GNU make
和 gcc-arm-none-eabi 5-4-2016q3
作为我的工具箱,ST 的 vanilla 链接器和 2015 年用于 STM32F405 的启动脚本,VCP 代码来自 2012 年 3 月。我是启动和链接器脚本的新手,但我看不出任何一个有任何可疑之处。我也没有在 ST 的 VCP 代码中看到任何明显的东西,但我肯定不理解每一行。
我有三个问题:
- 这听起来像是堆栈溢出吗?
- IRQ 的堆栈是如何分配的?在 ST 的实现中,VCP 中断有一个非常深的调用树。我是否只需要为 VCP IRQ 分配更多?
-O2
中的哪些优化可能导致此行为?我想知道我是否可以有选择地禁用一些可能帮助我追踪错误的优化。
- 如果没有优化和有优化存在差异,那么这听起来与优化+易失性问题有关。好的想法是理解 volatile 类型限定符以及它与 C 优化的关系。网上有很多好的文章。
- 堆栈只是存在,您不能 "allocate" 更多堆栈(至少在某种意义上在嵌入式系统上是这样)。该函数可以"allocate"入栈,即使用栈来存储局部变量、寄存器状态和移动栈指针。当 IRQ 发生时,当前执行状态被保存在栈顶,然后 IRQ 处理函数被执行。您可以通过在访问堆栈末尾的内存地址时设置断点来检测是否发生堆栈溢出。在 STM32 上,当您将某些内容放入堆栈时,堆栈指针会减少。您可以使用
-fstack-usage
检查函数的堆栈使用情况。但这与问题无关。使用更好的优化进行编译会创建堆栈使用量更小的代码。
- 我猜all/any.
现在我不知道你所说的 The actual bug manifests as a pointer (GREGS) getting dereferenced even though the pointer had been used earlier in the same function.
是什么意思。程序员的意图是取消引用 GINTMSK 两次。 GINTMSK 指针被声明为 volatile,每次使用时都会取消引用并且不会优化掉。这也是意图,因为 GINTMSK 是一个硬件映射寄存器变量。
根据您的描述,pdev->regs.GREG
的值似乎在该开关之间的某处进行了修改。
USB_OTG_ReadPacket() 看起来相当简单,但也许缓冲区指向错误的位置并且覆盖了 pdev 结构?
也许在此中断期间,其他具有不同优先级的中断会启动并修改 pdev
结构。尝试添加 __disable_irq()
和 __enable_irq()
守卫。
如果你正在移植这个项目,你可以考虑转移到 STM32 HAL 库并使用 STM32CubeMX 程序生成一些代码。 STM32 库在每个版本中都变得更好,一些最古老的库在各种优化方面存在问题。
我正在使用 STM32F405 微控制器开发一个嵌入式项目,并且有一些非常令人困惑的行为。我正在将一个现有的(工作中的)项目从 STM32F1 移植到 STM32F4,但我已经为 VCP 添加了 ST 的标准外设库 USB 堆栈。
如果我用 -O0
优化编译程序,那么它会 运行 无限期地按预期运行。但是,如果我使用 -O2
进行编译,那么该项目将 运行 持续 10-15 分钟,但随后我会得到在 ST 的 VCP 驱动程序代码中发生堆栈溢出的情况。
实际错误表现为指针 (GREGS
) 变得无效,即使该指针之前已在同一函数中使用过。这个指针指向 USB 外围设备的硬件中断配置寄存器,所以实际数据并没有消失,但是当访问指针时,我得到一个错误,我可以用我的调试器看到指针无效。 (我从下面的 usb_dcd_int.c
中复制了实际函数,并指出了麻烦的行。)
static uint32_t DCD_HandleRxStatusQueueLevel_ISR(USB_OTG_CORE_HANDLE *pdev)
{
USB_OTG_GINTMSK_TypeDef int_mask;
USB_OTG_DRXSTS_TypeDef status;
USB_OTG_EP *ep;
/* Disable the Rx Status Queue Level interrupt */
int_mask.d32 = 0;
int_mask.b.rxstsqlvl = 1;
/*****************************************************************/
/*********** POINTER IS READ HERE - NO PROBLEMS ******************/
/*****************************************************************/
USB_OTG_MODIFY_REG32( &pdev->regs.GREGS->GINTMSK, int_mask.d32, 0);
/* Get the Status from the top of the FIFO */
status.d32 = USB_OTG_READ_REG32( &pdev->regs.GREGS->GRXSTSP );
ep = &pdev->dev.out_ep[status.b.epnum];
switch (status.b.pktsts)
{
case STS_GOUT_NAK:
break;
case STS_DATA_UPDT:
if (status.b.bcnt)
{
USB_OTG_ReadPacket(pdev,ep->xfer_buff, status.b.bcnt);
ep->xfer_buff += status.b.bcnt;
ep->xfer_count += status.b.bcnt;
}
break;
case STS_XFER_COMP:
break;
case STS_SETUP_COMP:
break;
case STS_SETUP_UPDT:
/* Copy the setup packet received in FIFO into the setup buffer in RAM */
USB_OTG_ReadPacket(pdev , pdev->dev.setup_packet, 8);
ep->xfer_count += status.b.bcnt;
break;
default:
break;
}
/* Enable the Rx Status Queue Level interrupt */
/*****************************************************************/
/************************* GREGS == :-( ************************/
/*****************************************************************/
USB_OTG_MODIFY_REG32( &pdev->regs.GREGS->GINTMSK, 0, int_mask.d32);
return 1;
}
我使用 vanilla GNU make
和 gcc-arm-none-eabi 5-4-2016q3
作为我的工具箱,ST 的 vanilla 链接器和 2015 年用于 STM32F405 的启动脚本,VCP 代码来自 2012 年 3 月。我是启动和链接器脚本的新手,但我看不出任何一个有任何可疑之处。我也没有在 ST 的 VCP 代码中看到任何明显的东西,但我肯定不理解每一行。
我有三个问题:
- 这听起来像是堆栈溢出吗?
- IRQ 的堆栈是如何分配的?在 ST 的实现中,VCP 中断有一个非常深的调用树。我是否只需要为 VCP IRQ 分配更多?
-O2
中的哪些优化可能导致此行为?我想知道我是否可以有选择地禁用一些可能帮助我追踪错误的优化。
- 如果没有优化和有优化存在差异,那么这听起来与优化+易失性问题有关。好的想法是理解 volatile 类型限定符以及它与 C 优化的关系。网上有很多好的文章。
- 堆栈只是存在,您不能 "allocate" 更多堆栈(至少在某种意义上在嵌入式系统上是这样)。该函数可以"allocate"入栈,即使用栈来存储局部变量、寄存器状态和移动栈指针。当 IRQ 发生时,当前执行状态被保存在栈顶,然后 IRQ 处理函数被执行。您可以通过在访问堆栈末尾的内存地址时设置断点来检测是否发生堆栈溢出。在 STM32 上,当您将某些内容放入堆栈时,堆栈指针会减少。您可以使用
-fstack-usage
检查函数的堆栈使用情况。但这与问题无关。使用更好的优化进行编译会创建堆栈使用量更小的代码。 - 我猜all/any.
现在我不知道你所说的 The actual bug manifests as a pointer (GREGS) getting dereferenced even though the pointer had been used earlier in the same function.
是什么意思。程序员的意图是取消引用 GINTMSK 两次。 GINTMSK 指针被声明为 volatile,每次使用时都会取消引用并且不会优化掉。这也是意图,因为 GINTMSK 是一个硬件映射寄存器变量。
根据您的描述,pdev->regs.GREG
的值似乎在该开关之间的某处进行了修改。
USB_OTG_ReadPacket() 看起来相当简单,但也许缓冲区指向错误的位置并且覆盖了 pdev 结构?
也许在此中断期间,其他具有不同优先级的中断会启动并修改 pdev
结构。尝试添加 __disable_irq()
和 __enable_irq()
守卫。
如果你正在移植这个项目,你可以考虑转移到 STM32 HAL 库并使用 STM32CubeMX 程序生成一些代码。 STM32 库在每个版本中都变得更好,一些最古老的库在各种优化方面存在问题。