Продолжение моего предыдущего рассказа о перезаписи свойств только для чтения с помощью LLDB. На этот раз мы создадим точку останова, которая останавливается, когда стек вызовов соответствует строгому порядку и строгому подмножеству.

Stackpoint.py ‹- GIST!

import lldb
"""
NOTE:
LLDB print adds a bunch of other stuff to the string like in this example
(lldb) script
-- ENTERED PYTHON REPL
>>> print lldb.frame.GetDisplayFunctionName()
::-[ASLayoutTransition applySubnodeInsertionsAndMoves]()
"""

"""
REMEMBER: This is the FILO callstack. It should match the same visual order
that you see in the debugger.
"""
matchingCallStack = [
'-[_ASDisplayLayer layoutSublayers]'
]
initializedParams = False
def stop_matching_stack(debugger, command, result, internal_dict):
    global matchingCallStack
    global initializedParams
    if not initializedParams:
        debugger.HandleCommand('expr BOOL $matchedStack = 0x0')
    target = debugger.GetSelectedTarget()
    process = target.GetProcess()
    thread = process.GetSelectedThread()
    callStack = [frame.GetFunctionName() for frame in thread.frames]
    print callStack
    matchingCallStackCopy = list(matchingCallStack)
    matchedSignatures = 0
    for signature in callStack:
        found = False
        for requirement in matchingCallStackCopy:
            if requirement in signature:
                found = True
                break
        if found:
            matchingCallStackCopy = matchingCallStackCopy[1:]
            matchedSignatures = matchedSignatures + 1
    if matchedSignatures == len(matchingCallStack):
        print "Stack Matcher: Matched Stack!"
        debugger.HandleCommand('expr $matchedStack = 0x1')
        return True
    elif matchedSignatures > 0:
        print "Stack Matcher: Partial Match: " + str(matchedSignatures)
    print process
    debugger.HandleCommand('expr $matchedStack = 0x0')

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f stackpoint.stop_matching_stack stop_matching_stack')
    print 'The "stop_matching_stack" python command has been installed and is ready for use.'

Например, есть высокочастотная часть кода, такая как общий метод суперкласса, как в моем примере, использующий невероятно быструю текстуру фреймворка пользовательского интерфейса. В этом случае есть функция -(void)applySubnodeInsertionAndMoves, которая находится в конце нескольких разных точек вызова. Если бы мы попытались установить точку останова здесь, она бы пострадала от всех этих различных историй, и мы потеряли бы точность отладки конечного автомата, особенно в многопоточной ситуации.

Представляем Stackpoint.py, простой сценарий, в котором вы можете определить произвольный порядок элементов в стеке вызовов и, немного поработав вручную, остановить отладчик на соответствующем стеке. Алгоритм довольно прост, если у вас есть предложения по оптимизации, пишите в Gist!

ПРИМЕР

Перед запуском давайте загрузим скрипт при запуске сеанса LLDB, изменив ~/.lldbinit . Вставьте следующее в любую строку отдельной строки, где ~/stackpoint.py — это расположение stackpoint.py!

command script import ~/stackpoint.py

Итак, вот наша функция:

Очень простой. Мы хотим остановиться на строке 97, но только в том случае, если это было вызвано вызовом [CALayer layoutSublayers], а не вызовом [_ASDisplayLayer layoutSublayers].

Давайте перейдем к нашему скрипту stackpoint.py и поместим элемент в наш

matchingCallStack = [
'-[CALayer layoutSublayers]',
'earlier something in the call stack',
'main'
]

Алгоритм будет проходить и пытаться строго упорядочить, строгое подмножество соответствует сопоставлениюCallStack и стеку вызовов из текущего потока. Важно отметить, что этот сценарий записывает в глобальную переменную LLDB с именем $matchedStack, которую можно использовать для условных точек останова.

Итак, затем мы устанавливаем точку останова для запуска функции stackmatch и дочернюю точку останова, которая имеет чтение условия из $matchedStack, чтобы остановить выполнение. К вашему сведению, на снимке экрана показана символическая точка останова.

stop_matching_stack

Важно отметить, что родительская точка останова проверяет «Автоматически продолжить…», чтобы мы могли пропустить точку останова, если дочерняя точка останова не срабатывает.

И тогда мы можем настроить дочернюю точку останова.

И вуаля! Мы matcha да стек! 🥞

Каждый раз, когда вы хотите изменить стек, потому что, скажем, у вас что-то не так, вы всегда можете изменить список в самом скрипте, а затем запустить command source ~/.lldbinit из сеанса LLDB в XCode, чтобы повторно инициализировать команду lldb stop_matching_stack из скрипта python!