Basically I'm doing it by re-implementing Xlib in terms of Quartz, so that the source code of the individual savers doesn't have to change. I also wrote code that reads the existing XML files and constructs Cocoa controls, so all the savers will have the same configuration UI as before, too. That was actually the hardest part so far.
I have some OSX hacking questions: <lj-cut text=" --More--(14%) ">
I've got an XCode project that will eventually include hundreds of .saver targets. Each of these is built from about a dozen source files, only one file of which differs in each target. XCode builds a dozen .o files for each target, instead of sharing the majority that they have in common (which is slow, and takes up a lot of space). What's the right way to share these? Build a .a? A bundle? And how do I do that in XCode? (I don't want to build a library that gets installed globally on the machine; I want each .saver bundle to be self-contained. I'd just like to optimize the build process.)
- Answer: Add a ".a" target. Pretty straightforward.
Is there some sensible way to make XCode's "Build and Run" and "Build and Debug" commands work with .saver targets? If I double-click on the .saver product, it will launch System Preferences, as me if I want to re-install, and let me run it there, but that's a lot of clicking, so I've just been debugging them using a tiny test harness .app version. It's kinda kludgey though, because I have to modify what gets linked into that app when I want to switch from one saver to another.
- Answer: no, not really. You can make "Build and Run" launch System Preferences, but then you still have to manually select the Screen Savers page.
How do you debug use of freed memory errors on OSX? MallocDebug and the various MallocCheckHeap* environment variables aren't being any help. Things like:
- Target application (pid 18224) attempted to read address 0x55555575, which can't be read.
MallocDebug can't do anything about this, so the app's just going to have to be terminated.- objc: FREED(id): message retain sent to freed object=0x3604a0
objc: FREED(id): message retain sent to freed object=0x38fd90- Answer: No good answer.
Sadly, most of the older screen savers are written in a very non-reentrant way: heavy use of global variables, and some use of static locals to hold state machines. This means that when the OSX screen saver framework creates more than one ScreenSaverView at once (e.g., the preview view and the full-screen view; or one view on each screen of a multi-head system) the two copies fight with each other and it all blows up.
So, the right way to fix this is to change the code so that they store all their state in a structure that gets passed around, like the more modern savers do. But that's a lot of really tedious, boring work and I'd rather avoid it.
Maybe I could have the ScreenSaverView fork() so that each instance actually runs the graphics code in a private address space? But I don't know whether Quartz is re-entrant in that way, and that sounds like a big hassle anyway. Any other ideas?
- Answer: No good answer; I'll just fix them all to not use globals.
Likewise, the old savers aren't good about cleaning up after themselves: I'm sure most of them leak, because they assumed that when they stopped running, the process would exit and it didn't matter. I'm not really clear on how the OSX screen saver framework works, but it looks like it's just loading the .saver bundles right into its own address space and not sandboxing them in any way. What is the lifetime of the process that loads that bundle? If the answer is "until you log out", well,that could be a problem...
Is there some way to say that "all calls to malloc() between points A and B go into region X" and then at the end, just free region X in bulk? I know about NSAutoreleasePool, but I don't know if there's any mechanism like that that works with C code and plain-old-malloc.
- Answer: malloc_create_zone(), malloc_zone_malloc() and malloc_destroy_zone() look promising here.
As far as I can tell, CGContextClipToRect() has no effect on anything anywhere anytime. WTF? Without this, the savers are able to write outside of their View and mess up the enclosing window (e.g., the enclosing System Preferences dialog.)
- Answer: I was drawing while the wrong window was focused. Don't draw from within [ScreenSaverView startAnimation].
> Is there some way to say that "all calls to malloc() between points A and B go into region X" and then at the end, just free region X in bulk? I know about NSAutoreleasePool, but I don't know if there's any mechanism like that that works with C code and plain-old-malloc.
Not as such, but it'd be pretty darn easy to write. You might want to look at Apache's APR -- they have a memory pool abstraction too, maybe it's simpler than the NS one.
If you're only worried about memory allocations from the savers themselves then you could just provide your own malloc/calloc/realloc/free targets that actually call your pool-keeping versions (either with simple #define's, or with link-time preferences. If you're also worried about allocations made deep down in some OSX library it might be a little tricky.
You may be interested in Talloc:
http://talloc.samba.org/
from the Samba project. It provides a hierachical memory allocation framework, in C, around malloc()/free()/etc. In particular if you set the hierachy up right, you can say "free everything allocated by the thing I just called".
Ewen
Just for fun ...
NSPR has an abstraction for malloc(). I believe that if you MALLOC() rather than malloc() NSPR will take care of the free() for you.
I'm probably wrong though. It's been a long time since I was smart.
A hackish way to do this would be to wrap your malloc'd memory inside autoreleased NSData objects:
void *blargle = malloc(numObjects * sizeof(whatever_t);
NSData *blargleData = [NSData dataWithBytesNoCopy:blargle length:(numObjects * sizeof(whatever_t)) freeWhenDone:YES];
You can then just toss blargleData (or just never assign the created NSData to a pointer in the first place). The memory you malloc'd would then be freed whenever you free the surrounding NSAutoreleasePool. Of course, depending on the size of your mallocs, this could be incredibly wasteful of memory. Also, it wouldn't help you at all if you needed the memory to live beyond the scope of your NSAutoreleasePool.
blegh. I forgot to mention that if you wanted to do this in straight-up C, you'd use the Core Foundation C api instead. This should be exactly equivalent to the chunk of Objective-C mentioned above:
CFDataRef blargleData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, blargle, (numObject * sizeof(whatever_t)), kCFAllocatorDefault);
Actually, scratch that. The C call doesn't return a CFDataRef that is autoreleased. As far as I can tell, there is no way to use the Core Foundation CFObject API to autorelease an object. Instead, you'll have to do some sort of gross objc runtime call to send the autorelease message:
objc_msgSend(blargleData, sel_registerName("autorelease"));
Now seriously. I promise I'll stop correcting myself.
Depending on what this is linked against (which isn't completely clear to me on Mac), this could apply:
http://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html
"The GNU C library lets you modify the behavior of malloc, realloc, and free by specifying appropriate hook functions. You can use these hooks to help you debug programs that use dynamic memory allocation, for example."
This would allow you to log your memory calls. I guess there should be tracing libraries written on top of this?
Not likely to work. "nm /usr/lib/libSystem.B.dylib | grep malloc | grep hook" doesn't return anything. glibc's malloc and Darwin's don't seem to have much common ancestry. I know glibc's is a heavily modified version of Doug Lea's classic implementation. Looking at Darwin's exported symbols it doesn't look familiar at all.. its probably OS X (or maybe NeXT) specific
Yay!
Soon, moving to OS X will not be horribly painful!
IIRC you create a custom executable for /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app with the args '-debug -module -background' where is the name of the module you're testing (which should be symlinked or copied into the right directory, etc, etc). There also used to be something called SaverLab floating around that runs 'em inside of windows for easy debugging, but I don't know its status.
To debug a plugin in Xcode, you need to add the app that loads the plugin as a custom executable to the Xcode project, then run that in debug mode.
I'm guessing that the app is System Preferences for this one, so...
Main Menu -> Project -> New Custom Executable
Click on the Choose button and pick System Preferences (or just type /Applications/System Preferences.app in).
When you're ready to run it, go to Main Menu -> Debug -> Debug Executable. That'll run the app in debug mode, and any breakpoints or such you've set in your plugin will be hit.
I don't get it. That launches system preferences, but it doesn't load the .saver bundle into it.
It should ask if you want to install your saver bundle if it's not already installed - just pick yes. Then select your screen saver as you normally would. System Preferences acts just the same when it's run this way as it would any other time. The only difference is that you'll be able to hit breakpoints or pause execution in the debugger while your code is running.
You can also put /System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app as a custom executable and run the debugger with that, though I'm not sure how well that'll work, since the screensaver window is shown above everything and I'm not sure there's any way to switch it out if it's paused.
It launches System Preferences and asks to load the bundle if I double-click on the .saver product.
But I added System Preferences as a "custom executable" and I have "Active Target: foo.saver; Active Executable: System Preferences". When I do "Build and Run" it just opens System Preferences; it doesn't try to load the .saver, or even select the "Screen Savers" page of System Preferences.
Don't do a Build and Run - just build. Then select the Debug Executable item from the Debug menu.
That does me no good at all. That's exactly what I get when I double-click on the .saver file.
Since everyone keeps answering the question that I'm not asking, I assume that the answer is "it's impossible", but I'll try re-phrasing again...
What I have:
What I want:
I want this to reduce the number of useless clicks I have to do in my edit-compile-debug cycle, and so that the thing I'm clicking is up on the toolbar instead of at the bottom of the list on the left.
If I could pass the name of the current target on the command line of the current executable, then I could write my test harness executable to load the named .saver and run that, but I don't know how to get that name either. I tried passing the $TARGET_NAME environment variable, hoping that if "Active Target" and "Active Executable" were different, that variable would have the name of the target in it, but it turns out that no, it has the name of the executable in it instead.
Embedding screensaver in an application seems a good solution, but another one is to use applescript to activate the screensaver... for example with osascript from command line.
When I was debugging my screensavers, I had my symlink from the screensavers directory to the built package, and used an applescript as my "executable". It looked like this:
However, I was only debugging one screensaver. If you then want the screensaver pref window to automatically select the saver you're working on, you'll need to add some UI element scripting. You might have to turn on UI element scripting - it's in "Universal Access" prefpane, "Enable access for assistive devices" checkbox.
The reason for the double click on the "Use random screensaver." checkbox is to force ScreenSaverEngine to restart the screensaver. Please let me know if this fixes your problem.
-- curious_jp
somewhat OT, what do you think of xcode? I played around with it some earlier today and found the UI incredibly counter-intuitive, enough that I think I may just go back to terminal and vi.
I find XCode pretty awesome, actually. The built-in editor is even Emacsy enough that I do most of my editing in it (I only switch over to XEmacs if I'm doing something really hairy like keyboard macros or regexp replacements). (And even that's easy, since XCode auto-reverts the files when you switch back). Being able to fiddle breakpoints right in the source view is really nice, and ZeroLink is just god damned magical.
It took me quite a while to come to terms with the structure of Cocoa applications and nibs and so on, but that was more about the OS than about XCode itself.
I agree.
I've been using Xcode for years, but I still find it quite unintuitive, and sometimes they seem to actively put effort into moving around options from one place to another between versions, so you need to hunt about to do things.
And it seems to be really not liking my Subversion server. It works, but frequently doesn't notice files changing and such :)
A read to 0x55555555 sounds like you're dereferencing a freed region that's been auto-munged.
Forking a process with Mach ports (like anything interesting) is a recipe for disaster. Don't do it.
You're quite correct - saver bundles are never unloaded. In fact, Cocoa bundles cannot be unloaded at all. However, the ScreenSaverEngine process closes down as soon as the screen saver stops, so it isn't a huge issue unless you're randomizing savers.
Make a target that builds a .a with all the common code in it. Make the other screensaver targets depend on this target. Add the .a from that target to your project and then include the .a in the link step of your screensaver targets.
(And there was much rejoicing.)
*rejoices*
As for directing malloc/free into a specific zone, I'd do it (and have done it) by making the individual modules link in your versions of malloc and free (overriding the standard library), and also exposing a set of management functions with names like MemMgrPoolNew() and MemMgrPoolDelete(), which push and pop pools from a stack. As long as (1) your versions of malloc/realloc/free work the same as the library ones, and (2) the code in question doesn't call any outside routines that allocate or free memory through the 'usual' channels, and (3) the program's memory behavior is 'strictly nested', and (4) reality stays calm and out of the way, everything works.
I've done a bunch of wholesale replacements for new/delete/malloc/realloc/free that work without requiring any change to the client code. The concepts are simple, but there's an ungodly amount of hair. (e.g., you know off the top of your head what malloc(0) is defined to do? where does strdup(...) get its memory from? etc.) Given that you're going to completely destroy each saver, faking a process exit, you probably don't have to get everything perfect.
-Spike, willing to help with the memory bit if you're interested
jwz++
http://www.cocoadev.com/index.pl?ScreenSaverEngine
Create a custom Xcode executable. Then tell it to refer to:
/System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine
And pass in module via -module.
You may need to symlink your module(s) to ~/Library/Screen Savers/
Also - it is really easy to create your own Cocoa application that embeds a screensaver. You might want to do that - it might speed up the debug process a little.
And as for the problems with globals in your screen saver modules. You _should_ be ok. The saver you see in the Preferences window is being run inside the address space of System Preferences. The one you see when your screen saver kicks in is running inside ScreenSaverEngine.app. They wont share globals. You _might_ get a problem with two monitors but it would be easier to just only show the screensaver on the primary monitor.
Worst case scenario you could write your rendering code to work over some kind of IPC mechanism - your saver runs in a daemon and your compatibility layer sends mach messages to the host application that converts the calls into quartz. I _really_ doubt that would be necessary.
If you need a hand with Quartz/Cocoa/CoreImage etc I have a tonne of experience with those - so feel free to contact at schwa/at/toxicsoftware/com
Oh and as for leaks - ScreenSaver.app only runs when the screen saver is up and running. You wont need to worry about leaks. It'll terminate when the user moves the mouse/logs back in.
The only time you'll need to worry about serious leakage is if the user previews a bunch of xscreensavers in System Preferences.
SaverLab, which someone mentioned, is available at http://www.dozingcatsoftware.com/ and could perhaps be useful. It's fun to play with, in any case (setting the screensaver as desktop background or transparent overlay and whatnot)
You could enlist the power of Open Sores to help clean these up; make a list of the nasty ones and post, I'm sure more than one person would help (I'd throw down for a couple... keep meaning to toss a Rorschach patch your way that makes its display more colourful).
Looks like somebody has already ported some of xscreensaver. They didn't bother to attribute their sources of course.
How many savers are affected by the non-reentrancy problem? How many involve multiple source code files? I'm happy to do a couple too, if that'd help.
Assuming each instance gets its own thread (or could do so), would it be feasible to mark all the offending globals and statics with __thread to make them thread local?
Alternatively, could it be done as an automated source code transform by using nm to suck out the names of symbols in the DATA and BSS sections?