Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
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

@implementation Foo

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
(__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.

Thanks to Jim Ingham and John McCall at WWDC 2018 for lots of help making this work.

command script import ~/
import lldb
def print_block(debugger, userInput, context, result, internalDict):
# debugger: SBDebugger
# userInput: str
# context: SBExecutionContext
# result: SBCommandReturnObject
target =
addressOfBlockLiteral = context.frame.EvaluateExpression(userInput)
# 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')
addressOfBlockLiteral = addressOfBlockLiteral.Cast(voidPointerType)
if not addressOfBlockLiteral.IsValid():
result.PutCString('Unable to cast to void*')
# 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)
addressOfInvokeFunction = lldb.SBAddress()
addressOfInvokeFunction.SetLoadAddress(blockLiteralInvoke, target)
symbolContext = addressOfInvokeFunction.GetSymbolContext(lldb.eSymbolContextEverything)
lineEntry = symbolContext.line_entry
if lineEntry.IsValid():
symbolName =
invokeFunction = symbolContext.function
if not invokeFunction.IsValid():
result.PutCString("couldn't get block invoke function")
scope = invokeFunction.block
if not scope.IsValid():
result.PutCString("couldn't get top-level scope of invoke function")
scopeVariables = scope.GetVariables(target, True, False, False)
if not scopeVariables.IsValid():
result.PutCString("couldn't get top-level variables of invoke function")
blockDescriptorValue = scopeVariables.GetFirstValueByName('.block_descriptor')
if not blockDescriptorValue.IsValid():
result.PutCString("couldn't get .block_descriptor of invoke function")
blockDescriptorType = blockDescriptorValue.type
if not blockDescriptorType.IsValid():
result.PutCString("couldn't get type of .block_descriptor of invoke function")
typedBlockLiteralReference = addressOfBlockLiteral.Cast(blockDescriptorType)
if not typedBlockLiteralReference.IsValid():
result.PutCString("couldn't get typed reference to block literal")
typedBlockLiteral = typedBlockLiteralReference.deref
# XXX How to return this as a value that lldb will make available as e.g. $1
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
You can’t perform that action at this time.