Fun with Mach-O

So, I thought I would spend a little time today porting the excellent Atari 2600 emulator z26 to Mac OS X/Intel. Most of the code in z26 is written in x86 assembly, using nasm. z26 already works just fine under Linux/x86 and uses SDL for accessing the display and sound hardware. In other words, I thought this would be cake. Ha!

The first problem that I ran into is that Mac OS X uses Mach-O format instead of ELF. It turns out that it’s a really easy change to get nasm to output Mach-O files — just use -f macho on the nasm command line. Also, I had to change section .code to section .text. Apparently, Mach-O doesn’t have a .code section. No big deal.

z26 uses this weird build where it compiles a single .c file and a single .asm file into one .o file for each. The other .c and .asm files are included in the master .c and .asm file. So, at this point, I had two .o files. But, I also had a ton of unresolved symbol errors. It took me a few hours of poking around to figure out that unlike ELF, but very much like Windows, OS X puts a underscore prefix on each function name. You can see this with the nm command on a .o file compiled on OS X. This was again easily fixed by adding a --prefix _ to the nasm line.

But, there was still unresolved symbol errors. Hmm… it was certainly less errors than I had the first time — around 8 of them this time. What was with these? To find out, I ran grep for one of the symbols. Apparently, the only mention of it was as an extern in one of the asm files. I grepped the other symbols — all the same. Under nasm for Mach-O, if you declare a symbol as extern, even if you don’t use it, it still get’s put into the .o file as an undefined symbol. Which is why the linker was complaining so much. Simple fix again to remove the offending symbols, and finally, we have success. A z26 executable sitting there waiting for me.

Alas, not all is well. The binary passes the first test with flying colors — typing ./z26 at the prompt gives me the usage instructions. But then I try something else. I try to run it with my 3dcopper demo. And I get this happy dialog box:

z26 crash

Hmm… looks like I need to find out what stub_binding_helper_interface is doing. And after a while with my friend Google and some playing around with the code, I figured out that what was happening is that the assembly code is calling back into the C code, and that C code is in turn calling a function that hasn’t been loaded yet. Mac OS X uses lazy binding to allow applications to load faster. Instead of putting the address of the function, the dynamic linker puts in a special function call to load it on the first call. Once that call is made, the real address is stuck back in there and everything should just work the next time around.

Except that in this case, that function was blowing chunks. I was able to get some test code working just fine by wrapping the call to the C function with the following:

sub esp, 8
call foo
add esp, 8

But, when I transfer that back into z26, it only works for some functions calls, while others continue to die hard. Arrrrgh!

So, at this point, I’ve spent the better part of a day, and I have nothing to show for with this simple little port. I’m gonna try sleeping on it now, and maybe come back tomorrow or the next day. It’s probably something really simple, but I’m just not seeing it.


About this entry