iOS獲取任意線程調(diào)用棧

最近在寫(xiě)一些東西需要獲取任意線程調(diào)用棧,然后看了現(xiàn)有的一些開(kāi)源框架,寫(xiě)的比較復(fù)雜而且對(duì)Swift的支持不是很好,所以寫(xiě)了RCBacktrace

ARM幾種通用寄存器

ARM有15種通用寄存器蒲列,但是其實(shí)有些通用寄存器是有特殊用途的,PCS(Procedure Call Standard for Arm architecture)就定義了過(guò)程調(diào)用中搀罢,寄存器的特殊用途蝗岖。

r15:PC The Program Counter,也稱作程序計(jì)數(shù)器PC榔至,指令寄存器保存的是下一條將要執(zhí)行的指令的內(nèi)存地址抵赢。
r14:LR The Link Register,也稱作子程序連接寄存器(Subroutine Link Register)即連接寄存器LR唧取,LR寄存器則保存著最后一次函數(shù)調(diào)用指令的下一條指令的內(nèi)存地址铅鲤,即保存了返回地址。
r13:SP The Stack Pointer枫弟,堆棧指針邢享,sp寄存器在任意時(shí)刻會(huì)保存我們棧頂?shù)牡刂贰?br> r12:IP The Intra-Procedure-call scratch register,可簡(jiǎn)單的認(rèn)為暫存SP淡诗。

實(shí)際上骇塘,還有一個(gè)r11是optional的,被稱為FP韩容,即frame pointer款违,某些時(shí)刻我們利用它保存棧底的地址。在arm64中LR是x30寄存器群凶,F(xiàn)P是x29寄存器插爹。

ARM的棧幀

每個(gè)線程都有自己的棧空間座掘,線程中會(huì)有很多函數(shù)調(diào)用递惋,每個(gè)函數(shù)調(diào)用都有自己的stack frame棧幀柔滔,棧就是由一個(gè)一個(gè)棧幀組成溢陪。

下面這個(gè)是ARM的棧幀布局圖:


130320150468341.png

main stack frame為調(diào)用函數(shù)的棧幀萍虽,func1 stack frame為當(dāng)前函數(shù)(被調(diào)用者)的棧幀,棧底在高地址形真,棧向下增長(zhǎng)杉编。圖中FP就是棧基址咆霜,它指向函數(shù)的棧幀起始地址邓馒;SP則是函數(shù)的棧指針,它指向棧頂?shù)奈恢枚昱鳌RM壓棧的順序很是規(guī)矩光酣,依次為當(dāng)前函數(shù)指針PC、返回指針LR脉课、棧指針SP救军、棧基址FP倘零、傳入?yún)?shù)個(gè)數(shù)及指針唱遭、本地變量和臨時(shí)變量。如果函數(shù)準(zhǔn)備調(diào)用另一個(gè)函數(shù)呈驶,跳轉(zhuǎn)之前臨時(shí)變量區(qū)先要保存另一個(gè)函數(shù)的參數(shù)拷泽。

backtrace

從上圖我們可以看到當(dāng)前棧幀中FP的值存儲(chǔ)的是上一個(gè)棧幀的FP地址。拿到本函數(shù)的FP寄存器袖瞻,所指示的棧地址司致,出棧,就能得到調(diào)用函數(shù)的LR寄存器的值聋迎,然后就能通過(guò)dynsym動(dòng)態(tài)鏈接表脂矫,找到對(duì)應(yīng)的函數(shù)名。

void **currentFramePointer = (void **)machineContext.__ss.__framePointer;
while (i < maxSymbols) {
    void **previousFramePointer = *currentFramePointer;
    if (!previousFramePointer) break;
    stack[i] = *(currentFramePointer+1);
    currentFramePointer = previousFramePointer;
    ++i;
}

線程執(zhí)行狀態(tài)

上面我們可以看到拿到某個(gè)線程的LR和FP寄存器就能進(jìn)行backtrace砌庄,那怎么拿到呢?

Thread是對(duì)pthread的封裝羹唠,在Foundation/Thread.swift,可以看到用pthread封裝Thread的詳細(xì)代碼娄昆。
不同的操作會(huì)設(shè)計(jì)自己的線程模型, 所以底層 API 是不相同的, 但是 POSIX提供的pthread就是相當(dāng)于對(duì)底層進(jìn)行了一次封裝, 讓不同平臺(tái)運(yùn)行得到相同的效果.

Unix 系統(tǒng)提供的 thread_get_state 和 task_threads 等方法佩微,操作的都是內(nèi)核線程,每個(gè)內(nèi)核線程由 thread_t 類型的 id 來(lái)唯一標(biāo)識(shí)萌焰,pthread 的唯一標(biāo)識(shí)是 pthread_t 類型哺眯。

內(nèi)核線程和 pthread 的轉(zhuǎn)換(也即是 thread_t 和 pthread_t 互轉(zhuǎn))很容易,因?yàn)?pthread 誕生的目的就是為了抽象內(nèi)核線程扒俯。

_STRUCT_MCONTEXT 類型的結(jié)構(gòu)體中奶卓,存儲(chǔ)了當(dāng)前線程的SP和最頂部棧幀的FP一疯,_STRUCT_MCONTEXT在不同平臺(tái)上的結(jié)構(gòu)不同,如:

ARM64夺姑,如iPhone 5s

_STRUCT_MCONTEXT64
{
    _STRUCT_ARM_EXCEPTION_STATE64   __es;
    _STRUCT_ARM_THREAD_STATE64  __ss;
    _STRUCT_ARM_NEON_STATE64    __ns;
};

_STRUCT_ARM_THREAD_STATE64
{
    __uint64_t    __x[29];  /* General purpose registers x0-x28 */
    __uint64_t    __fp;     /* Frame pointer x29 */
    __uint64_t    __lr;     /* Link register x30 */
    __uint64_t    __sp;     /* Stack pointer x31 */
    __uint64_t    __pc;     /* Program counter */
    __uint32_t    __cpsr;   /* Current program status register */
    __uint32_t    __pad;    /* Same size for 32-bit or 64-bit clients */
};

有了thread_t和_STRUCT_MCONTEXT就可以通過(guò)thread_get_state獲得線程的FP和SP等墩邀。

_STRUCT_MCONTEXT machineContext;
mach_msg_type_number_t stateCount = THREAD_STATE_COUNT;
    
kern_return_t kret = thread_get_state(thread, THREAD_STATE_FLAVOR, (thread_state_t)&(machineContext.__ss), &stateCount);

dladdr獲取某個(gè)地址的符號(hào)信息

接著就可以通過(guò)dladdr函數(shù)和Dl_info獲得某個(gè)地址的符號(hào)信息

extern int dladdr(const void *, Dl_info *);

/*
 * Structure filled in by dladdr().
 */
public struct dl_info {

    public var dli_fname: UnsafePointer<Int8>! /* Pathname of shared object */

    public var dli_fbase: UnsafeMutableRawPointer! /* Base address of shared object */

    public var dli_sname: UnsafePointer<Int8>! /* Name of nearest symbol */

    public var dli_saddr: UnsafeMutableRawPointer! /* Address of nearest symbol */

    public init()

    public init(dli_fname: UnsafePointer<Int8>!, dli_fbase: UnsafeMutableRawPointer!, dli_sname: UnsafePointer<Int8>!, dli_saddr: UnsafeMutableRawPointer!)
}

Swift命名重整

OC方法沒(méi)有問(wèn)題,因?yàn)橹卣?guī)則比較簡(jiǎn)單盏浙,就是符號(hào)前加了一個(gè)'_'眉睹,但是Swift的命名重整比較復(fù)雜,所以方法經(jīng)過(guò)命名重整很難辨認(rèn)废膘,如下:

$s15RCBacktraceDemo14ViewControllerC3baryyF

所以我們需要調(diào)用swift_demangle對(duì)重整過(guò)的符號(hào)進(jìn)行還原竹海,所以還原成原本的樣子后如下:

RCBacktraceDemo.ViewController.bar() -> ()

更詳細(xì)的Swift的命名重整可以看Friday Q&A 2014-08-08: Swift Name Mangling

參考文章

ARM FP寄存器及frame pointer介紹
iOS中線程Call Stack的捕獲和解析(一)
ARM函數(shù)調(diào)用過(guò)程分析
Friday Q&A 2014-08-08: Swift Name Mangling
獲取任意線程調(diào)用棧的那些事

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丐黄,一起剝皮案震驚了整個(gè)濱河市斋配,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灌闺,老刑警劉巖艰争,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異菩鲜,居然都是意外死亡园细,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門接校,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)猛频,“玉大人,你說(shuō)我怎么就攤上這事蛛勉÷寡埃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵诽凌,是天一觀的道長(zhǎng)毡熏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)侣诵,這世上最難降的妖魔是什么痢法? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮杜顺,結(jié)果婚禮上财搁,老公的妹妹穿的比我還像新娘。我一直安慰自己躬络,他們只是感情好尖奔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般提茁。 火紅的嫁衣襯著肌膚如雪淹禾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天茴扁,我揣著相機(jī)與錄音铃岔,去河邊找鬼。 笑死丹弱,一個(gè)胖子當(dāng)著我的面吹牛德撬,可吹牛的內(nèi)容都是我干的铲咨。 我是一名探鬼主播躲胳,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纤勒!你這毒婦竟也來(lái)了坯苹?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摇天,失蹤者是張志新(化名)和其女友劉穎粹湃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泉坐,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡为鳄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腕让。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孤钦。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纯丸,靈堂內(nèi)的尸體忽然破棺而出偏形,到底是詐尸還是另有隱情,我是刑警寧澤觉鼻,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布俊扭,位于F島的核電站,受9級(jí)特大地震影響坠陈,放射性物質(zhì)發(fā)生泄漏萨惑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一仇矾、第九天 我趴在偏房一處隱蔽的房頂上張望庸蔼。 院中可真熱鬧,春花似錦若未、人聲如沸朱嘴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萍嬉。三九已至乌昔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壤追,已是汗流浹背磕道。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留行冰,地道東北人溺蕉。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像悼做,于是被迫代替她去往敵國(guó)和親疯特。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容