首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

深入 GDB 追蹤 C 語言變長數組(VLA)實現機制

     編輯:關於C語言

問題背景:VLA 越界導致首地址歸零

專案中發現一個函數使用變長數組(VLA),對超出範圍的索引賦值後,數組首地址變為 NULL。簡化後的複現代碼如下:

#include <stdio.h>

int test(int n) {
    char *arg[n + 4];
    printf("before:arg = %p\n", &arg[0]);
    arg[16] = NULL;  // 越界寫入
    printf("after:arg = %p\n", &arg[0]);
    return 0;
}

int main() {
    test(2);
    return 0;
}

在 i386 平台執行,arg[16] = NULL 後再印出 &arg[0] 顯示為 NULL。

GDB 指令級追蹤分析

使用 GDB 設中斷並單步執行(si),觀察 $pc 對應的組合語言:

(gdb) b test
(gdb) display /i $pc
(gdb) run
(gdb) si  // 重複單步

關鍵組合語言片段(objdump -d 反組譯對照):

804835b: 89 e0       mov %esp,%eax
804835d: 89 c3       mov %eax,%ebx
804835f: 8b 45 08    mov 0x8(%ebp),%eax
8048362: 83 c0 04    add $0x4,%eax
8048365: c1 e0 02    shl $0x2,%eax
8048368: 83 c0 0f    add $0xf,%eax
804836b: 83 c0 0f    add $0xf,%eax
804836e: c1 e8 04    shr $0x4,%eax
8048371: c1 e0 04    shl $0x4,%eax
8048374: 29 c4       sub %eax,%esp
8048376: 8d 44 24 08 lea 0x8(%esp),%eax
804837a: 89 45 e8    mov %eax,0xffffffe8(%ebp)
...

逐條指令語義解析

  1. 保存棧指針:mov %esp,%eax; mov %eax,%ebx — 將當前 esp 存入 ebx,函數結束時恢復用。
  2. 計算元素數量:mov 0x8(%ebp),%eax; add $0x4,%eax — 取參數 n,加 4 得到 n+4。
  3. 計算位元組大小並 16 位元組對齊:
    • shl $0x2,%eax — 左移 2 位(×4,sizeof(char*))
    • 兩次 add $0xf + shr $0x4 + shl $0x4 — 標準 16-byte 對齊演算法:(size + 15) & ~15
  4. 調整棧幀分配空間:sub %eax,%esp — 向下擴展棧,預留 VLA 空間。
  5. 計算數組基址並存入區域變量槽:lea 0x8(%esp),%eax 等 — 將對齊後的基址存入 ebp-0x18、ebp-0x8 等槽位,後續 &arg[0] 實際讀取的是這些槽位的值。

根因總結

VLA 在棧上動態分配,基址計算過程中會將對齊後的地址暫存至棧上臨時槽位。arg[16] = NULL 越界寫入剛好覆蓋了存放基址的臨時槽位(ebp-0x8),導致後續讀取 &arg[0] 時取到被覆蓋的 NULL。這說明 VLA 的元数据(基址指標)緊鄰數組本體,極易被越界寫入破壞。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved