46

I'm taking over the development of an iPad app for a client. There's a substantial amount of work that's already been done and I'm trying to piece together how the whole thing is designed to run.

One of the things I'd like to do is log which methods get called when the app runs. I've seen a custom DTrace script that's meant to log all methods from startup, but when I run it in Instruments I get no results.

What's the best way of logging the methods?

Adam Wright
  • 48,938
  • 12
  • 131
  • 152
Phil John
  • 1,225
  • 1
  • 15
  • 24
  • 1
    Was the DTrace script the one that I describe in my answer [here](http://stackoverflow.com/questions/3790772/how-do-i-see-every-method-called-when-i-run-my-application-in-the-iphone-simulato/3791935#3791935)? If so, that will only work when run against the Simulator. Also, it only tracks methods called within `-applicationDidFinishLaunching:`, so if you use the newer `-application:didFinishLaunchingWithOptions:`, it won't show anything. You can edit the script to reflect this newer method, or simply remove the condition to have it log everything. – Brad Larson Sep 01 '11 at 17:32
  • Hi Brad. Yes it was the code from your blog. I made the changes but without success, but that's simply down to my lack of experience with DTrace. – Phil John Sep 02 '11 at 12:05
  • Maybe something has changed in how the script is handled. I'll take a look at it. – Brad Larson Sep 02 '11 at 14:44

8 Answers8

57

Inspired by tc's answer to a similar question here, I put together a debug breakpoint action that will log out the class and method name for every time objc_msgSend() is triggered in your application. This works similarly to the DTrace script I described in this answer.

To enable this breakpoint action, create a new symbolic breakpoint (in Xcode 4, go to the breakpoint navigator and create a new symbolic breakpoint using the plus at the bottom left of the window). Have the symbol be objc_msgSend, set it to automatically continue after evaluating actions, and set the action to be a debugger command using the following:

printf "[%s %s]\n", (char *)object_getClassName(*(long*)($esp+4)),*(long *)($esp+8)

Your breakpoint should look something like the following:

Breakpoint action

This should log out messages like this when run against your application:

[UIApplication sharedApplication]
[UIApplication _isClassic]
[NSCFString getCString:maxLength:encoding:]
[UIApplication class]
[SLSMoleculeAppDelegate isSubclassOfClass:]
[SLSMoleculeAppDelegate initialize]

If you're wondering where I pulled the memory addresses, read this Phrack article on the Objective-C runtime internals. The memory addresses above will only work against the Simulator, so you might need to tweak this to run against applications on the iOS devices. Collin suggests the following modification in his answer to run this on a device:

printf "[%s %s]\n", (char *)object_getClassName($r0),$r1

Also, I think you'll see that logging out every method called in your application will overwhelm you with information. You might be able to use some conditions to filter this, but I don't know if this will help you to learn how your code executes.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • 2
    Great idea, but as you say a bit overwhelming when you run it. – Phil John Sep 02 '11 at 12:06
  • 5
    trying this in xcode 4 and got this "error: 'printf' is not a valid command." – ramo May 23 '14 at 17:54
  • @ramo - That might be due to the switch from GDB to LLDB. You could drop back to GDB or find the equivalent for LLDB (if it isn't printf). – Brad Larson May 23 '14 at 17:56
  • 1
    this one works expr -- (void)printf("[%s, %s]\n",(char *) object_getClassName(*(long*)($esp+4)), (char *) *(long *)($esp+8) ) and it started logging a never ending list, when the application start? – ramo May 23 '14 at 18:09
  • 2
    Is there any way to avoid ssytem calls, in the condition of breakpoint setting? – ramo May 23 '14 at 18:13
  • You can't use breakpoints in Instruments however, and if your app is only failing in Instruments (Xcode 6) then you need a method that actually executes in runtime. – Awesome-o Sep 24 '14 at 23:42
  • In LLDB you'll see to prefix with expr, e.g. "expr printf..." – Graham Perks Apr 21 '15 at 22:27
  • 1
    This worked for me on **simulator** (Xcode 8): `expr -- (void)printf("[%s %s]\n",(char *) object_getClassName(*(long*)($rdi)), (char *)($rsi))` – staticVoidMan Dec 22 '16 at 09:51
  • This worked for me on **device** (Xcode 8): `expr -- (void)printf("[%s, %s]\n",(char *) object_getClassName(*(long*)($esp+4)), (char *) *(long *)($esp+8))` – staticVoidMan Dec 22 '16 at 09:51
  • does anybody get this messge? "Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion." – Thilina Chamath Hewagama Nov 18 '21 at 08:56
19

If you are using LLDB, you will need to use the following debugger commands. These were tested in Xcode 4.6.

Device:

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($r0),$r1)

Simulator:

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName(*(long*)($esp+4)), *(long *)($esp+8))
warhammerkid
  • 955
  • 5
  • 6
  • 2
    This fails in XCode 6.1 with the following message: `The process has been returned to the state before expression evaluation. error: Execution was interrupted, reason: Attempted to dereference an invalid pointer.` Any idea how to modify the statement so that it will work? – shmim Feb 17 '15 at 19:56
  • 4
    On ARM64 this needs to be `expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($x0),$x1)`. – bcattle Dec 07 '15 at 23:44
  • @bcattle The first argument needs to be an object. This works for me now: `expr -- (void)printf("[%s %s]\n", (char *)object_getClassName(@($x0)),$x1)` – William GP Aug 19 '20 at 11:14
8

To trace the app code under Xcode 6 on device, I had to use the following debugger expression.

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($arg1),$arg2)
Zmicier Zaleznicenka
  • 1,894
  • 1
  • 19
  • 22
5

Brad Larson's approach can be adapted to run on the device by using the debugger command:

printf "[%s %s]\n", (char *)object_getClassName($r0),$r1

More information can be found in the Technical Note here: technotes

NANNAV
  • 4,875
  • 4
  • 32
  • 50
Collin
  • 208
  • 4
  • 8
4

later xcode versions you need to call like that

expr -- (void)printf("[%s, %s]\n",(char *) object_getClassName(*(long*)($esp+4)), (char *) *(long *)($esp+8) )
Ali Kıran
  • 1,006
  • 7
  • 12
  • 2
    It's just throwing "error: use of undeclared identifier '$esp' error: 1 errors parsing expression" over and over again now – Ethan Mar 15 '13 at 16:04
  • 1
    that's because $esp is an x86 register which only exists in the simulator --> this command will only work in simulator. – jimpic Dec 09 '13 at 14:04
  • 2
    This fails in XCode 6.1 with the following message: `The process has been returned to the state before expression evaluation. error: Execution was interrupted, reason: Attempted to dereference an invalid pointer.` Any idea how to modify the statement so that it will work? – shmim Feb 17 '15 at 19:49
4

If you want to limit the output to just the messages sent to one class you can add a condition like this

(int)strcmp((char*)object_getClassName($r0), "NSString")==0

Jeff
  • 6,646
  • 5
  • 27
  • 33
2

If you want to log methods in the Simulator on 64 bit, use the following command instead:

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($rdi), (char *) $rsi)

Or if that doesn't work, log it this way:

enter image description here

The main idea is to use $rdi for the object (self), and $rsi for the selector.

BitParser
  • 3,748
  • 26
  • 42
1

A fellow developer taught me to add the same two log statements to each method. One as the first line, the other as the last line. I think he has a script that does this automatically for his projects, but the result is:

NSLog(@"<<< Entering %s >>>", __PRETTY_FUNCTION__);
NSLog(@"<<< Leaving %s >>>", __PRETTY_FUNCTION__);

At the console, this will spit out something like:

 <<< Entering -[MainListTableViewController viewDidLoad] >>>

Very helpful in tracking what is going on.

DenVog
  • 4,226
  • 3
  • 43
  • 72
  • 4
    I would use [Marcus Zarra's `DLog` statement](http://www.cimgf.com/2010/05/02/my-current-prefix-pch-file/) for this to avoid a zillion log statements when running the Release build. Or, better yet, make a version that uses a different `#ifdef` so that you can turn method logging on and off independently of Debug or Release build. – SSteve Sep 01 '11 at 16:45
  • This assumes there are no returns inside the method, triggering early exits out of that method. In such cases, it looks like the program is entering methods and never leaving. – mahboudz Oct 27 '17 at 20:19