你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 調試沒有符號的 iOS 應用

調試沒有符號的 iOS 應用

編輯:IOS開發綜合

說明:

  這裡的調試是指使用 lldb 遠程調試 iOS 應用

  設置斷點是指在 ObjC 方法上設置斷點

使用場景:

  1、調試被 strip 了的 iOS 應用

  2、調試被 strip 了的 iOS 系統 dylib

在調試時沒有符號的 iOS 應用時,設置斷點非常不方便:

  1、App:在沒有開啟 ASLR 時,需要首先找到方法的地址,然後針對地址設置斷點

  2、Dylib:在沒有開啟 ASLR 時,需要找到dylib的基地址,然後計算偏移

如果開啟了 ASLR,設置斷點會更麻煩。

一直想解決這個問題,曾經想過的方法:

首先,ObjC 語言是一個相對動態的語言,所以使用class-dump這樣的工具,可以 dump 出類信息,函數地址。

另外,DWARF 格式是有公開標准的,

因此,可以通過將 class-dump 的輸出信息轉換成 DWARF,在調試時動態加載符號。

這個方法我不是第一想到,這個帖子中有詳細說明:http://stackoverflow.com/questions/17554070/import-class-dump-info-into-gdb

但是照這個方法進行操作後,發現對 iOS 應用沒效果,而且過程繁瑣。

後來想,ObjC是通過在C語言之上封裝了薄薄的一層(消息特性)而形成的,

所有 ObjC 的方法調用最終會轉換為 C 方法調用,

因此,可以通過在對應的 C 函數上設置斷點來解決斷點設置問題,

而如何得到 C 函數的地址,就依賴於 ObjC 的運行時方法了,主要涉及:

  1、object_getClass

  2、NSSelectorFromString

  3、class_respondsToSelector

  4、class_getMethodImplementation

在解決了在什麼位置設置斷點的問題後,

接下來需要解決如果在 lldb 中方便的設置斷點。

lldb 集成了 Python 腳本引擎,參考:http://lldb.llvm.org/python-reference.html

因此我們可以通過Python腳本擴展 lldb 的調試命令,主要用到如下幾個函數:

  1、lldb.debugger

  2、lldb.debugger.GetSelectedTarget()

  3、lldb.debugger.GetSelectedTarget().GetProcess()

  4、lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread()

  5、lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()

  6、lldb.frame.EvaluateExpression

  7、lldb.debugger.HandleCommand

腳本配置方法:

  方法一:在調試控制台執行:command script import bt_objc.py的文件路徑

  方法二:將如上命令加入到 ~/.lldbinit,如果文件不存在則可以自己動手創建

腳本內容:

  1 #!/usr/bin/python
  2 
  3 '''
  4 Author: 
  5     Proteas
  6 Date:
  7     2014-03-05
  8 Purpose:
  9     set breakpoint without symbols, for examle: stripped macho
 10 Usage:
 11     add the following line to ~/.lldbinit
 12     command script import ~/.lldb/bt_objc.py
 13 '''
 14 
 15 import lldb
 16 import commands
 17 import shlex
 18 import optparse
 19 import re
 20 
 21 def __lldb_init_module (debugger, dict):
 22     debugger.HandleCommand('command script add -f bt_objc.bt_objc bt_objc')
 23     print 'The "bt_objc" command has been installed'
 24 
 25 def create_command_arguments(command):
 26     return shlex.split(command)
 27     
 28 def is_command_valid(args):
 29     ""
 30     if len(args) == 0:
 31         return False
 32 
 33     arg = args[0]
 34     if len(arg) == 0:
 35         return False
 36 
 37     ret = re.match('^[+-]\[.+ .+\]$', arg) # TODO: more strict
 38     if not ret:
 39         return False
 40 
 41     return True
 42 
 43 def get_class_name(arg):
 44     match = re.search('(?<=\[)[^\[].*[^ ](?= +)', arg) # TODO: more strict
 45     if match:
 46         return match.group(0)
 47     else:
 48         return None
 49 
 50 def get_method_name(arg):
 51     match = re.search('(?<= )[^ ].*[^\]](?=\]+)', arg) # TODO: more strict
 52     if match:
 53         return match.group(0)
 54     else:
 55         return None
 56 
 57 def is_class_method(arg):
 58     if len(arg) == 0:
 59         return False
 60 
 61     if arg[0] == '+':
 62         return True
 63     else:
 64         return False
 65 
 66 def get_selected_frame():
 67     debugger = lldb.debugger
 68     target = debugger.GetSelectedTarget()
 69     process = target.GetProcess()
 70     thread = process.GetSelectedThread()
 71     frame = thread.GetSelectedFrame()
 72 
 73     return frame
 74 
 75 def get_class_method_address(class_name, method_name):
 76     frame = get_selected_frame();
 77     class_addr = frame.EvaluateExpression("(Class)object_getClass((Class)NSClassFromString(@\"%s\"))" % "FigPluginView").GetValueAsUnsigned()
 78     if class_addr == 0:
 79         return 0
 80 
 81     sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()
 82     has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()
 83     if not has_method:
 84         return 0
 85 
 86     method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))
 87 
 88     return method_addr.GetValueAsUnsigned()
 89 
 90 def get_instance_method_address(class_name, method_name):
 91     frame = get_selected_frame();
 92     class_addr = frame.EvaluateExpression("(Class)NSClassFromString(@\"%s\")" % class_name).GetValueAsUnsigned()
 93     if class_addr == 0:
 94         return 0
 95 
 96     sel_addr = frame.EvaluateExpression("(SEL)NSSelectorFromString(@\"%s\")" % method_name).GetValueAsUnsigned()
 97     has_method = frame.EvaluateExpression("(BOOL)class_respondsToSelector(%d, %d)" % (class_addr, sel_addr)).GetValueAsUnsigned()
 98     if not has_method:
 99         return 0
100 
101     method_addr = frame.EvaluateExpression('(void *)class_getMethodImplementation(%d, %d)' % (class_addr, sel_addr))
102 
103     return method_addr.GetValueAsUnsigned()
104 
105 def bt_objc(debugger, command, result, dict):
106     args = create_command_arguments(command)
107 
108     if not is_command_valid(args):
109         print 'please specify the param, for example: "-[UIView initWithFrame:]"'
110         return
111 
112     arg = args[0]
113     class_name = get_class_name(arg)
114     method_name = get_method_name(arg)
115 
116     address = 0
117     if is_class_method(arg):
118         address = get_class_method_address(class_name, method_name)
119     else:
120         address = get_instance_method_address(class_name, method_name)
121 
122     if address:
123         lldb.debugger.HandleCommand ('breakpoint set --address %x' % address)
124     else:
125         print "fail, please check the arguments"

如上腳本也可以從這個鏈接下載:https://raw.github.com/Proteas/lldb-scripts/master/bt_objc.py

腳本配置完畢後,可以通過如下命令設置斷點:

bt_objc "-[UIView initWithFrame:]"

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved