2023年10月29日 星期日

Reconstruct Call Flow from SIGSEGV

存取不合法位址時會產生 signal SIGSEGV。重建呼叫流程需要的步驟如下:

  1. 程式加 SIGSEGV 的 signal handler 來 dump process 資訊、CPU 暫存器、和 stack。
  2. 編譯時加「-g」選項,並保留 unstripped 的 obj 檔或執行檔備用。
  3. 程式發生 SIGSEGV,取回 dump 資訊。
  4. 分析 dump 資訊找出「程式碼位址」。
  5. 用「addr2line」轉換「程式碼位址」成原始碼檔名和行號,或者無除錯資訊時用「nm」取得函數名稱。

透過 sigaction() 安裝 SIGSEGV 時的呼叫函數「sa_sigaction」,其第三個引數會指到 ucontext_t 格式的資料 (定義在 ucontext.h),其中 mcontext_t 的資訊是處理器相關的,包括 CPU 暫存器等,定義在 sigcontext.h,在 ARM 是:

struct sigcontext {
    unsigned long trap_no;
    unsigned long error_code;
    unsigned long oldmask;
    unsigned long arm_r0;
    unsigned long arm_r1;
    unsigned long arm_r2;
    unsigned long arm_r3;
    unsigned long arm_r4;
    unsigned long arm_r5;
    unsigned long arm_r6;
    unsigned long arm_r7;
    unsigned long arm_r8;
    unsigned long arm_r9;
    unsigned long arm_r10;
    unsigned long arm_fp; // frame pointer only if optimization disabled
    unsigned long arm_ip; // temp workspace
    unsigned long arm_sp; // top of stack,可反向建構出呼叫流程
    unsigned long arm_lr; // link address/workspace,return 位址 (在 caller)
    unsigned long arm_pc; // program counter,SIGSEGV 時的程式位址
    unsigned long arm_cpsr;
    unsigned long fault_address; // fault data address
};

PC 是當機時的 Program Counter,查詢 /proc/[pid]/maps 可以知道落在哪個檔案,如果是執行檔,「addr2line -ife <unstripped執行檔> <PC>」可以查到在原始碼檔名和行號。如果是 relocatable object (so 檔),除非編譯時有下「-g」參數,不然只能知道落在哪個 symbol。「arm-linum-nm -lnDS <so檔>」列出 symbol 的偏移及大小,PC 減去 so 檔載入的位址就是偏移,查表可知是哪個函數。

LR 是呼叫函數的下個指令,照 PC 的方式,可知道在是哪個函數裡面的位置,其前一個呼叫的函數就是 PC 所在的函數。

再來是透過 SP 回朔更多 link addresses,有幾個技巧:

  • ARM 指令不在 thumb 模式都是 32-bit 對齊,所以只需要找 4 倍數的位址。
  • 只會落在 maps 中可執行的檔案才可能是。
  • addr2line 找 LR 所在的函數可能的呼叫者,一直往回推。
  • arm-linux-nm -nS <unstripped執行檔> 可列出 symbol,但不會有 inline 函數,這個時候可以找原始檔 symbol 的位址範圍。
  • PC 和 LR 可能都在函式庫。

可能進階改善

  • 程式解析 log 資料自動重新建立呼叫流程。
  • locate 並回復函數引數和 local 變數。
  • 讓 log 資料 loadable by GDB。

注意:signal 呼叫的「sa_sigaction」函數是執行在 the context of a signal handler,呼叫的函數有限制,請「man 7 signal」查看 section  “Async-signal-safe functions”。

沒有留言:

張貼留言

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...