How to set a breakpoint on “objectAtIndex:” method

2020-07-06 03:33发布

问题:

I would like to set a symbolic breakpoint on "objectAtIndex:" method of a specific property in a specific class.

See the following code :

@interface Foo
...
@property (strong,nonatomic) NSMutableArray *fooArray;
...
@end

I 've tried the following things:

  • -[[Foo fooArray] objectAtIndex:]
  • -[Foo::fooArray objectAtIndex:]
  • -[Foo::fooArray objectAtIndex]
  • Foo::fooArray::objectAtIndex:
  • Foo::fooArray::objectAtIndex
  • Foo::fooArray::objectAtIndex()

None of theses solutions work.

Any ideas to do the trick ?

回答1:

Unfortunately, while this would be useful, it cannot work, for multiple reasons.

The first involves how methods are specified. A method signature for identifying a method in a breakpoint has three parts:

-¹[NSDictionary² objectForKey:³]
+¹[NSString² stringWithContentsOfURL:encoding:error:³]
  1. Is this an instance method (-) or a class method (+)?
  2. Which class's implementation of this method?
  3. What's the selector of this method?

So your first problem is that what you have written for #2 is not a class.

What you do have is a class, followed in some fashion by a property name. This cannot work, because the debugger has no way to know whether that is a pure accessor—it cannot be sure that you, or whoever implemented that property, didn't write a custom accessor that does something else. This means that the debugger has no good, reliable way to obtain that value, or to know when that value changes, without potentially incurring side effects.

Moreover, the role of a class in a method signature is to identify which class provides the implementation you're setting a breakpoint on. That goes out the window as soon as you start trying to refer to a property that holds an object instead, because the debugger needs a class, and will have to get it from the object—and see the previous paragraph for some of the difficulties of knowing which object that is at all times.

(To be fair, it would indeed be possible for the debugger to watch the value of an instance variable—IIRC, both debuggers can already do this in a watchpoint, though reliability of watchpoints was flaky the last time I tried one. If the debugger could translate the property into its backing ivar, if it has one, and watch that, it would be a decent 90% solution for the majority of properties, which aren't backed by imaginative storage implementations and custom accessors. But the debuggers cannot do this today.)

The second reason is that NSArray is a class cluster.

You probably already know the first part of this (I suspect it's why you're trying to specify a single object by a property of another):

NSArray and NSMutableArray are both abstract classes, which in turn means that neither one implements the business of being an array; each one implements a bunch of convenience methods, while leaving a select set of core methods unimplemented, for subclasses to implement.

So, when you create an NSArray, you do not create an NSArray. The object you get back will be an instance of some private subclass of NSArray, with its own implementation of all of the details of how it manages an ordered list of objects.

So you could set a breakpoint on, say, -[NSArray objectAtIndex:], but it would never get hit, because nothing uses NSArray's implementation of objectAtIndex:—it would not make sense to use that implementation, because that implementation raises an exception (intended to catch subclasses that forget to implement it).

The part that breaks your question is:

While NSArray's implementations of various non-essential methods are defined ultimately in terms of the core methods, such as objectAtIndex:, that does not mean that subclasses are bound to use those implementations. A subclass could very well have its own implementations that don't use objectAtIndex:, if objectAtIndex: is not the most efficient way to do what they do (e.g., if the array is backed by a linked list rather than a C array).

So, to summarize this long answer:

  • It is not possible for the debugger to reliably watch the value of a property.
  • As such, it is not possible for the debugger to break when a method in the class of the object that is the value of that property is called, because the correct method to set the breakpoint on may change at any time, and the debugger cannot know when that happens.
  • Even if you could break on objectAtIndex: of some object identified by property, the array may validly never use objectAtIndex:, in which case your breakpoint would never get hit anyway.

You probably should ask another question about whatever you're trying to do by breaking on objectAtIndex:. I assume you're trying to investigate a bug in your app; that bug is probably another interesting question.



回答2:

After some digging, I found a way to work this out. That's kinda ugly.

It involves creating a conditional breakpoint dynamically, in a command triggered by a first breakpoint.

First, break whenever your fooArray is ready. I settled on the fooArray accessor, but it could be done earlier :

breakpoint set --name "-[Foo fooArray]"

Then, what you want is break when objectAtIndex: is called on this specific array object. First let's put its pointer in a variable :

expr id $watch = self->_fooArray

and then create a new breakpoint, using this variable in the condition :

breakpoint set --name "-[__NSArrayI objectAtIndex:]" --condition "$rdi == $watch"

  • $rdi contains self, at least on x86_64. Use $r0 on ARM. (See Clark Cox's great post on the topic.)
  • -[NSArray objectAtIndex:] is never called. As Peter mentioned, NSArray is a class cluster, and your array is actually an __NSArrayI.

Or, in Xcode :

(Don't forget to check the "continue" box.)

It's not really beautiful, but it seems to work !



回答3:

I am not at my Mac, so I cannot try this myself, but how about:

breakpoint set -n "-[[Foo fooArray] objectAtIndex:]"