轉: ltrace 是如何工作的(2016)

skyycj發表於2024-03-29

http://arthurchiao.art/blog/how-does-ltrace-work-zh/

strace 是一個系統呼叫,也是一個訊號跟蹤器(signal tracer),

  • 主要用於跟蹤系統呼叫,列印系統呼叫的引數、返回值、時間戳等很多資訊。
  • 也可以跟蹤和列印程序收到的訊號。
  • 在前一篇文章strace 是如何工作的 中介紹過, strace 內部基於 ptrace 系統呼叫。

ltrace 是一個函式庫呼叫跟蹤器(library call tracer)

  • 顧名思義,主要用於跟蹤程式的函式庫呼叫資訊。
  • 它也可以像 strace 一樣跟蹤系統呼叫和訊號。
  • 它的命令列引數和 strace 很相似。
  • ltrace 也是基於 ptrace。

雖然 straceltrace 底層都是基於 ptrace 系統呼叫, 但跟蹤庫函式和跟蹤系統呼叫還是有很大差別的,這就是為什麼會有 ltrace 的原因。

#重要概念

共享庫可以被載入到任意地址。這意味著,共享庫內的函式地址只有在執行時載入以後才能確定。 即使重複執行同一程式,載入同一動態庫,庫內的函式地址也是不同的。那麼,程式是如何呼叫地址未知的函式的呢?

簡短版的回答是:二進位制格式、作業系統,以及載入器。在 Linux 上,這是一 支程式和動態載入器之間的曼妙舞蹈

下面是詳細版的回答。

Linux 程式使用 ELF binary format,它提供了 許多特性。出於本文目的,我們這裡只介紹兩個:

  • 過程連結表(Procedure Linkage Table,PLT
  • 全域性偏移表(Global Offset Table,GOT

庫函式在 PLT 裡都有一組對應的彙編指令,通常稱作 trampoline,在函式被呼叫的時候執行。

PLT trampoline 程式碼

PLT trampoline 都遵循類似的格式,下面是一個例子:

PLT1: jmp *name1@GOTPCREL(%rip)
      pushq $index1
      jmp .PLT0
  • 第一行程式碼跳轉到一個地址,這個地址的值儲存在 GOT 中。
  • GOT 儲存了絕對地址。這些地址在程式啟動時初始化,指向 PLT pushq 指令所在的地址(第二行程式碼)。
  • 第三行 pushq $index1 為動態聯結器準備一些資料,然後透過 jmp .PLT0 跳轉到另一段程式碼,後者會進而呼叫動態連結器。

動態連結器透過 $index1 和其他一些資料來判斷程式想呼叫的是哪個庫函式,然後定位 到函式地址,並將其寫入 GOT,覆蓋之前初始化時的預設值。

當後面再次呼叫到這個函式時,就會直接找到函式地址,而不需再經過以上的動態連結器查 找過程。

流程總結

總結起來:

  1. 程式載入到記憶體時,程式和每個動態共享庫(例如 DSO)透過 PLT 和 GOT 對映到記憶體
  2. 程式開始執行時,動態共享庫裡的函式的記憶體地址是未知的,因為動態庫可以被載入到程式地址空間的任意地址
  3. 首次執行到一個函式的時候,執行過程轉到函式的 PLT,裡面是一些彙編程式碼(trampoline)
  4. trampoline 組織資料,然後呼叫動態連結器
  5. 動態連結器透過 PLT 準備的資料找到函式地址
  6. 將地址寫入 GOT 表,然後執行轉到該函式
  7. 後面再次呼叫到這個函式時,不再經過動態連結器,因為 GOT 裡已經儲存了函式地址,PLT 可以直接呼叫

相關文章