From 4b5b455dbf9ef55fb305e95d26c2efb59f0ee62c Mon Sep 17 00:00:00 2001 From: zylugl Date: Sat, 25 Jul 2026 22:55:44 +0800 Subject: [PATCH] =?UTF-8?q?vg=5Flite=5Fhal:=20=E4=BF=AE=E5=A4=8D=E7=A9=BA?= =?UTF-8?q?=E9=97=B2=E5=9D=97=E5=90=88=E5=B9=B6=E4=B8=8D=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=9C=B0=E5=9D=80=E7=9B=B8=E9=82=BB=20+=20=E5=88=86=E9=85=8D?= =?UTF-8?q?=E9=81=8D=E5=8E=86=E5=8A=A0=E9=98=B2=E6=AD=BB=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E6=8A=A4=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 Edgi-Talk(PSE84)上启用 LVGL VGLite GPU 渲染(GC555)后,遇到 UI 线程无声卡死(无断言、无 HardFault)。逐层定位到 `vg_lite_hal_allocate_contiguous()` 中堆链表遍历死循环,根本原因是本文件堆管理实现的两处缺陷: ### 缺陷 1:`vg_lite_hal_free_contiguous()` 合并不相邻空闲块 原实现合并前/后节点时只判断 `status == 0`(空闲),**不校验地址是否相邻**,并用 `min()` 调整 offset: ```c if (pos->status == 0) { node->size += pos->size; if (node->offset > pos->offset) node->offset = pos->offset; /* 两个不相邻块被合并成一个"假象块" */ ... } ``` 当相邻链表节点对应的显存块并不连续时,会合并出一个覆盖空洞的"假象空闲块",其 size/offset 与实际不符;该块后续被分配出去即形成 use-after-free,进而写坏链表指针。 ### 缺陷 2:`vg_lite_hal_allocate_contiguous()` 遍历无护栏 链表一旦被破坏(哪怕由其他原因,如越界写),反向遍历会沿损坏的 `prev` 指针无限循环或访问野指针,表现为整个线程卡死且无任何日志。 ## 修复内容 1. **合并前校验地址相邻**:仅当邻居空闲且 `node->offset + node->size == pos->offset`(后邻)/ `pos->offset + pos->size == node->offset`(前邻)时才合并,同时把两个 for 循环简化为单次判断(原循环体末尾无条件 `break`,实际只执行一次)。 2. **分配遍历加护栏**:检测 NULL 指针、自环,并以 256 步为上限,发现链表损坏时打印错误并返回 `VG_LITE_OUT_OF_RESOURCES`,不再挂死调用线程。 ## 验证 - 硬件:KIT_PSE84_EVAL_EPC2,RT-Thread 5.0.2,LVGL 9.2 + VGLite(`LV_USE_DRAW_VG_LITE=1`) - 修复前:首帧渲染即挂死(栈溢出诱发堆链表损坏后,分配遍历死循环) - 修复后:UI 正常渲染(填充、圆角、渐变、文本均走 GPU),长时间运行无异常;故意破坏链表时返回错误而非挂死 --- .../COMPONENT_GFXSS/vsi/gcnano/vg_lite_hal.c | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/libraries/components/mtb-device-support-pse8xxgp/pdl/drivers/third_party/COMPONENT_GFXSS/vsi/gcnano/vg_lite_hal.c b/libraries/components/mtb-device-support-pse8xxgp/pdl/drivers/third_party/COMPONENT_GFXSS/vsi/gcnano/vg_lite_hal.c index 1d36d2649..e3f73b544 100644 --- a/libraries/components/mtb-device-support-pse8xxgp/pdl/drivers/third_party/COMPONENT_GFXSS/vsi/gcnano/vg_lite_hal.c +++ b/libraries/components/mtb-device-support-pse8xxgp/pdl/drivers/third_party/COMPONENT_GFXSS/vsi/gcnano/vg_lite_hal.c @@ -375,33 +375,48 @@ vg_lite_error_t vg_lite_hal_allocate_contiguous(unsigned long size, vg_lite_vidm return VG_LITE_OUT_OF_MEMORY; } - /* Walk the heap backwards. */ - for (pos = (heap_node_t *)device->heap[pool].list.prev; - &pos->list != &device->heap[pool].list; - pos = (heap_node_t*) pos->list.prev) { - /* Check if the current node is free and is big enough. */ - if (pos->status == 0 && pos->size >= aligned_size) { - /* See if we the current node is big enough to split. */ + /* Walk the heap backwards. Guard against corrupted links (use-after-free + * has been observed in this driver): never follow a NULL or self-cycle, + * and bound the walk so a damaged list can never hang the caller. */ + { + unsigned int guard = 0; + const unsigned int guard_max = 256; + + for (pos = (heap_node_t *)device->heap[pool].list.prev; + &pos->list != &device->heap[pool].list; + pos = (heap_node_t*) pos->list.prev) { + + if (++guard > guard_max || pos->list.prev == NULL || + pos->list.prev == (void *)&pos->list) + { + rt_kprintf("[vg] alloc: HEAP LIST CORRUPTED, abort allocation\n"); + return VG_LITE_OUT_OF_RESOURCES; + } + + /* Check if the current node is free and is big enough. */ + if (pos->status == 0 && pos->size >= aligned_size) { + /* See if we the current node is big enough to split. */ if (0 != split_node(pos, aligned_size)) { return VG_LITE_OUT_OF_RESOURCES; } - /* Mark the current node as used. */ - pos->status = 0xABBAF00D; + /* Mark the current node as used. */ + pos->status = 0xABBAF00D; - /* Return the logical/physical address. */ - /* *logical = (uint8_t *) private_data->contiguous_mapped + pos->offset; */ - *logical = (uint8_t *)device->virtual[pool] + pos->offset; - *klogical = *logical; - *physical = gpuMemBase[pool] + (uint32_t)(*logical);/* device->physical + pos->offset; */ + /* Return the logical/physical address. */ + /* *logical = (uint8_t *) private_data->contiguous_mapped + pos->offset; */ + *logical = (uint8_t *)device->virtual[pool] + pos->offset; + *klogical = *logical; + *physical = gpuMemBase[pool] + (uint32_t)(*logical);/* device->physical + pos->offset; */ - /* Mark which pool the pos belong to */ - pos->pool = pool; + /* Mark which pool the pos belong to */ + pos->pool = pool; - device->heap[pool].free -= aligned_size; + device->heap[pool].free -= aligned_size; - *node = pos; - return VG_LITE_SUCCESS; + *node = pos; + return VG_LITE_SUCCESS; + } } } @@ -411,7 +426,6 @@ vg_lite_error_t vg_lite_hal_allocate_contiguous(unsigned long size, vg_lite_vidm void vg_lite_hal_free_contiguous(void *memory_handle) { - /* TODO: no list available in RTOS. */ heap_node_t *pos, *node; vg_lite_vidmem_pool_t pool; @@ -431,45 +445,31 @@ void vg_lite_hal_free_contiguous(void *memory_handle) /* Add node size to free count. */ device->heap[pool].free += node->size; - /* Check if next node is free. */ - pos = node; - for (pos = (heap_node_t *)pos->list.next; - &pos->list != &device->heap[pool].list; - pos = (heap_node_t *)pos->list.next) { - if (pos->status == 0) { - /* Merge the nodes. */ - node->size += pos->size; - if (node->offset > pos->offset) - node->offset = pos->offset; - /* Delete the next node from the list. */ - delete_list(&pos->list); - vg_lite_hal_free(pos); - } - break; + /* Merge with the next node only when it is free AND address-adjacent. + * The original code merged any free neighbor without an adjacency check + * and adjusted offsets with a simple min(), which can coalesce + * non-contiguous blocks and corrupt the list (use-after-free seen in + * later allocations). */ + pos = (heap_node_t *)node->list.next; + if (&pos->list != &device->heap[pool].list && + pos->status == 0 && + node->offset + node->size == pos->offset) + { + node->size += pos->size; + delete_list(&pos->list); + vg_lite_hal_free(pos); } - /* Check if the previous node is free. */ - pos = node; - for (pos = (heap_node_t *)pos->list.prev; - &pos->list != &device->heap[pool].list; - pos = (heap_node_t *)pos->list.prev) { - if (pos->status == 0) { - /* Merge the nodes. */ - pos->size += node->size; - if (pos->offset > node->offset) - pos->offset = node->offset; - /* Delete the current node from the list. */ - delete_list(&node->list); - vg_lite_hal_free(node); - } - break; + /* Merge with the previous node only when it is free AND address-adjacent. */ + pos = (heap_node_t *)node->list.prev; + if (&pos->list != &device->heap[pool].list && + pos->status == 0 && + pos->offset + pos->size == node->offset) + { + pos->size += node->size; + delete_list(&node->list); + vg_lite_hal_free(node); } - - /* when release command buffer node and ts buffer node to exit,release the linked list*/ - /* if(device->heap[pool].list.next == device->heap[pool].list.prev) { - delete_list(&pos->list); - vg_lite_hal_free(pos); - }*/ } void vg_lite_hal_free_os_heap(void)