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?
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:
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.
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))
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)
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
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) )
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
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:
The main idea is to use $rdi for the object (self), and $rsi for the selector.
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.
Related
I have read this post: what happens to NSLog info when running on a device?
...but wondering if NSLog is a problem when distributing the app such as filling up the memory or something? I am only interested to see it when i test the consistency of my input data to the database.
The reason is that I have NSLog to control when i load the data into my database in the simulator. I could remove it when i upload but it would be good if i do not need to?
You should remove it. For example if you log contents of a UITableViewCell (in -tableView:cellForRowAtIndexPath:), it can make a big difference in performance, especially on slower hardware.
Use a macro to keep NSLog output in Debug mode, but remove it from Release mode. An example can be found on the following site: http://iphoneincubator.com/blog/debugging/the-evolution-of-a-replacement-for-nslog
I use a set of macros in my pch file that are quite handy for this.
See http://www.cimgf.com/2010/05/02/my-current-prefix-pch-file/ for details.
#ifdef DEBUG
#define DLog(...) NSLog(#"%s %#", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#define ALog(...) [[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithCString:__PRETTY_FUNCTION__ encoding:NSUTF8StringEncoding] file:[NSString stringWithCString:__FILE__ encoding:NSUTF8StringEncoding] lineNumber:__LINE__ description:__VA_ARGS__]
#else
#define DLog(...) do { } while (0)
#ifndef NS_BLOCK_ASSERTIONS
#define NS_BLOCK_ASSERTIONS
#endif
#define ALog(...) NSLog(#"%s %#", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#endif
#define ZAssert(condition, ...) do { if (!(condition)) { ALog(__VA_ARGS__); }} while(0)
The log on the device only keeps around an hour of data, even Apple's apps log quite a few things there. So it is generally acceptable to have meaningful output in the log.
But since logging is a disk operation you might find that excessive logging slows down your app since the writing blocks the main (= UI) thread for a short while for every NSLog.
Because of this you should only log things that give more information if errors are happening in your app to facilitate finding what is going wrong.
Consider switching to cocoalumberjack, it is a drop-in replacement for NSLog and has significantly more useful functionality and better performance.
It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for objective-c, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the objective-c runtime.
There only real reason to remove your NSLog entries is to save on memory consumption by running unnecessary code, but unless you're consistenly adding data, it shouldn't be too much of an issue.
Furthermore, if a user has an issue such as app crash, etc. NSlogs can be submitted which the dev can read to work out the reason for the crash. If you're loading the NSlog with a great deal of unnecessary data, it can be troublesome at a later date if you need to go through said log to find a user's issue in order to fix the issue.
Ultimately, I wouldn't worry too much about removing them. That's my 2 cents anyhow.
When I write any new code I generally add two custom macros like this:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NOT_IMPLEMENTED();
NOT_TESTED();
}
Where these macros are just something simple like:
#define NOT_IMPLEMENTED() do{ __asm int 3 } while(0)
I use this for two reasons: 1) I can grep my source for functions I haven't implemented or stepped through in the debugger and 2) If I hit one of these conditions it just halts the debugger at that point rather than throwing an exception like NSAssert does. This way I can look at the callstack and proceed with execution from the point of where the int 3 was hit (P.S. 'int 3' only works on intel processors).
Now here's the rub. For things like 'didFailWithError' there's almost no documentation on this message can be sent. Ideally, I'd like to build a dummy webservice that knows how to trigger this condition so I know that I'm handling this type of condition. This is just an example. If you've ever worked with the in-app purchase API there's equally nebulous error delegates that give no indication of when they might be called.
I guess what I'm asking is what do other developers do to test error delegates that are available in many of the iOS libraries? Especially for the ones that give no indication on how they can be triggered.
As a newer programmer, I've discovered the magic of NSlog, and use it all through my code. It's been extremely helpful (along with NSZombieEnabled) in debugging.
I can see a definite performance hit on the simulator as it prints out all this stuff. I don't think I see any such hit on devices, but I'm not sure.
So does it cost anything to leave all the NSLogs in? Is it using more memory on the device? Or does the compiler just ignore them, like it does comments when I compile for a device?
EDIT:
Here's what I implemented, per the suggestion from rano.
In my App_Prefix.pch file, I added:
// DLog is almost a drop-in replacement for NSLog
// DLog();
// DLog(#"here");
// DLog(#"value: %d", x);
// Unfortunately this doesn't work DLog(aStringVariable); you have to do this instead DLog(#"%#", aStringVariable);
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((#"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif
// ALog always displays output regardless of the DEBUG setting
#define ALog(fmt, ...) NSLog((#"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
And then in my Project Info inspector, for the Debug configuration, under the heading GCC 4.2 - Preprocessing, I added the value DEBUG to the top entry called, Preprocessor Macros.
Works like a charm - DLog outputs when I build a Debug version and ALog always outputs.
NSLog surely is more pricy than a simple printf but is a little more integrated into the objective-C and Cocoa framework.
By the way when you are seriously programming you will notice that it is unsufficient to most of your needs. Take a look at this article and its references to have an idea on how to replace it in a smart way.
In that way you can also have the compiler to ignore it when it is no more useful (e.g. when you will release your piece of code). In general you could #ifdef the calling to a log function in computing intensive loops/sequences of code.
NSLog is not implemented as a macro, so there will definitely be a cost. Quantifying it and deciding if it is important is harder. The only sure way to do that is to measure your own application.
The answers above are outdated. You should simply use CocoaLumberjack is vastly superior to NSLog and provides much more functionality then the above answers.
I'm debugging a heavily assert()'ed iPhone app (Xcode, Objective-C++, and device simulator). In some cases, the assert failure would just terminate the app, instead of breaking into the debugger as I'd expect.
I made a workaround by implementing my own kinda-assert to the effect of:
#define AssertLite(b) if(!(b)) {asm {int 3}}
(fluff omitted), but I wonder if anyone ever encountered this. I could not determine a pattern as to when does it break and when does it terminate. The code is not threaded; all it does is done in event handlers.
Why does this happen and how do I make vanilla assert() behave like a conditional breakpoint it should be?
First off, since you are working on an iPhone app, you should probably use NSAssert() instead of the vanilla BSD assert function.
e.g. NSAssert(the_object, #"NIL object encountered");
The NSAssert macro will throw an Objective-C exception (NSInternalInconsistencyException) if the assertion fails.
Since your goal is to break on the exception, the next step is to make the Xcode debugger break on Objective-C exceptions. This is probably a good thing to do anyway.
In the Breakpoints window (Run->Show->Breakpoints menu item), click where it says "Double-Click for Symbol" to enter the symbol -[NSException raise]
The last thing to be careful off is that NSAsserts do not compile out in a release build. That means that you have to either be prepared to handle the exception in your application, or you need to create your own macro that does compile out in release builds.
Here's the macro I use to compile out assertions in runtime code (note that I then use HMAssert in my code instead of NSAssert):
#ifdef DEBUG
# define HMAssert(A,B) NSAssert(A,B)
#else
# define HMAssert(A,B)
#endif
This requires a DEBUG preprocessor macro to be defined. Here's how to set that up:
Right-click on your project in Xcode. That will be the top item in the left panel where your projects files are listed
Select "Get Info" from the context menu that pops up.
Go to the "Build" tab.
Make sure the "Configuration" is set to "Debug".
Type DEBUG into the field next to "Preprocessor Macros" under "GCC 4.2 - Preprocessing".
First of all, if you "Add Exception Breakpoint..." in the Breakpoint Navigator (⌘6), the debugger will stop on NSAssert's failures, allowing you to look at the stack and understand what went wrong.
You should use the standard NSAssert. If you use it correctly, there is not a lot that you need to manually create -- everything Mike mention is similar to the default NSAssert implementation.
You should run you release configuration with NS_BLOCK_ASSERTIONS set in your precompiled headers (follow Mike's steps), to disable assertions. If you need more info on why to do so, check out: http://myok12.wordpress.com/2010/10/10/to-use-or-not-to-use-assertions/
In Xcode 4 and new iOS, NSAssert may actually take a variable list of parameters. This may be useful to log some values together with the assert. The compiling-out assert (see answer by Mike above) could be defined like this:
#ifdef DEBUG
# define DAssert(A, B, ...) NSAssert(A, B, ##__VA_ARGS__);
#else
# define DAssert(...);
#endif
Also, there is no longer Run → Show → Breakpoints menu item. See this post to set up Xcode 4 to break on an assert as defined above.
One time I saw a different behavior from the assert() calls once. It was caused by the compiler picking up different macro definitions at different portions of the build process.
Once the include paths were straightened out, they all worked the same.
I have a simple question about debugging on Xcode and GDB.
I often run into an error:
unrecognized selector sent to instance 0x1081ad0
which makes the program load into GDB. Is there an easy way to examine what instance is located in that memory from GDB?
po 0x1081ad0
po = Print Object.
You can even call methods, like
po [myArray objectAtIndex:0]
Note that it only works on objects, so
po 1
will crash your program.
Steven is correct — the gdb command po is a shortcut for print-object, which actually calls -debugDescription (not -description, as you might expect) on the object provided as an argument. In many cases you'll see the same result from both methods, since one calls the other unless overridden. (See the related Note: callout on this Apple technote for details. Note that in their code sample, po $r3 prints the contents of a PowerPC register, but you can use any object pointer/reference, including Intel registers, etc.)
Also, be aware that print-object will only work on valid objects that haven't been deallocated. It won't help at all if you're sending a message to a borked pointer. Given the error you cited, though, it would seem that it's a valid object instance, it just doesn't implement the method you're trying to invoke.
It's also remotely possible that the object has already been destroyed. This answer should help in that case.
Edit:
There are other ways to "examine" objects in the debugger. I asked this SO question about Xcode data formatters, which is one way you can determine how a custom class appears in the Summary column of the debugger. The documentation linked from that question explain how it works. I've found the summary approach to help a lot with seeing the state of an object.
There are a couple of things you can do.
You can insert a break point that will trigger every time you have an exception, so basically create a break point for this (go to breakpoints and create a new one): -[NSException raise]
Alternatively, you can actually see what the object at that mem location is:
info symbol 0x1081ad0 or
info line *0x1081ad0
There's more info at the cocoadev wiki entry for exceptionhandling and debugging tips for objective C at cocoawithlove.
Your instance is not valid. You have release the object somewhere else, but you did not clear out your pointer... enable Zombie detection.