Skip to content

Instantly share code, notes, and snippets.

@comiclandapp
Forked from mayoff/!README.md
Created June 8, 2018 20:10
Show Gist options
  • Save comiclandapp/a07150386c55b1552a9437973a63dc34 to your computer and use it in GitHub Desktop.
Save comiclandapp/a07150386c55b1552a9437973a63dc34 to your computer and use it in GitHub Desktop.
Debugging Objective-C blocks in lldb

The attached lldb command pblock command lets you peek inside an Objective-C block. It tries to tell you where to find the source code for the block, and the values captured by the block when it was created.

Consider this example program:

#import <Foundation/Foundation.h>

@interface Foo: NSObject
@end

@implementation Foo
@end

typedef void (^MyBlock)(int y);

MyBlock makeBlock(Foo *foo, int x) {
    return ^(int y){
        NSLog(@"foo=%@ x+y=%d\n", foo, x + y);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Foo *foo = [Foo new];
        MyBlock block = makeBlock(foo, 7);
        block(8);            // <------------ breakpoint here
    }
    return 0;
}

Suppose you put a breakpoint at the call to block, stopping before the block is called. Then in the debugger, you can use pblock to find out what's about to happen:

(lldb) pblock block
/Users/mayoff/TestProjects/blockTest2/blockTest2/main.m:12
(__block_literal_1) *$0 = {
  __isa = 0x00007fffa2380160
  __flags = -1023410172
  __reserved = 0
  __FuncPtr = 0x0000000100000dc0 (blockTest2`__makeBlock_block_invoke at main.m:12)
  __descriptor = 0x0000000100001060
  foo = 0x0000000100580c10
  x = 7
}

The pblock command prints (if available) the source file and line number where the block body was defined, and the contents of the block literal, if debug information for it is available. Everything following the __descriptor field of the block literal is a value captured by the block when it was created.

command script import ~/pblock.py
import lldb
def print_block(debugger, userInput, context, result, internalDict):
# debugger: SBDebugger
# userInput: str
# context: SBExecutionContext
# result: SBCommandReturnObject
target = context.target
addressOfBlockLiteral = context.frame.EvaluateExpression(userInput)
if addressOfBlockLiteral.error.fail:
result.SetError(addressOfBlockLiteral.error)
return
# If lldb thinks addressOfBlockLiteral is a block type, then GetValueAsUnsigned doesn't
# work. Cast to a void pointer first.
voidPointerType = target.FindFirstType('void').GetPointerType()
if not voidPointerType.IsValid():
result.PutCString('Unable to find void* type for casting')
return
addressOfBlockLiteral = addressOfBlockLiteral.Cast(voidPointerType)
if not addressOfBlockLiteral.IsValid():
result.PutCString('Unable to cast to void*')
return
# https://clang.llvm.org/docs/Block-ABI-Apple.html
# struct Block_literal_1 {
# void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
# int flags;
# int reserved;
# void (*invoke)(void *, ...);
addressOfBlockLiteralInvoke = addressOfBlockLiteral.unsigned + target.addr_size + 4 + 4
error = lldb.SBError()
blockLiteralInvoke = context.process.ReadPointerFromMemory(addressOfBlockLiteralInvoke, error)
if error.fail:
result.SetError(error)
return
addressOfInvokeFunction = lldb.SBAddress()
addressOfInvokeFunction.SetLoadAddress(blockLiteralInvoke, target)
symbolContext = addressOfInvokeFunction.GetSymbolContext(lldb.eSymbolContextEverything)
lineEntry = symbolContext.line_entry
if lineEntry.IsValid():
result.PutCString(str(lineEntry))
else:
symbolName = symbolContext.symbol.name
result.PutCString(symbolName)
invokeFunction = symbolContext.function
if not invokeFunction.IsValid():
result.PutCString("couldn't get block invoke function")
return
scope = invokeFunction.block
if not scope.IsValid():
result.PutCString("couldn't get top-level scope of invoke function")
return
scopeVariables = scope.GetVariables(target, True, False, False)
if not scopeVariables.IsValid():
result.PutCString("couldn't get top-level variables of invoke function")
return
blockDescriptorValue = scopeVariables.GetFirstValueByName('.block_descriptor')
if not blockDescriptorValue.IsValid():
result.PutCString("couldn't get .block_descriptor of invoke function")
return
blockDescriptorType = blockDescriptorValue.type
if not blockDescriptorType.IsValid():
result.PutCString("couldn't get type of .block_descriptor of invoke function")
return
typedBlockLiteralReference = addressOfBlockLiteral.Cast(blockDescriptorType)
if not typedBlockLiteralReference.IsValid():
result.PutCString("couldn't get typed reference to block literal")
return
typedBlockLiteral = typedBlockLiteralReference.deref
# XXX How to return this as a value that lldb will make available as e.g. $1
result.PutCString(str(typedBlockLiteral))
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f ' + __name__ + '.print_block pblock')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment