Last active
February 17, 2023 01:40
-
-
Save oozoofrog/560e211a46867cf5e3617b33ce3a7b77 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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