iOS LLDB調(diào)試學(xué)習(xí)使用

之前使用Xcode調(diào)試在LLDB中只用了打印、看日志輸入、再復(fù)雜點(diǎn)就是po一個(gè)屬性的值,前段時(shí)間看了篇關(guān)于LLDB的文章,感覺之前完全白白浪費(fèi)了Xcode提供這么好的調(diào)試工具。這段時(shí)間學(xué)習(xí)實(shí)驗(yàn),此文供記錄學(xué)習(xí)。

LLDB 概述

LLDB全稱 " Low Level Debugger ", 是由蘋果出品。標(biāo)準(zhǔn)的 LLDB 提供了一組廣泛的命令,旨在與熟悉的 GDB 命令兼容。 除了使用標(biāo)準(zhǔn)配置外,還可以很容易地自定義 LLDB 以滿足實(shí)際需要,帶給我們更豐富的流程控制和數(shù)據(jù)檢測(cè)的調(diào)試功能。

LLDB 作用

  • 允許你在程序運(yùn)行的特定時(shí)暫停它;
  • 查看變量的值;
  • 執(zhí)行自定的指令;
  • 按照你所認(rèn)為合適的步驟來操作程序的進(jìn)展;

LLDB控制臺(tái)

Xcode中內(nèi)嵌了LLDB控制臺(tái),在Xcode中代碼的下方,我們可以看到LLDB控制臺(tái)??旖萱I是command + shift + y。LLDB控制臺(tái)平時(shí)會(huì)輸出一些log信息。如果我們想輸入命令調(diào)試,必須讓程序進(jìn)入暫停狀態(tài)。


LLDB控制臺(tái).png

LLDB 語法

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
<command>(命令)和<subcommand>(子命令):LLDB調(diào)試命令的名稱。命令和子命令按層級(jí)結(jié)構(gòu)來排列:一個(gè)命令對(duì)象為跟隨其的子命令對(duì)象創(chuàng)建一個(gè)上下文,子命令又為其子命令創(chuàng)建一個(gè)上下文,依此類推。
<action>:執(zhí)行命令的操作
<options>:命令選項(xiàng)
<arguement>:命令的參數(shù)
[]:表示命令是可選的,可以有也可以沒有

舉個(gè)例子,假設(shè)我們給main.m中16行設(shè)置一個(gè)斷點(diǎn),我們使用下面的命令:

breakpoint set -f main.m -l 16

與上面語法結(jié)構(gòu)對(duì)應(yīng)的是:

command: breakpoint 添加斷點(diǎn)命令
action: set 表示設(shè)置斷點(diǎn)
option: -f 表示在某文件添加斷點(diǎn)
arguement: mian.m 表示要添加斷點(diǎn)的文件名為mian.m
option: -l 表示某一行
arguement: 16 表示第16行

上面的print命令只是LLDB調(diào)試中的一個(gè)很簡單但很常用的命令,除此之外還有很多有可能用到的命令:

apropos           -- 列出與單詞或主題相關(guān)的調(diào)試器命令
  breakpoint        -- 在斷點(diǎn)上操作的命令 (詳情使用'help b'查看)
  bugreport         -- 用于創(chuàng)建指定域的錯(cuò)誤報(bào)告
  command           -- 用于管理自定義LLDB命令的命令
  disassemble       -- 拆分當(dāng)前目標(biāo)中的特定說明。 默認(rèn)為當(dāng)前線程和堆棧幀的當(dāng)前函數(shù)
  expression        -- 求當(dāng)前線程上的表達(dá)式的值。 以LLDB默認(rèn)格式顯示返回的值
  frame             -- 用于選擇和檢查當(dāng)前線程的堆棧幀的命令
  gdb-remote        -- 通過遠(yuǎn)程GDB服務(wù)器連接到進(jìn)程。 如果未指定主機(jī),則假定為localhost
  gui               -- 切換到基于curses的GUI模式
  help              -- 顯示所有調(diào)試器命令的列表,或提供指定命令的詳細(xì)信息
  kdp-remote        -- 通過遠(yuǎn)程KDP服務(wù)器連接到進(jìn)程。 如果沒有指定UDP端口,則假定端口41139
  language          -- 指定源語言
  log               -- 控制LLDB內(nèi)部日志記錄的命令
  memory            -- 用于在當(dāng)前目標(biāo)進(jìn)程的內(nèi)存上操作的命令
  platform          -- 用于管理和創(chuàng)建平臺(tái)的命令
  plugin            -- 用于管理LLDB插件的命令
  process           -- 用于與當(dāng)前平臺(tái)上的進(jìn)程交互的命令
  quit              -- 退出LLDB調(diào)試器
  register          -- 命令訪問當(dāng)前線程和堆棧幀的寄存器
  script            -- 使用提供的代碼調(diào)用腳本解釋器并顯示任何結(jié)果。 如果沒有提供代碼,啟動(dòng)交互式解釋器。
  settings          -- 用于管理LLDB設(shè)置的命令
  source            -- 檢查當(dāng)前目標(biāo)進(jìn)程的調(diào)試信息所描述的源代碼的命令
  target            -- 用于在調(diào)試器目標(biāo)上操作的命令
  thread            -- 用于在當(dāng)前進(jìn)程中的一個(gè)或多個(gè)線程上操作的命令
  type              -- 在類型系統(tǒng)上操作的命令
  version           -- 顯示LLDB調(diào)試器版本
  watchpoint        -- 在觀察點(diǎn)上操作的命令


縮寫命令 (使用 'help command alias'查看更多信息):

  add-dsym  -- ('target symbols add')  通過指定調(diào)試符號(hào)文件的路徑,或使用選項(xiàng)指定下載符號(hào)的模塊,將調(diào)試符號(hào)文件添加到目標(biāo)的當(dāng)前模塊中的一個(gè)
  attach    -- ('_regexp-attach')  通過ID或名稱附加到進(jìn)程
  b         -- ('_regexp-break')  使用幾種簡寫格式之一設(shè)置斷點(diǎn)
  bt        -- ('_regexp-bt')  顯示當(dāng)前線程的調(diào)用堆棧。通過數(shù)字參數(shù)設(shè)置最多顯示幀數(shù)。參數(shù)“all”顯示所有線程
  c         -- ('process continue')  繼續(xù)執(zhí)行當(dāng)前進(jìn)程中的所有線程
  call      -- ('expression --')  計(jì)算當(dāng)前線程上的表達(dá)式,使用LLDB的默認(rèn)格式顯示返回的值
  continue  -- ('process continue')  繼續(xù)執(zhí)行當(dāng)前進(jìn)程中的所有線程
  detach    -- ('process detach')  脫離當(dāng)前目標(biāo)進(jìn)程
  di        -- ('disassemble')  拆分當(dāng)前目標(biāo)中的特定說明。 默認(rèn)為當(dāng)前線程和堆棧幀的當(dāng)前函數(shù)
  dis       -- ('disassemble')  同上
  display   -- ('_regexp-display')  在每次停止時(shí)計(jì)算表達(dá)式(請(qǐng)參閱'help target stop-hook')
  down      -- ('_regexp-down')  選擇一個(gè)新的堆棧幀。默認(rèn)為移動(dòng)一個(gè)幀,數(shù)字參數(shù)可以指定值
  env       -- ('_regexp-env')  查看和設(shè)置環(huán)境變量的簡寫
  exit      -- ('quit')  退出LLDB調(diào)試器
  f         -- ('frame select')  從當(dāng)前線程中通過索引選擇當(dāng)前堆棧幀(參見'thread backtrace')
  file      -- ('target create')  使用參數(shù)作為主要可執(zhí)行文件創(chuàng)建目標(biāo)
  finish    -- ('thread step-out')  完成當(dāng)前堆棧幀的執(zhí)行并返回后停止。 默認(rèn)為當(dāng)前線程
  image     -- ('target modules')  用于訪問一個(gè)或多個(gè)目標(biāo)模塊的信息的命令
  j         -- ('_regexp-jump')  將程序計(jì)數(shù)器設(shè)置為新地址
  jump      -- ('_regexp-jump')  同上
  kill      -- ('process kill')  終止當(dāng)前目標(biāo)進(jìn)程
  l         -- ('_regexp-list')  使用幾種簡寫格式之一列出相關(guān)的源代碼
  list      -- ('_regexp-list')  同上
  n         -- ('thread step-over')  源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  next      -- ('thread step-over')  同上
  nexti     -- ('thread step-inst-over')  指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  ni        -- ('thread step-inst-over')  同上
  p         -- ('expression --')  計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
 parray    -- ('expression -Z %1   --')  同上
  po        -- 計(jì)算當(dāng)前線程上的表達(dá)式。顯示由類型作者控制的格式的返回值。
  poarray   -- ('expression -O -Z %1    --')  計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
  print     -- ('expression --')  同上
  q         -- ('quit')  退出LLDB調(diào)試器
  r         -- ('process launch -X true --')  在調(diào)試器中啟動(dòng)可執(zhí)行文件
  rbreak    -- ('breakpoint set -r %1')  在可執(zhí)行文件中設(shè)置斷點(diǎn)或斷點(diǎn)集
  repl      -- ('expression -r  -- ')  E計(jì)算當(dāng)前線程上表達(dá)式的值,以LLDB默認(rèn)格式顯示返回值
  run       -- ('process launch -X true --')  在調(diào)試器中啟動(dòng)可執(zhí)行文件
  s         -- ('thread step-in')  源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  si        -- ('thread step-inst')  指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  sif       -- 遍歷當(dāng)前塊,如果直接步入名稱與TargetFunctionName匹配的函數(shù),則停止
  step      -- ('thread step-in')  源級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  stepi     -- ('thread step-inst')  指令級(jí)單步執(zhí)行、步進(jìn)調(diào)用,默認(rèn)當(dāng)前線程
  t         -- ('thread select')  更改當(dāng)前選擇的線程
  tbreak    -- ('_regexp-tbreak')  使用幾種簡寫格式之一設(shè)置單次斷點(diǎn)
  undisplay -- ('_regexp-undisplay')  每次停止時(shí)停止顯示表達(dá)式(由stop-hook索引指定)
  up        -- ('_regexp-up')  選擇較早的堆棧幀。 默認(rèn)為移動(dòng)一個(gè)幀,數(shù)值參數(shù)可以指定任意數(shù)字
  x         -- ('memory read')  從當(dāng)前目標(biāo)進(jìn)程的內(nèi)存中讀取

上面的命令不需要都記住,記住常用的幾個(gè)如: p, po, call, breakpoint, call, expression 等 ,其他需要的時(shí)候再查,通過“help”命令顯示所有調(diào)試命令的列表,或查詢指定命令的詳細(xì)信息。

LLDB 常用命令

LLDB擁有大量有用的調(diào)試工具。

獲取變量值:expression, e, print, po, p
獲取執(zhí)行環(huán)境+特定語言命令:bugreport, frame, language
執(zhí)行流程控制:process, breakpoint, thread, watchpoint
其他:command,platform,gui

下面列一些常用的命令:

expression

expression命令的作用是執(zhí)行一個(gè)表達(dá)式,并將表達(dá)式返回的結(jié)果輸出。

expression命令的格式

expression <cmd-options> -- <expr>

<cmd-options>:命令選項(xiàng),一般情況下使用默認(rèn)的即可,不需要特別標(biāo)明。
--: 命令選項(xiàng)結(jié)束符,表示所有的命令選項(xiàng)已經(jīng)設(shè)置完畢,如果沒有命令選項(xiàng),--可以省略
<expr>: 要執(zhí)行的表達(dá)式

<font color=gray size=4>例如</font>
expression -O -- testStr
-O 是命令選項(xiàng)
testStr 是執(zhí)行的表達(dá)式

<font color=gray size=4>再例如</font>
expression testStr 和 expression -- testStr
兩個(gè)命令功能是一致的,沒有命令選項(xiàng)時(shí)可以省略--

expression最基本的功能是打印和修改變量的值,你可以在運(yùn)行時(shí)執(zhí)行幾乎任何表達(dá)式或命令。

// 改變顏色

(lldb) expression self.view.backgroundColor = UIColor.brown    //改變顏色
(lldb) expression CATransaction.flush()   //刷新
(lldb) 

// 改變屬性值

(lldb) expression testStr   // 打印testStr
(String) $R0 = "123"
(lldb) expression testStr = "abc"  //修改testStr的值
(lldb) expression testStr
(String) $R2 = "abc"  //testStr值調(diào)試期間變成abc

expression 擁有大約30個(gè)命令選項(xiàng)

下面列出了幾個(gè)比較常用的選項(xiàng):

-D ( --depth ) - 設(shè)置打印聚合類型的遞歸深度(默認(rèn)無限遞歸)。
-O ( --object-desctiption ) - 打印description方法。
-T ( --show-types ) - 顯示每個(gè)變量的類型。
-f ( --format ) - 設(shè)置輸出格式。
-i ( --ignore-breakpoints ) - 運(yùn)行表達(dá)式時(shí)忽略表達(dá)式內(nèi)的斷點(diǎn)。

p、 print、 e、 call

實(shí)際上call、 p、print這三個(gè)指令都是 expression 指令的別名, 實(shí)際上的運(yùn)行效果是一樣的,舉例說明,請(qǐng)看一下代碼:

(lldb) call testStr
(String) $R8 = "abc"
(lldb) p testStr
(String) $R9 = "abc"
(lldb) print testStr
(String) $R10 = "abc"
(lldb) e testStr
(String) $R11 = "abc"

po

po self 命令與 expression -O -- self功能類似。

breakpoint

開發(fā)調(diào)試的第一步就是設(shè)置斷點(diǎn),我們用的最多的就是breakpoint命令,但我們很少在LLDB 中使用breakpoint命令,大多在Xcode GUI界面中設(shè)置斷點(diǎn)。除了直接直接出發(fā)暫停調(diào)試外,我們還可以進(jìn)一步的設(shè)置。


image.png
  • 添加condition,一般用于多次調(diào)用的函數(shù)或者循壞的代碼中,在作用域內(nèi)達(dá)到某個(gè)條件,才會(huì)觸發(fā)程序暫停
  • 忽略次數(shù),這個(gè)很容易理解,在忽略觸發(fā)幾次后再觸發(fā)暫停
  • 添加Action,為這個(gè)斷點(diǎn)添加子命令、腳本、shell命令、聲效(有個(gè)毛線用)等Action,我的理解是一個(gè)腳本化的功能,我們可以在斷點(diǎn)的基礎(chǔ)上添加一些方便調(diào)試的腳本,提高調(diào)試效率。
  • 自動(dòng)繼續(xù),配合上面的添加Action,我們就可以不用一次又一次的暫停程序進(jìn)行調(diào)試來查詢某些值(大型程序中斷一次還是會(huì)有卡頓),直接用Action將需要的信息打印在控制臺(tái),一次性查看即可。

除去在代碼中直接點(diǎn)擊添加斷點(diǎn)外,我們也可以在 BreakPoint Navigation頁面下直接添加相關(guān)的斷點(diǎn)。我們常用的有 Exception Breakpoint 與 Symbolic Breakpoint

breakpointNav.png
breakPointException.png
  • Add Exception Breakpoint
    Exception Breakpoint為異常斷點(diǎn)。在某些情況下,TableView的數(shù)據(jù)源與UI操作不一致,或者容器插入了nil的指針,將消息傳至野指針,都會(huì)導(dǎo)致程序的crash,并且LLDB輸出的信息不是很友好。加上異常斷點(diǎn),能夠使程序在拋出異常的棧自動(dòng)暫停,可直接定位導(dǎo)致拋出異常的代碼。在一般的開發(fā)流程中,都建議開啟這個(gè)異常斷點(diǎn),反正你總是會(huì)crash的嘿嘿。
  • Add Symbolic Breakpoint
    Symbolic Breakpoint 為符號(hào)斷點(diǎn)。有時(shí)候,我們并不清楚程序會(huì)在什么情況下調(diào)用某一個(gè)函數(shù),那我們可以通過符號(hào)斷點(diǎn)來獲取調(diào)用該函數(shù)時(shí)的程序堆棧。當(dāng)然,在自己實(shí)現(xiàn)的類,我們也可以在該函數(shù)實(shí)現(xiàn)的地方打上斷點(diǎn),但如果需要定位其他框架提供的API的調(diào)用,就只能使用符號(hào)斷點(diǎn)啦。

當(dāng)然,LLDB的breakpoint命令也可以實(shí)現(xiàn)上述的功能,因?yàn)椴怀S?,所以這里就簡單列舉一些用法。 breakpoint set -n trigger //在所有類的trigger函數(shù)實(shí)現(xiàn)中打上斷點(diǎn)

    breakpoint set -f ViewController.m -n trigger //在ViewController.m中的trigger方法打上斷點(diǎn) 
    breakpoint set -f ViewController.m -l 50 //在ViewController.m的50行打上斷點(diǎn) 
    breakpoint set -f ViewController.m -n trigger: -c testCondition > 5 //在ViewController.m中的trigger方法打上斷點(diǎn)并添加condition, testCondition大于5時(shí)觸發(fā)斷點(diǎn) 
    breakpoint set -n trigger -o //單次斷點(diǎn) 
    breakpoint command add -o "frame info" 3 //在設(shè)置的三號(hào)斷點(diǎn)加入子命令frame info 
    breakpoint list // 列出所有斷點(diǎn) 
    breakpoint delete 3 //刪除3號(hào)斷點(diǎn)

watchpoint

通過watchpoint 來查看某個(gè)屬性是否有變化,watchpoint是對(duì)地址生效的斷點(diǎn),用watchpoint觀察屬性的地址變化情況。

(lldb) watchpoint set variable self.testArr  //觀察一個(gè)名為testArr的屬性值
Watchpoint created: Watchpoint 1: addr = 0x7f81b4607478 size = 8 state = enabled type = w
    declare @ '/LLDBDemo/ViewController.swift:16'
    watchpoint spec = 'self.testArr'
    new value: 1 value
屏幕快照 2018-05-12 上午11.11.54.png

按鈕點(diǎn)擊方法中修改了testArr的值,

(lldb) watchpoint set variable self.testArr  //觀察一個(gè)名為testArr的屬性值
Watchpoint created: Watchpoint 1: addr = 0x7f81b4607478 size = 8 state = enabled type = w
    declare @ '/LLDBDemo/ViewController.swift:16'
    watchpoint spec = 'self.testArr'
    new value: 1 value

Watchpoint 1 hit:    //檢測(cè)到屬性變化
old value: 1 value
new value: 2 values

thread

可以使用 thread backtrace(或 bt )命令打印線程堆棧信息:

屏幕快照 2018-05-12 上午11.22.27.png
屏幕快照 2018-05-12 上午11.22.19.png

thread backtrace 后面可以添加命令選項(xiàng):

-c:設(shè)置打印堆棧的幀數(shù)(frame)
-s:設(shè)置從哪個(gè)幀(frame)開始打印
-e:是否顯示額外的回溯

Debug 的時(shí)候,也許會(huì)因?yàn)楦鞣N原因,我們不想讓代碼執(zhí)行某個(gè)方法,或者要直接返回一個(gè)想要的值??梢允褂?thread return 命令:

thread return '要返回的值'

thread return 不讓代碼執(zhí)行某個(gè)方法,可以在某個(gè)方法的開始位置設(shè)置一個(gè)斷點(diǎn),當(dāng)程序運(yùn)行到斷點(diǎn)的位置時(shí)直接返回我們?cè)O(shè)置的返回值。
但我實(shí)驗(yàn)了,總是提示錯(cuò)誤:

error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..

有了解的同學(xué)幫忙指導(dǎo)下。

參考鏈接:
使用 LLDB 調(diào)試 APP
iOS調(diào)試-LLDB學(xué)習(xí)總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容