Skip to content

Instantly share code, notes, and snippets.

@oozoofrog
Last active February 17, 2023 01:40
Show Gist options
  • Save oozoofrog/560e211a46867cf5e3617b33ce3a7b77 to your computer and use it in GitHub Desktop.
Save oozoofrog/560e211a46867cf5e3617b33ce3a7b77 to your computer and use it in GitHub Desktop.
try:
import lldb
except:
pass
import textwrap
import hashlib
from collections import namedtuple
object_register = ""
# autoreleasepool_on = 0
# hash는 instance type + source location 의 hash값
# {
# "address": "hash",
# ....
# }
autoreleased_address_info = {}
# {
# "hash": [
# {
# "type": "NSURL",
# "count": 12,
# "source_infos": [
# {
# "source_entry": "[location] name () -> Type",
# "source": "source"
# },
# {
# "source_entry": "[location2] name2 () -> Type",
# "source": "source2"
# }
# ]
# }
# ]
# }
autoreleased_type_source_info = {}
ainfo_called_frame = lldb.SBFrame()
TypeAddress = namedtuple("TypeAddress", ["type", "address"])
SourceInfo = namedtuple("SourceInfo", ["source_entry", "source"])
def ainfo(debugger, command, result, internal_dict):
reset()
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
global autoreleasepool_on
global autoreleased_address_info
global autoreleased_type_source_info
autoreleasepool_on = 0
autoreleased_address_info = {}
autoreleased_type_source_info = {}
update_object_register(target)
print_thread_info(thread)
thread_id = thread.id
# thread_index = thread.idx
# queue = thread.queue
# queue_id = thread.queue_id
breakpoint_anchor_option = f' -t {thread_id}'
global ainfo_called_frame
ainfo_called_frame = frame
print(f"called from {ainfo_called_frame.addr}")
print("breakpoint for objc_autorelease function")
debugger.HandleCommand(f'rb ^objc_autorelease$ {breakpoint_anchor_option} -N abr_autorelease -G true')
print("breakpoint for objc_release function")
debugger.HandleCommand(f'rb ^objc_release$ {breakpoint_anchor_option} -N abr_release -G true')
# print("breakpoint for objc_autoreleasePoolPush function")
# debugger.HandleCommand(f'rb objc_autoreleasePoolPush$ {breakpoint_anchor_option} -N abr_autoreleasepool_start -G true')
# print("breakpoint for objc_autoreleasePoolPop function")
# debugger.HandleCommand(f'rb objc_autoreleasePoolPop$ {breakpoint_anchor_option} -N abr_autoreleasepool_finish -G true')
debugger.HandleCommand('breakpoint command add -F lldb_autorelease.abr_autorelease abr_autorelease')
debugger.HandleCommand('breakpoint command add -F lldb_autorelease.abr_release abr_release')
# debugger.HandleCommand('breakpoint command add -F lldb_autorelease.abr_autoreleasepool_start abr_autoreleasepool_start')
# debugger.HandleCommand('breakpoint command add -F lldb_autorelease.abr_autoreleasepool_finish abr_autoreleasepool_finish')
def ainfo_finish(debugger, command, result, internal_dict):
global autoreleased_address_info
global autoreleased_type_source_info
print(f"{len(autoreleased_address_info)} instances is autoreleased")
for hash in autoreleased_type_source_info:
autoreleased_info = autoreleased_type_source_info[hash]
type = autoreleased_info["type"]
count = autoreleased_info["count"]
source_infos = autoreleased_info["source_infos"]
print(f'{type} is {count} remain{"s" if count > 0 else ""}')
print()
source_prefix = f'call chain{"s" if len(source_infos) > 1 else ""}: '
source_prefix_space = " " * len(source_prefix)
indent_count = 0
for source_info in reversed(source_infos):
source_entry = source_info.source_entry
source = source_info.source
description = ""
indent = source_prefix if indent_count == 0 else source_prefix_space
if indent_count > 0:
indent += "> "
if source is None:
description = f"{indent}{source_entry}"
else:
description = f"{indent}{source_entry}\n\n{source}"
print(textwrap.indent(description, " " * indent_count))
print()
indent_count += 4
print()
reset()
def reset():
global autoreleasepool_on
global autoreleased_address_info
global autoreleased_type_source_info
debugger = lldb.debugger
target = debugger.GetSelectedTarget()
def delete_match_names(target, br, names):
for name in names:
if br.MatchesName(name):
target.BreakpointDelete(br.id)
for br in target.breakpoint_iter():
delete_match_names(target, br, ["abr_autorelease", "abr_release"])
autoreleasepool_on = 0
autoreleased_address_info = {}
autoreleased_type_source_info = {}
# def is_in_autoreleasepoolpage():
# global autoreleasepool_on
# if autoreleasepool_on > 0:
# return True
# else:
# return False
def update_object_register(target):
global object_register
triple = target.GetTriple()
print(triple)
if triple.startswith('arm64'):
object_register = 'x0'
elif triple.startswith('x86_64'):
object_register = "rdi"
else:
object_register = ''
def print_thread_info(thread):
thread_id = thread.id
thread_index = thread.idx
queue = thread.queue
queue_id = thread.queue_id
print(f"in Thread {thread_index}:{thread_id}, queue {queue}:{queue_id}")
def abr_autorelease(frame, bp_loc, internal_dict):
# if is_in_autoreleasepoolpage is True:
# return
register = frame.FindRegister(object_register)
if register.value == "0x0000000000000000":
return
global autoreleased_address_info
global autoreleased_type_source_info
typeAddress = get_type_address(frame, register.value)
address = typeAddress.address
typename = typeAddress.type
frames = frame.thread.frames[1:]
source_infos, source_entries = get_source_infos(frames, ainfo_called_frame)
hash = hash_from(typename, source_entries)
autoreleased_address_info[address] = hash
if hash in autoreleased_type_source_info:
autoreleased_info = autoreleased_type_source_info[hash]
autoreleased_info["count"] += 1
else:
autoreleased_info = {}
autoreleased_info["type"] = typename
autoreleased_info["count"] = 1
autoreleased_info["source_infos"] = source_infos
autoreleased_type_source_info[hash] = autoreleased_info
def abr_release(frame, bp_loc, internal_dict):
# if is_in_autoreleasepoolpage is True:
# return
global autoreleased_address_info
global autoreleased_type_source_info
register = frame.FindRegister(object_register)
address = register.value
del_address_list = []
del_type_source_list = []
if address in autoreleased_address_info:
del_address_list.append(address)
hash = autoreleased_address_info[address]
if hash in autoreleased_type_source_info:
autoreleased_info = autoreleased_type_source_info[hash]
count = autoreleased_info["count"] - 1
if count < 1:
del_type_source_list.append(hash)
for address in del_address_list:
del autoreleased_address_info[address]
for hash in del_type_source_list:
del autoreleased_type_source_info[hash]
def hash_from(typename, source_locations):
key = typename + "".join(source_locations)
return hashlib.sha256(key.encode('utf-8')).hexdigest()
# def abr_autoreleasepool_start(frame, bp_loc, internal_dict):
# global autoreleasepool_on
# autoreleasepool_on += 1
# def abr_autoreleasepool_finish(frame, bp_loc, internal_dict):
# global autoreleasepool_on
# if autoreleasepool_on == 0:
# return
# autoreleasepool_on -= 1
# return SBValue
def get_type_address(frame, addr):
options = lldb.SBExpressionOptions()
options.SetIgnoreBreakpoints()
options.SetLanguage(lldb.eLanguageTypeObjC)
options.SetCoerceResultToId(True)
value = frame.EvaluateExpression(f"(id){addr}")
return TypeAddress(value.GetDisplayTypeName(), addr)
def get_source_infos(frames, end_frame):
source_infos = []
source_entries = []
for frame in frames:
sourceInfo = extract_source_info(frame)
if sourceInfo is not None:
source_entry = sourceInfo.source_entry
source = sourceInfo.source
source_entries.append(source_entry)
source_infos.append(sourceInfo)
if frame.addr == end_frame.addr:
break
return source_infos, source_entries
def extract_source_info(frame):
line_entry = frame.GetLineEntry()
if line_entry.IsValid():
src_mgr = lldb.debugger.GetSourceManager()
line = line_entry.line
column = line_entry.column
spec = line_entry.GetFileSpec()
stream = lldb.SBStream()
src_mgr.DisplaySourceLinesWithLineNumbersAndColumn(spec, line, column, 0, 0, '->', stream)
source = stream.GetData().strip()
return SourceInfo(f'{frame.name} [{spec.fullpath} {line}:{column}]', f'{source}')
else:
return SourceInfo(frame.name, None)
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f lldb_autorelease.ainfo ainfo')
debugger.HandleCommand('command script add -f lldb_autorelease.ainfo_finish ainfo_finish')
print(f"ainfo registered ver 23.02.17-10:11")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment