Swift 使用 LLDB 調試命令



打印和賦值,觀察數值變量和view對象屬性

  • p指令可打印其對象類型、內存地址以及該對象的值等具體信息,
  • po指令則是打印其調用description方法得到的值。
  • e 賦值指令(后面有例子詳解)

流程控制

image_1bpvqsf71os1t5e64h16mm4lm9.png-61.6kB

n 命令,代表 Step Over 操作。
s 命令,代表 Step Into 操作。
finish 命令,代表 Step Out 操作。
c 命令,代表恢復程序執行操作。

修改指針變量的值,觀測程序不同變化

程序中: testThreadReturn = “testThreadReturn”

(lldb) p testThreadReturn
(String) $R2 = "testTheardReturn"
(lldb) e testThreadReturn = "zaozuo"
(lldb) p testThreadReturn 
(String) $R4 = "zaozuo"

動態修改view的屬性

image_1bpvvlkmfdnjgq0t4toj41fodm.png-45.4kB

Thread Return,函數設置斷點返回值(swift不兼容)

  • 這個在OC一切ok,在swift還不能用會有以下錯誤
(lldb) thread return "zaozuo-return"
error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..

調試時,還有一個很棒的函數可以用來控制程序流程:thread return 。它有一個可選參數,在執行時它會把可選參數加載進返回寄存器里,然后立刻執行返回命令,跳出當前棧幀。這意味這函數剩余的部分不會被執行。這會給 ARC 的引用計數造成一些問題,或者會使函數內的清理部分失效。但是在函數的開頭執行這個命令,是個非常好的隔離這個函數,偽造返回值的方式 。

直接設置返回值,不用寫死代碼破壞代碼結構

(五星推薦):編輯斷點

設置斷點進入條件,condition

image_1blat7pff10qmu251thl1c29fs8g.png-182.4kB

斷點行為 (Action):

可以設置或不設置,斷點進入的條件,設置進入條件后還可以,自定義后續的動作,比如,打印值,修改值,執行shell命令,執行lldb命令,打印log等等很多自定義的動作,這一點確實很強大,這里就可以和shell 腳本聯動,測試一些不好測試的點,和需要提前處理外部因素的案例

(五星推薦):在lldb中初始化值,動態賦值,執行多行語句

  • 簡單變量賦值,僅僅在調試環境賦值了一個值,是局部變量,可以用print打印
(lldb) e let hello = "Hello"
you can simply use:
(lldb) p let hello = "Hello"
  • 使用"$"符號定義全局常量和變量
OC中為:
(lldb) e NSString *$a = @"c" (lldb) po $a c 

Swift中變為:
(lldb) e let $a = "a" 
(lldb) po $a "a"  
  • Swift中多行語句:輸入完成敲一個空行即可開始執行
(lldb) p
Enter expressions, then terminate with an empty line to evaluate:
struct compass{var direction = "N"; var angle = 16.5}
var c = compass()
print(c)
(lldb)
  • 同樣的你也可以向當前view添加一個layer
(lldb) p
Enter expressions, then terminate with an empty line to evaluate:
1 let layer = CALayer()
2 layer.backgroundColor = UIColor.yellow.cgColor
3 layer.bounds = CGRect(x:0, y:0, width:100, height:100)
4 layer.position = CGPoint(x:250, y:300)
5 layer.cornerRadius = 12.0
6 view.layer.addSublayer(layer)
7 
(lldb)

image_1bq027mrc1hea136n116b1vj617m91a.png-18.3kB

  • 修改函數中成員變量的值
    image_1bnnq8f3i155b1k2aaov108dkv19.png-106.9kB

引入模塊 Importing modules

(lldb) p import MapKit

打印UI層級關系及更新UI

  • oc,試了下這個在swift也可以運行:
(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7ffcd2f0f1e0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7ffcd2f10170>; layer = <UIWindowLayer: 0x7ffcd2f0ea80>>
   | <UIView: 0x7ffcd2c6dc10; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7ffcd2c17f10>>
   |    | <UIButton: 0x7ffcd2c6dfc0; frame = (20 62; 78 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffcd2c6bc10>>
   |    |    | <UIButtonLabel: 0x7ffcd2f15af0; frame = (16 6; 46 18); text = 'Button'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7ffcd2f16120>>
   |    | <_UILayoutGuide: 0x7ffcd2c6fae0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7ffcd2c6faa0>>
   |    | <_UILayoutGuide: 0x7ffcd2c70740; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7ffcd2c6d3d0>>
  • swift:
expr -l objc++ -O -- [[[UIWindow keyWindow] rootViewController] _printHierarchy]
  • view指針賦值
(lldb) e id $view = (id) 0x7fbd71432590 
  • 使用指針進行操作
(lldb) e (void) [$view setBackgroundColor:[UIColor redColor]]
  • 當然,我們運行完這條命令,界面上不會馬上反應出來,我們還需要
    調用這個命令刷新一下:
(lldb) e (void)[CATransaction flush]

Push 一個 View Controller

  • 第一步我本地實驗了下沒有成功,報錯了就,可能我代碼是swift的所以得用swift 代碼,下面僅僅oc 可以
(lldb) e navigationController?.pushViewController($vcc, animated: true)
MainNavigationController.swift[36], pushViewController: [pushViewController=====><zaozuo.ProductCategoryController: 0x7ffce5147600>]
(Swift.Void?) $R4 = nil

然后執行:
(lldb) caflush // e (void)[CATransaction flush]
或是
(lldb) c
都可以

查找按鈕的 target(這個方法還未嘗試,留在這里開闊下思路)

想象你在調試器中有一個 $myButton 的變量,可以是創建出來的,也可以是從 UI 上抓取出來的,或者是你停止在斷點時的一個局部變量。你想知道,按鈕按下的時候誰會接收到按鈕發出的 action。非常簡單:

(lldb) po [$myButton allTargets]
{(
    <MagicEventListener: 0x7fb58bd2e240>
)}
(lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
<__NSArrayM 0x7fb58bd2aa40>(
_handleTap:
)

觀察實例變量的變化

假設你有一個 UIView,不知道為什么它的 _layer 實例變量被重寫了 (糟糕)。因為有可能並不涉及到方法,我們不能使用符號斷點。相反的,我們想監視什么時候這個地址被寫入。

首先,我們需要找到 _layer 這個變量在對象上的相對位置:

(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer"))
(ptrdiff_t) $0 = 8
現在我們知道 ($myView + 8) 是被寫入的內存地址:

(lldb) watchpoint set expression -- (int *)$myView + 8
Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
    new value: 0x0000000000000000
這被以 wivar $myView _layer 加入到 Chisel 中。

非重寫方法的符號斷點

假設你想知道 -[MyViewController viewDidAppear:] 什么時候被調用。如果這個方法並沒有在MyViewController 中實現,而是在其父類中實現的,該怎么辦呢?試着設置一個斷點,會出現以下結果:

(lldb) b -[MyViewController viewDidAppear:]
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.

因為 LLDB 會查找一個符號,但是實際在這個類上卻找不到,所以斷點也永遠不會觸發。你需要做的是為斷點設置一個條件 [self isKindOfClass:[MyViewController class]],然后把斷點放在 UIViewController 上。正常情況下這樣設置一個條件可以正常工作。但是這里不會,因為我們沒有父類的實現。

viewDidAppear: 是蘋果實現的方法,因此沒有它的符號;在方法內沒有 self 。如果想在符號斷點上使用 self,你必須知道它在哪里 (它可能在寄存器上,也可能在棧上;在 x86 上,你可以在 $esp+4 找到它)。但是這是很痛苦的,因為現在你必須至少知道四種體系結構 (x86,x86-64,armv7,armv64)。想象你需要花多少時間去學習命令集以及它們每一個的調用約定,然后正確的寫一個在你的超類上設置斷點並且條件正確的命令。
幸運的是,這個在 Chisel 被解決了。這被成為

bmessage:
(lldb) bmessage -[MyViewController viewDidAppear:]
Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c

讓代碼停在Swift Error 或者Objective C異常

  • 停在Objective C異常
(lldb) br s -E  objc
Breakpoint 6: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010dededbb
  • 停在Swift Error
(lldb) br s -E swift
Breakpoint 7: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55ccc0
  • 停在某一種類型的Swift Error
(lldb) br s -E swift -O EnumError
Breakpoint 8: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55

參考
http://lldb.llvm.org/
https://lldb.llvm.org/lldb-gdb.html
https://www.invasivecode.com/weblog/lldb-expression-for-code-injection-in-xcode
http://www.theappbusiness.com/blog/debugging-in-swift
iOS 開發者旅途中的指南針 - LLDB 調試技術,swiftcafe
與調試器共舞 - LLDB 的華爾茲
Chisel-LLDB命令插件,讓調試更Easy
Chisel常用命令總結
Swift 代碼調試核武-LLDB調試基礎
Basic LLDB tips
udacity 的視頻教程

結尾

LLDB 調試命令強大,但是有些語法不大好書寫,Chisel 做了封裝和一些擴展,使得調試App 不在打印各種log,我們可以快捷的查看或是修改代碼內的變量,常量,數組,對象,在斷點位置執行log,shell等但這都是調試代碼內部信息,如何更加靈活的調試UIView和約束信息呢?

這就有了下文:

Reveal 9 iOS 界面調試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM