CoreGraphics Log Jam

May 5th, 2006

It’s been a while since I’ve complained about Apple in the context of an actual bug report, so here is a little diversion back into “Apple Bug Friday” land.

One of the aspects of Mac OS X programming that can really frustrate traditional Mac OS programmers is the lack of error reporting from many APIs. While traditional Carbon APIs tend to return numeric failure codes when something goes wrong, it’s not unusual for CoreFoundation and related APIs to simply return NULL. What’s the developer to do in this case? The usual argument is that NULL is returned only from functions where a more specific error would not help the programmer convey meaningful information to the user. In other words, if it returns NULL when you don’t expect it to, your computer is either melting down, or you’ve got some debugging to do. Regardless of the validity of this argument, it can be infuriating to look at code that seems perfectly correct, and be clueless as to why it is failing.

I was chatting with Jonathan Wight when he shared his frustration with the CGBitmapContextCreate function. He was in the midst of debugging just one of these mind-boggling NULL returns. All the parameters looked more or less correct but the function just plain refused to cooperate.

I guess I was between builds or something because I got interested in the problem. While Jon took the more efficient path of carefully examining the parameters being passed and comparing their values to the documentation’s restrictions, I started traipsing through assembly in Xcode. You know, the fun stuff.

Of course, Jon found the answer first. It was a problem with the combination of colorspace and alpha parameters. The limitations are well-documented (if a bit overwhelming) in Apple’s Technical Q&A. In this case, Jon was fetching the values directly from a CGImage that had been loaded from disk. Can’t blame a guy for thinking that maybe a JPG image’s attributes might be suitable for creating a bitmap context!

Even after the bug was resolved, I continued digging into the framework via the debugger. I’m due for a complete retraining on Intel, but as long as I have my PowerPC machine around, I’m fairly comfortable zipping through the various API to see what really happens when an error occurs.

Sure enough, a few levels into the API, I spot an examination of the input colorspace, and an objection to the fact that it was only 24-bits per pixel. Great, I thought. If the API knows exactly what’s wrong, why doesn’t it let us know somehow, at least through a logging message or something. Then I spotted a call to “CGPostError.” Well, that’s interesting. A well-defined error reporting mechanism? The results of which the developer never sees? I traced along a while until discovered myself in a logging function, the parameter to which was this choice bit of text:

CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component;
	24 bits/pixel; 3-component colorspace; kCGImageAlphaNone.

Well! It doesn’t exactly point to the right solution, but it would have been nice to know! Thanks for not sharing, CoreGraphics. Why the heck does it go to all this work and not print out the result? I decided to dig deeper.

The framework has an internal function, “hasTerminal,” which seems to control whether logging to the console occurs or not. I guess the engineers on the CoreGraphics team are trying to be considerate and not spew logging messages all over your console log unless you’re actually, you know, debugging. Here’s the kicker – hasTerminal returns true if you are running from the terminal. All Jon had to do was test his app from the command-line and the exceedingly helpful logging message would have appeared.

But the “hasTerminal” function returns false when debugging from within Xcode. Bugger that debugger! How does hasTerminal decide whether to allow logging or not? It simply attempts to open “/dev/tty” with read/write access. If it is successful, the user must be running from a terminal? But somehow, when running from within Xcode, this test fails, and therefore logging is not done. I assume this has something to do with Xcode’s magic terminal-esque window, but the discovery raises two questions:

  1. Should CoreGraphics limit its logging functionality based on assumptions about “/dev/tty”?
  2. Should debugging in Xcode impact a target application’s ability to open “/dev/tty”?

These questions are posed in Radar #4538344- “CoreGraphics logging doesn’t appear withing Xcode”.

11 Responses to “CoreGraphics Log Jam”

  1. Jonathan Wight Says:

    Wow. Thanks for taking the time to write this up and even posting a radar bug.

    As much as people love to bitch about old school Carbon programming, at least Carbon had the ever so useful OSErr (and later OSStatus) types to inform the programmer what went wrong with a OS function call. While the errors returned from old Carbon APIs were often cryptic, at least they would point you in the right direction. With the CGBitmapContextCreate problem I was making all sorts of wild guesses before I found the Q&A article. Returning ‘paramErr’ from the function (and returning the CGContextRef as a indirect parameter) would have saved me a little bit of time.

  2. Brian Webster Says:

    I haven’t tried this, so I don’t know if it would make a difference, but in Xcode, if you inspect the executable you’re running, it gives you an option for where standard I/O should go. The default is “Pseudo terminal”, but there’s also an option for “System console”, which I assume would spit the output to console.log. I’m not sure if that would cause the CoreGraphics logging to get output, but it may be worth a try. There’s also a third option in the pop-up, “Pipe”. Heck if I know what that does though. :-\

  3. Daniel Jalkut Says:

    Brian: Yeah, Kevin Ballard also wondered whether those might make any difference. I tried them out and didn’t see any change. I think the problem is that the “Pseudo TTY” choice is the closest to what we want, but even when that is selected, it’s not sufficient to convince CoreGraphics that there is a tty.

    I don’t know enough about how “/dev/tty” gets hooked up to the process’s pseudo ttys to know who is really at fault here.

  4. ++Don Says:

    > I don’t know enough about how “/dev/tty” gets hooked up to
    > the process’s pseudo ttys to know who is really at fault here.

    It’s been a while since I looked under the hood, but here goes…

    /dev/tty is a pseudo-device that the kernel guarantees always refers to a process’s controlling terminal, if it has one. A process inherits its controlling terminal from its parent, but can disassociate itself from its controlling terminal (in which case its children will also have no controlling terminal). As far as I can tell, GUI programs have no controlling terminal if they aren’t run from the command line. XCode can allocate and open a pty device (pseudo-terminal, see pty4. man page), for a process it is debugging. However, just opening a tty device, pseudo or otherwise, does not automatically make that device a controlling terminal. It takes a specific ioctl() call to make it a controlling terminal, and thereby enabling /dev/tty for that process.

    On the other hand, there is no requirement that a terminal be a *controlling* terminal. A controlling terminal is important for Unix-style job control (foreground and background processes, terminal signals, etc.), but not for plain old terminal I/O. I don’t know what hasTerminal() does under the hood, but the very similar C standard library function isatty() takes a file descriptor as an argument and tells you whether or not it refers to a terminal device, irrespective of whether or not it is the process’ controlling terminal. It does this by executing an ioctl() that only works for terminal devices (see the tty.4 man page for a list; isatty() uses a nondestructive call like TIOCGETA or TIOCGETD).

    In either case, starting a process from the command line both will give it a controlling terminal and will have the stdin/out/err file descriptors open on that terminal (the latter assuming no I/O redirection is done). Given that Xcode has the ability to send stdout to a pty but hasTerminal() doesn’t seem to be picking up on that fact, I can think of two possibilities: 1) Xcode connects stdout to the pty but not stdin, and hasTerminal() only checks stdin; or 2) hasTerminal() checks for a controlling terminal. Feel free to continue your assembly traipsing to figure out which it is. :-)

  5. Daniel Jalkut Says:

    Thanks Don for the analysis. From your comments:

    Given that Xcode has the ability to send stdout to a pty but hasTerminal() doesn’t seem to be picking up on that fact, I can think of two possibilities: 1) Xcode connects stdout to the pty but not stdin, and hasTerminal() only checks stdin; or 2) hasTerminal() checks for a controlling terminal. Feel free to continue your assembly traipsing to figure out which it is. :-)

    In fact it is just as simple as I described in the entry: hasTerminal checks only for “/dev/tty”. It doesn’t check for file descriptors or anything. Just calls open(“/dev/tty”, 2).

  6. Sam’s random musings » CoreGraphics Log Jam Says:

    […] CoreGraphics Log Jam: […]

  7. John Oldfield Says:

    > All Jon had to do was test his app from the command-line…

    > But the “hasTerminal” function returns false when debugging from within Xcode.
    (and from ++Don)
    > A process inherits its controlling terminal from its parent…

    So what happens if you launch Xcode from the terminal?

  8. Sanjay Samani Says:

    The thing is that Java style Exceptions, NSException and @try/ @catch / @finally are relatively new in Objective-C / Cocoa (10.3) – even Wil Shipley didn’t know about them (see comments in http://wilshipley.com/blog/2005/08/pimp-my-code-part-4-returning-late-to.html). And unfortunately a lot of people leave adding Exception handling in their software until last because they try to code to handle all conditions. Its hard to get into the habit when you’re new to Cocoa programming cos the learning curve is so great anyway and once you’re up the learning curve you’re into bad habits. What’s fascinating is that the CG guys actually have good error and exception handling – the only problem is that cos Exception handling isn’t really ingrained in Cocoa programmers is it going to end up throwing a whole wobbly of exceptions that will never be handled?

  9. ++Don Says:

    > In fact it is just as simple as I described in the entry: hasTerminal checks only for “/dev/tty”.
    > It doesn’t check for file descriptors or anything. Just calls open(”/dev/tty”, 2).

    Ah, I read over that bit. My bad. OK, /dev/tty won’t work if the process has no controlling terminal, so the open() will fail. As I said, a process inherits its controlling terminal (or lack thereof), from its parent, and simply opening a terminal device (as Xcode does when it sends the debugged process’ output to a pseudo-terminal), won’t automatically make it a controlling terminal. Xcode could execute the necessary ioctl() to make it a controlling terminal. I don’t know if its failure to do this is simply an oversight on Apple’s part or a deliberate decision to avoid some implications of having a controlling terminal that I don’t know about. Controlling terminals generate job control signals (SIGTSTP, SIGTTIN, SIGTTOU), and maybe Apple has some reason for wanting to avoid those.

    If Xcode is directing stdout to a pty, then you should be able to insert an ioctl(1. TIOCSCTTY, 0) call in your code in order to make it a controlling terminal. If the process already has a controlling terminal, or if stdout is not a tty device, then this call will fail harmlessly.

  10. Daniel Jalkut Says:

    Thanks, Don. I included a pointer to this blog entry in my bug report, so hopefully they’ll check it out. It sounds like the best thing to do would be for Xcode to do whatever it can to convince the apps it runs that there is indeed a “controlling terminal.” But who knows, maybe this has other unwanted side-effects.

  11. Monday morning Apple links | Ars Technica Says:

    […] Jalkut writers about the frustrations of working with CoreGraphics, specifically the lack of error reporting when working in Xcode: "The framework has an […]

Comments are closed.

Follow the Conversation

Stay up-to-date by subscribing to the Comments RSS Feed for this entry.