一. Python配置
这里学习python,我们使用的是VSCode,首先在VSCode中安装Python插件,作用如下
- 用来帮助我们跟系统安装的Python解释器进行关联,方便代码调试
- 如果安装了多个版本python解释器,这里选择不同版本
// 查看系统默认安装的python3信息
$ python3
Python 3.8.2 (default, Nov 4 2020, 21:23:28)
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> quit()
// 查看系统安装的python3路径
$ which python3
/usr/bin/python3
// 注意这里安装最新版本,会跟系统自身的Python 3.8.2版本产生冲突
$ brew install python3
// 如果产生冲突,就卸载python3
$ brew uninstall python3
// 查看通过brew安装的python解释器信息
$ ls -l /usr/local/bin/python
ls: /usr/local/bin/python: No such file or directory
// 系统提供的环境变量,查看Python路径,如果要使用lldb Python的api,可以把Python3的路径加入PYTHONPATH环境变量,这样Python3在去执行的时候可以直接通过PYTHONPATH环境变量找到lldb的api
$ echo $PYTHONPATH
// 查看lldb使用的Python3路径
$ lldb -P
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3
$ open /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources
// 如果要配置上面的环境变量,可以配置在下面文件中
$ vim ~/.bashrc
// 当把Python路径配置到环境变量PYTHONPATH,执行下面python3
$ python3
Python 3.8.2 (default, Nov 4 2020, 21:23:28)
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys //引入系统库,类似引用Foundation
>>> print(sys.path) //如果上面环境变量PYTHONPATH配置了Python路径,下面打印就可以看到
['', '/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python38.zip', '/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8', '/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages']
>>> quit()
环境变量推荐配置在$ vim ~/.bashrc
文件,配置在这个文件就可以和zsh进行关联。配置好PYTHONPATH环境变量,终端执行 $ echo $PYTHONPATH
就能输出Python的路径,要想使用lldb的api,就必须先配置PYTHONPATH,也就是Python路径
注意⚠️ 如果运行Python的时候发现runtime的错误,必然是Python解释器版本冲突
开发Python使用的工具
- 使用VSCode,B.py文件中导入lldb模块
import lldb
,实际上lldb是Xcode中的,目录为../Python3/lldb.. - 使用PyCharm,这个工具比较简洁。但是这个工具不能直接读取配置的环境变量PYTHONPATH,需要对路径进行手动配置。
二. 打印与程序入口
Python引入库或文件的方式
- 方式一
import lldb
- 方式二
from module import A
或import module.A
//从module目录下引入A,如果A.py文件与当前Python文件在同一目录下,可以直接import A
。如果A.py文件与当前Python文件不在同一目录下,需要先找到A.py所在目录
当前Python文件引用其他Python文件的变量,比如引用A.py文件中的变量a
- 如果使用
import module.A
引入的A.py文件,可以这样使用module.A.a
- 如果使用
from module import A
引入的A.py文件,可以直接使用A.a
- 如果使用
from module import A as Person
引入的A.py文件,Person相当于给A.py起了别名,可以这样使用Person.a
Python文件中的打印方式
- 普通方式
print(Person.a)
- 使用%
print("打印一个数据:'%d'" % Person.a)
%d表示这里接收的是一个十进制数,传参数使用%,注意%左右各有一个空格 - 使用{}占位符 .format是py中字符串的方法
print('你好,我是模块B……{}'.format(Person.a))
-
print(f'你好,我是模块Person……{Person.a}')
上面方式的一种简写形式
上面第三种打印方式 {} 作为占位符,那如果想打印 {} 符号该怎么办?
print('你好,我是模块B……{{}}{}'.format(Cat.a))
,{} 里面的 {} 作为字符串打印出来,Python中也有转义字符,只是转义字符对 {} 无效。
Python文件中函数的使用
# pass占位,定义方法person(),注意后面要加冒号: if判断后面也是要加:
def person():
pass //前面一定要加空格
- 方式一调用 person()
- 方式二调用 print("%s" % __name__) //__表示私有属性,私有方法,注意Python中没有绝对的私有
Python文件中规避一些打印语句
这里以B.py文件作为主文件运行,B.py文件中调用A.py文件的方法,由于Python中没有类似iOS中的main入口函数,执行B.py文件的时候 print("%s" % __name__)
中的__name__ 会转变为 __main__
,而执行A.py的时候__name__ 为module.A
所以就不会打印print(a)
// A.py文件
a = 100
print('你好,我是模块A……')
print(__name__) //打印的name是module.A
// 规避print(a)语句
if __name__ == '__main__':
print(a)
// B.py文件
if __name__ == '__main__':
# 创建程序的入口函数
print("%s" % __name__)
哪个文件作为主文件运行,哪个文件里面的__name__
就是程序的入口__main__
三. Python与lldb插件
3.1 lldb中如何使用Python
这里直接拿的本地的一个可执行文件test来进行测试
- 方式一 针对断点层面的lldb与Python的交互
$ lldb -file test
(lldb) target create "test"
Current executable set to '/上课代码/02-lldb使用Python/代码/test' (x86_64).
(lldb) b main //添加一个断点
Breakpoint 1: where = test`main, address = 0x0000000100003f70
(lldb) script //启用一个script进入Python交互环境
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
>>> quit()
(lldb) br command add --script-type python 1 //给上面断点添加配置,通过Python添加额外功能
Enter your Python command(s). Type 'DONE' to end.
def function (frame, bp_loc, internal_dict):
"""frame: the lldb.SBFrame for the location at which you stopped
bp_loc: an lldb.SBBreakpointLocation for the breakpoint location information
internal_dict: an LLDB support object not to be used"""
name = frame.GetFunctionName()
print(f"Person-----{name}")
DONE //表示上面function已经定义完成,回撤
(lldb) r
Process 27619 launched: '/上课代码/02-lldb使用Python/代码/test' (x86_64)
Person-----main //这里成功打印上面function名称
Process 27619 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100003f70 test`main
test`main:
-> 0x100003f70 <+0>: pushq %rbp
0x100003f71 <+1>: movq %rsp, %rbp
0x100003f74 <+4>: subq $0x10, %rsp
0x100003f78 <+8>: movl $0x0, -0x4(%rbp)
Target 0: (test) stopped.
- 方式二自定义一个lldb命令,要想在lldb中使用Python,首先要在lldb中启用一个Python解释器。其中定义的方法参数必须是固定的,这样才能成为lldb的命令
(lldb) script //启用Python解释器进行交互
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
>>> def nm(debugger, command, result, internal_dict): //定义一个function名称为nm,其中function接收的参数是固定的,debugger代表当前执行lldb中的debugger实例对象,command代表传递的参数,result代表对外返回的结果
... cmd = "e -f x -- 4*2" //执行一个加法的16进制打印,注意cmd前面有tab空格键
... lldb.debugger.HandleCommand(cmd) //通过lldb debugger来处理上面命令
... // 这里继续回撤,下面不再出现... 表示nm函数编写完成
>>> quit()
(lldb) command script add nm -f nm //转换成lldb命令,前面一个nm是给函数起的调用命令,后面一个nm是定义的Python函数,-f是将命令与Python函数进行关联
(lldb) nm
(int) $0 = 0x00000008 //正常将4*2的结果转换为16进制
上面使用Python自定义lldb命令,与自定义lldb插件时传递的参数是类似的,都有debugger实例对象 command传递的参数 result返回结果
- 使用自定义lldb命令查看可执行文件信息
(lldb) image list
[ 0] E8D1A92C-1C2A-3084-A138-36FEF3288E8C 0x0000000100000000 /上课代码/02-lldb使用Python/代码/test // 可执行文件的地址
[ 1] DEA51514-B4E8-3368-979B-89D0F8397ABC 0x0000000100010000 /usr/lib/dyld
[ 2] A7FB4899-9E04-37ED-9DD8-8FFF0400879C 0x00007fff2a539000 /usr/lib/libSystem.B.dylib
[ 3] 2F7F7303-DB23-359E-85CD-8B2F93223E2A 0x00007fff2a533000 /usr/lib/system/libcache.dylib
...
(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
>>> def nm(debugger, command, result, internal_dict):
... cmd = "platform shell nm {}".format(command) //platform shell表示在lldb中执行shell相关的命令,后面表示使用nm命令,{}表示占位符
... lldb.debugger.HandleCommand(cmd)
...
>>> quit()
(lldb) command script add nm -f nm
(lldb) nm -pa /上课代码/02-lldb使用Python/代码/test //成功输出可执行文件的符号信息
0000000000000000 - 00 0000 SO /上课代码/02-lldb使用Python/
0000000000000000 - 00 0000 SO test.m
00000000603c8425 - 03 0001 OSO /上课代码/02-lldb使用Python/test.o
0000000100003f50 - 01 0000 BNSYM
0000000100003f50 - 01 0000 FUN _test
0000000000000010 - 00 0000 FUN
0000000000000010 - 01 0000 ENSYM
0000000100003f60 - 01 0000 BNSYM
0000000100003f60 - 01 0000 FUN _test_1
0000000000000010 - 00 0000 FUN
0000000000000010 - 01 0000 ENSYM
...
上面nm -pa
命令后面路径是通过image list
获取的,现在用更好的方式获取路径?
可以提前执行image list
,把它输出的语句转换成数组,取出数组第一条数据即可。或者指定数组中的某一个可执行文件路径进行符号查看
请思考怎么在一个函数中获取路径,再通过nm命令打印出可执行文件的符号信息?下去自己探讨?
- 通过文件的方式查看可执行文件信息(只是把上面自定义lldb命令写在文件内,导入文件的形式查看)
创建nm.py文件,修改内容如下
import lldb
# 执行命令的具体函数
def nm(debugger, command, result, internal_dict):
cmd = "platform shell nm {}".format(command)
debugger.HandleCommand(cmd)
# 作为一个模块导入来使用,相当于文件入口,类似lldb插件中 PluginInitialize
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f nm.nm nm -h "nm command ----12312312"') //nm.nm表示nm模块下的nm方法,后面nm表示命令
(lldb) command script import /上课代码/02-lldb使用Python/代码/nm.py
(lldb) help nm
nm command ----12312312 Expects 'raw' input (see 'help raw-input'.)
(lldb) nm /上课代码/02-lldb使用Python/代码/test
0000000000000000 - 00 0000 SO /上课代码/02-lldb使用Python/
0000000000000000 - 00 0000 SO test.m
00000000603c8425 - 03 0001 OSO /上课代码/02-lldb使用Python/test.o
0000000100003f50 - 01 0000 BNSYM
0000000100003f50 - 01 0000 FUN _test
...
- 使用Python编写我们的lldb插件,类似自定义lldb插件
// 创建cmd.py文件,内容如下
import lldb
import os //os系统模块提供getcwd()
#创建debugger
debugger = lldb.SBDebugger.Create()
debugger.SetAsync(False)
#设置一个可执行文件路径
exe = "/上课代码/03-实现一个lldb plugin/test"
#创建target
target = debugger.CreateTargetWithFileAndArch(exe, "x86_64")
if target:
#target设置断点
main_bp = target.BreakpointCreateByName("main")
# 创建一个进程,后面进程都在上面断点执行。os表示Python系统模块,这个系统模块提供了getcwd目录
process = target.LaunchSimple(None, None, os.getcwd())
if process:
thread = process.GetThreadAtIndex(0)
if thread:
print(thread)
frame = thread.GetFrameAtIndex(0)
if frame:
function = frame.GetFunction()
if function:
print(function)
#获取当前汇编指令,并且for循环打印指令
ins = function.GetInstructions(target)
for i in ins:
#pass
print(f"指令:{i}")
//运行上面Python文件,成功打印出汇编指令
SBFunction: id = 0x100000079, name = main, type = main
指令: test[0x100003f70]: pushq %rbp
...
上面证明我们的测试工程是OK的,下面开始测试我们定义的lldb命令查看可执行文件信息
// cmd.py文件内容如下
import lldb
import os //os系统模块提供getcwd()
import subprocess //进程处理相关
#创建debugger
debugger = lldb.SBDebugger.Create()
debugger.SetAsync(False)
#设置一个可执行文件路径
exe = "/上课代码/03-实现一个lldb plugin/test"
#创建target
target = debugger.CreateTargetWithFileAndArch(exe, "x86_64")
#获取当前Python命令解释器
interpreter = debugger.GetCommandInterpreter()
#拼接cmd.py文件路径
cmd = f"command script import '{os.getcwd()}/nm.py'"
result = lldb.SBCommandReturnObject()
interpreter.HandleCommand(cmd, result)
interpreter.HandleCommand(f"nm '{exe}'", result)
print(f"GetOutPut---{result.GetOutput()}---Error:{result.GetError()}")
// nm.py文件内容如下
import lldb
import os
import subprocess
# 执行命令的具体函数
def nm(_, command, result, internal_dict):
print(f"nm----{command}")
print("{}".format(subprocess.getoutput("nm {}".format(command))))
# __lldb_init_module
def __lldb_init_module(debu, internal_dict):
debu.HandleCommand('command script add -f nm.nm nm -h "nm command ----12312312"')
// 以cmd.py文件作为主文件运行,成功打印可执行文件符号信息
__main__
nm----'/上课代码/03-实现一个lldb plugin/test'
0000000100000000 T __mh_execute_header
0000000100000000 0 _global
...
网友评论