Jeremy W. Sherman

stay a while, and listen

Using debugDescription with GCD and XPC objects

dispatch_debug and xpc_copy_description are inconvenient, particularly during impromptu debugging.

Mountain Lion’s Obj-C-ification of GCD and XPC objects lets you use your comfortable Obj-C tools:

  • NSLog with %@,
  • the debugDescription method, and
  • po obj while in the debugger.

dispatch_debug

If you first learned GCD back before Mountain Lion, you might have played around with the dispatch_debug function:

void
dispatch_debug(dispatch_object_t object, const char *message, ...);

This function is the NSLog of Grand Central Dispatch land. If you need to pin down what exactly is going on with a complex network of dispatch objects, this can be a useful tool, especially since you can use the libdispatch source code to illuminate the more cryptic debug info.

But it’s also kind of annoying: unless you remembered to set LIBDISPATCH_LOG=stderr in the environment before starting your process, you’ll have to watch the system log for your dispatch_debug output; it won’t show up in Xcode’s debug console.

Changing the value of the environment variable after startup also doesn’t seem to affect dispatch_debug’s behavior, so by the time you realize you’ve forgotten to set this environment variable, it’s already too late.

xpc_copy_description

If you wanted to log information about an XPC object without leaking, you used to have to xpc_copy_description, log the string, then free the returned pointer when you’re done with it:

char *desc = xpc_copy_description(obj);
NSLog(@"%s: xpc obj %p %s", __func__, obj, desc);
free(desc);

debugDescription

Well, good news: As of Mountain Lion, GCD and XPC objects are all also NSObjects, so you can use them as the target for the %@ format specifier and as the target for the -debugDescription instance method. The latter dumps all the information you used to get from dispatch_debug.

As an example:

2013-01-08 23:53:13.451 debug[80089:707] dispatch queue: description:
<OS_dispatch_queue: com.jeremywsherman.demo[0x7f8980c07f80]>
2013-01-08 23:53:13.453 debug[80089:707] dispatch queue: debugDescription:
<OS_dispatch_queue: com.jeremywsherman.demo[0x7f8980c07f80] = {
    xrefcnt = 0x2, refcnt = 0x1, suspend_cnt = 0x0, locked = 0,
    target = com.apple.root.default-priority[0x7fff72c47d00],
    width = 0x7fffffff, running = 0x0, barrier = 0 }>

XPC objects are pretty verbose even with description, but you get a bit – sometimes quite a bit – more info if you send debugDescription:

2013-01-08 23:53:13.453 debug[80089:707] xpc connection: description:
<OS_xpc_connection: <connection: 0x7f8980e017f0> {
    name = com.jeremywsherman.conn, listener = false,
    PID = 0, EUID = 4294967295,
    EGID = 4294967295, ASID = 4294967295 }>
2013-01-08 23:53:13.454 debug[80089:707] xpc connection: debugDescription:
<OS_xpc_connection: connection[0x7f8980e017f0]: {
    refcnt = 1, xrefcnt = 2,
    name = com.jeremywsherman.conn, type = named, state = new,
    queue = 0x7f8980e00420->0x0, error = 0x0, mach = false,
    privileged = false, bssendp = 0x0, recvp = 0x0, sendp = 0x0,
    pid/euid/egid/asid = 0/4294967295/4294967295/4294967295 }
    <connection: 0x7f8980e017f0> {
    name = com.jeremywsherman.conn, listener = false,
    PID = 0, EUID = 4294967295, EGID = 4294967295, ASID = 4294967295 }>

2013-01-08 23:53:13.454 debug[80089:707] xpc bool: description:
<OS_xpc_bool: <bool: 0x7fff7244d320>: true>
2013-01-08 23:53:13.455 debug[80089:707] xpc bool: debugDescription:
<OS_xpc_bool: bool[0x7fff7244d320]: {
    refcnt = 80000000, xrefcnt = 80000000, value = true }
    <bool: 0x7fff7244d320>: true>

The odd trailer to the XPC objects' debug descriptions is not a typo – the XPC objects really do include their regular description as a component of their debug description.

print-object (po)

debugDescription also happens to be what gets printed when you print-object (or po for short) an object while debugging.

Treating a GCD/XPC object as a regular Objective-C object is particularly handy during impromptu debugging, since you no longer need to futz about with dispatch_debug and xpc_copy_description.

Instead, just use po obj when debugging:

% lldb ./debug
(lldb) Current executable set to './debug' (x86_64).
b debug.m:34
breakpoint set --file 'debug.m' --line 34
Breakpoint created: 1: file ='debug.m', line = 34, locations = 1
(lldb) r
Process 80337 launched: '/Users/jeremy/Documents/Blog/GCDTips/debug' (x86_64)
Process 80337 stopped
* thread #1: tid = 0x1c03, 0x0000000100000db7 debug`main + 135
  at debug.m:34, stop reason = breakpoint 1.1
    frame #0: 0x0000000100000db7 debug`main + 135 at debug.m:34
   31           Log(@"xpc connection", conn);
   32
   33           xpc_object_t pred = xpc_bool_create(true);
-> 34           Log(@"xpc bool", pred);
   35       }
   36       return 0;
   37   }
(lldb) fr var
(dispatch_queue_t) q = 0x0000000100107fa0
(xpc_connection_t) conn = 0x0000000100400830
(xpc_object_t) pred = 0x00007fff7244d320
(lldb) po q
(dispatch_queue_t) $0 = 0x0000000100107fa0 <OS_dispatch_queue:
com.jeremywsherman.demo[0x100107fa0] = { xrefcnt = 0x1, refcnt = 0x2,
suspend_cnt = 0x0, locked = 0, target =
com.apple.root.default-priority[0x7fff72c47d00], width = 0x7fffffff,
running = 0x0, barrier = 0 }>
(lldb) po conn
(xpc_connection_t) $1 = 0x0000000100400830 <OS_xpc_connection:
connection[0x100400830]: { refcnt = 1, xrefcnt = 1,
name = com.jeremywsherman.conn, type = named, state = new,
queue = 0x100400530->0x0, error = 0x0, mach = false, privileged = false,
bssendp = 0x0, recvp = 0x0, sendp = 0x0,
pid/euid/egid/asid = 0/4294967295/4294967295/4294967295 }
<connection: 0x100400830> { name = com.jeremywsherman.conn,
listener = false, PID = 0, EUID = 4294967295, EGID = 4294967295,
ASID = 4294967295 }>
(lldb) po pred
(xpc_object_t) $2 = 0x00007fff7244d320 <OS_xpc_bool: bool[0x7fff7244d320]:
{ refcnt = 80000000, xrefcnt = 80000000, value = true } <bool:
0x7fff7244d320>: true>