Building Mono for Android

<Edit>I have figured out how to get Mono building in the Android build environment fairly easily. Click here for the updated instructions.</Edit>

As I mentioned in previous posts, I managed to get Mono compiled and running under Debian running side by side with Android. I built Mono "natively" by setting up the Debian environment in the emulator. For the sake of having all the instructions in one place on how to set up a native build environment under Debian, I will be consolidating and tweaking the directions from Saurik's post on how to set up Debian on the G1.

Update 1: If you want an installable Mono APK, which is trimmed down to bare minimum dependencies (3MB) and works on non rooted phones, click here or get it off Market. I have updated the instructions below to reflect the configuration and steps I took to build Mono to create this APK.

Update 0: If you just want a premade SD card image for your phone, skip to the very end of this post about deploying Mono to a real phone and follow the directions from there.

Setting up a Native Build Environment under Debian

If you follow the instructions to build Mono for ARM, you will fail miserably. Doing native builds on a G1 is not an option: the hardware is slow, runs out of memory, and just becomes unstable and unresponsive. Ultimately the watchdog timer kicks in and restarts the phone. However, the emulator does not have this issue. It runs faster than a G1, and you can tweak the qemu options to allocate extra RAM. So, the emulator makes a perfect "native" build environment!

As I mentioned, Mono will run under Debian. The Debian installation will be run off the SD card, contained in an ext2 image file. We will set up the ext2 image in the emulator, build Mono, and then transfer the image to a real phone.

Go to a directory somewhere and create a workspace for the installation:

mkdir monoimage

cd monoimage

Set up the SD card image to be used by the emulator by using the mksdcard command that comes with the Android SDK:

# use whatever size you wish

mksdcard 800MB sdcard.img

Build the Debian image as per the instructions in Saurik's post:

sudo apt-get install debootstrap

# use a size smaller than your SD card, must be less than 4GB as that is the max file size for FAT

dd if=/dev/zero of=debian.img seek=749999999 bs=1 count=1

mkefs -F debian.img

sudo mount -o loop debian.img debian

sudo debootstrap --verbose --arch armel --foreign lenny debian http://ftp.de.debian.org/debian

sudo umount debian

Now create the Debian kit on the SD card:

mkdir sdcard

sudo mount -o loop sdcard.img sdcard

sudo mkdir sdcard/kit

sudo cp debian.img sdcard/kit

rm debian.img # don't need this anymore!

# this script sets up the debian mount and gets you a bash prompt

wget http://koushikdutta.blurryfox.com/MonoWalkthrough/bash.sh

sudo cp bash.sh sdcard/kit

# this unmounts the debian image

wget http://koushikdutta.blurryfox.com/MonoWalkthrough/umount.sh

sudo cp umount.sh sdcard/kit

# busybox. handy tools...

wget http://koushikdutta.blurryfox.com/MonoWalkthrough/busybox

sudo cp busybox sdcard/kit

sudo umount sdcard

Time to start the emulator with SD card image we created. However, before we can can do that, we need to make a quick change. The emulator does not support ext2 out of the box, but that can be fixed by recompiling the kernel. I have a prebuilt emulator kernel available for download. (For further do it yourself instructions on how to compile the Android kernel manually, check out Motz's instructions. After following his instructions, you can use "make menuconfig" to tweak the kernel build options and then use "make" to build it.)

# get the custom emulator kernel

wget http://koushikdutta.blurryfox.com/MonoWalkthrough/zImageExt2

Ok, we're set now! Start her up!

# the last 2 arguments tell qemu to provide 512MB of virtual RAM to the emulator

emulator -sdcard sdcard.img -kernel zImageExt2 -qemu -m 512

Now, let's get into a shell on the emulator to finalize the Debian installation:

# This will get you to a "sh" instance on the emulator. Let's get a real bash prompt next.

adb shell

# Ignore any errors you may have with this command!

#You should see "I have no name!@localhost:/#" as your bash prompt after executing this.

. /sdcard/kit/bash.sh

/debootstrap/debootstrap --second-stage

echo 'deb http://ftp.de.debian.org/debian lenny main' > /etc/apt/sources.list

echo 'nameserver 4.2.2.2' > /etc/resolv.conf

# Let's restart the prompt to get rid of that "I have no name!" problem.

exit

# Ignore any errors again. You should get a "localhost:/#" bash prompt now.

. /sdcard/kit/bash.sh

 

Building Mono

Whew! You now have a full Debian distribution on the emulator. Now that we're set up, let's get the ARM portion of Mono built. But first, let's grab all the updates, dependencies, and tools we will need to build it.

apt-get update

apt-get install bison libglib2.0-0 libglib2.0-dev bzip2 gcc make

cd ~

# The following URL is the trunk snapshot at the time of writing this entry.

# Go to http://mono.ximian.com/monobuild/snapshot/sources-trunk/ to get the latest snapshot.

wget http://mono.ximian.com/monobuild/snapshot/snapshot_sources/mono/mono-122990.tar.bz2

tar xvf mono-12290.tar.bz2

cd ~/mono-122990

wget http://anonsvn.mono-project.com/viewvc/trunk/mono/eglib.tar.gz?view=tar

# wget mangles the file name...

tar xvf eglib.tar.gz?view=tar

And now we can finally begin the native build process. First let's set up some environment variables.

# CPPFLAGS=-DARM_FPU_VFP is used because the G1 supports VFP.

# Also, tell the linker where the runtime linker is found, and the path to the libraries.

export 'CFLAGS=-DARM_FPU_NONE -DPLATFORM_ANDROID -Wl,-rpath,.,-rpath,/data/data/com.koushikdutta.mono/assets/lib,--dynamic-linker,/data/data/com.koushikdutta.mono/assets/lib/ld-linux.so.3'

export CPPFLAGS=$CFLAGS

# We need to set the linker flags specifically as well...

export 'LDFLAGS=-Wl,-rpath,.,-rpath,/data/data/com.koushikdutta.mono/assets/lib,--dynamic-linker,/data/data/com.koushikdutta.mono/assets/lib/ld-linux.so.3'

# --disable-mcs-build configures it not to build the managed assemblies.

# Managed assemblies will be built on the host.

# --with-tls-=pthread changes how thread local storage is implemented.

# The default __thread does not work on Android.

# --disable-shared-handles is used because the G1 kernel does not have SYSVIPC enabled.

# --with-static-mono=no forces mono to dynamically link to libmono to save space.

# --with-glib=embedded embeds a trimmer version of glib into Mono.

# --prefix and --exec-prefix inform Mono of where it's binaries, libraries, and assemblies are found.

./configure --disable-mcs-build --with-tls=pthread --disable-shared-handles --with-static_mono=no --with-glib=embedded --prefix=/data/data/com.koushikdutta.mono/assets --exec-prefix=/data/data/com.koushikdutta.mono/assets

# This will take a couple hours... make a pot of coffee or 6.

make

Note that we have only built the Mono runtime at this point. We need to build the managed code portion now. First let's exit the emulator cleanly and unmount the ext2 image cleanly:

# Exit bash

exit

# Unmount the ext2 image

. /sdcard/kit/umount.sh

# Exit adb shell

exit

You can now close the emulator and remount the SD card on your host PC. You should be in the monoimage directory you created earlier.

sudo mount -o loop sdcard.img sdcard

sudo mount -o loop sdcard/kit/debian.img debian

Get the Mono sources again and build them on the host:

wget http://ftp.novell.com/pub/mono/sources/mono/mono-2.0.1.tar.bz2

tar xvf mono-2.0.1.tar.bz2

cd mono-2.0.1

./configure --with-glib=embedded --prefix=/data/data/com.koushikdutta.mono/assets --exec-prefix=/data/data/com.koushikdutta.mono/assets

make

So, both portions of Mono have been built. Let's install the managed portion directly to the ext2 image.

sudo make install DESTDIR=/full/path/to/monoimage/data/data/com.koushikdutta.mono/assets

Unmount the images:

sudo umount debian

sudo umount sdcard

Restart the emulator to finalize the Mono installation (this will overwrite the PC runtime that was installed):

emulator -sdcard sdcard.img -kernel zImageExt2 -qemu -m 512

. /sdcard/kit/bash.sh

cd ~/mono-2.0.1

make install

Mono should now be installed and functional! Let's test it:

cd ~

wget http://koushikdutta.blurryfox.com/MonoWalkthrough/hello.cs

# gmcs IS a managed executable; and obviously quite a complex one at that.

# If it succeeds. Then Mono is working... quite well.

gmcs hello.cs

# Hello World!

mono hello.exe

Before we transfer the Debian image to a real phone, let's shut down gracefully again:

# Exit bash

exit

# Unmount the ext2 image

. /sdcard/kit/umount.sh

# Exit adb shell

exit

Putting Android onto a Real Phone

If you just want a a premade SD card image that contains a working Debian install and Mono runtime, click here. Unzip the sdcard.zip and follow the instructions below to transfer the debian.img (which is contained in the sdcard.img) to your phone.

Your phone must be rooted and have the latest version of modified RC30 (1.3+) . Connect your phone and copy the kit directory from the SD card image to your real SD card. You should preferably do this by mounting your phone's SD card.

# Mount the SD card image first so we can access the kit.

sudo mount -o loop sdcard.img sdcard

# It is actually much faster to mount your phone's SD card as a removable disk on your

# computer and copy the kit directory over from the sdcard.img to the real SD card.

# It is also less likely to be corrupted as well. Don't use adb push unless you really want to.

adb push sdcard/kit /sdcard/kit

Now, go to a bash prompt on your phone and you will be able to run Mono!

adb shell

. /sdcard/kit/bash.sh

cd ~

mono hello.exe

What Now?

Well, now Mono is running on Android, side by side with Debian. However, attempting to invoke it from outside of a chroot /bin/bash will result in a "not found" error. This is because Mono is not configured to use Android's linker (/system/bin/linker), and looks for it at "/lib/linux-ld.so.3". You can fix that by doing the unionfs trick that Saurik posted about in his blog.

However, this process still requires the phone to have root, and have a Debian installation. For this to really be viable, Mono would need to be compiled statically, with all its dependencies. And I have no idea how to do that, yet. If someone does, please let me know!

21 comments:

Anonymous said...

Thanks for posting the instructions!

Anonymous said...

Think you could upload your sdcard.zip file elsewhere? My download speed slows to a crawl after a couple minutes of downloading. Thanks.

Koush said...

I'll look into getting a mirror.

m said...

What are the dyanamic dependencies that you have?

Would you mind running: ldd mono?

You could try passing the "-static" flag to the GCC command line that produces the `mono' executable.

joe said...

I can see that m(iguel?) is intensely interested in getting Mono to run on Android. I can't blame him, as I too would love to see Mono run on ... everything!

I'll be watching this blog closely. I don't have a G1 to test with yet, the Agora Pro is on the top of my list though.

Koush said...

I've tried the -static flag; it doesn't work; didn't look into it further though. It's a bit of a kludge. :)

The cause of the problem is that mono is looking for the linker at /lib/ld-linux.so.3, which does not exist on Android. Android's linker is /system/bin/linker. However, by using the --dynamic-linker option on ld, you can change where an executable looks for the linker.

So, basically by passing "--dynamic-linker ld-linux.so.3" to the linker during make, and make sure that the linker (and all of Mono's dependencies) are in the same directory as mono.

I've tested with a few simple programs to verify it works-- and it does.

Furthermore, this should work for *all* phones, not just jailbroken ones.

Here are the list of dependencies of Mono (each dependency may have further unlisted dependencies).

libgthread-2.0.so.0
libglib-2.0.so.0
librt.so.1
libdl.so.2
libpthread.so.0
libm.so.6
libgcc_s.so.1
libc.so.6
libpcre.so.3
/lib/ld-linux.so.3

Koush said...

To elaborate further on why "--dynamic-linker ld-linux.so.3" works (in case it isn't obvious).

Since it is not a full coded path, executables will look for that file in their (current) directory at runtime.

I should probably write this up in a separate post-- I know a lot of other Android developers are also trying to make the standard ARM/Linux build outputs work on Android.

The downside to this is that libc.so.6 is 5 times the size of /system/bin/libc.so. And we'd effectively be loading the same library twice into memory. The optimal solution is to recompile everything to reference Android's libc.so. Or, maybe customize ld-linux.so.3 to special case libc. But that might not work-- I'll take a wild stab and say that Android's libc may just be a subset of ARM/Linux libc due to the disparity in file sizes.

Richard said...

Rapidshare is a good upload site. I too can't access the sdcard.zip and this measly laptop would take hours for me to compile myself. Thanks for all your hard work.

Koush said...

Total disk footprint of Mono and all it's dependencies is 6598288. I'm trying a build with eglib now (and I'm also going to change the path of the linker and see if it works on Android out-of-box). Crossing my fingers...

Koush said...

Hopefully I'll have a simple Mono APK available on the Market later this week!

Richard said...

Using your sdcard.zip I get a:

Unhandled Exception: System.TypeInitializationException: An exception was
thrown by the type initializer for Mono.CSharp.Location --->
System.ExecutionEngineException: SIGILL
at System.Collections.Hashtable..ctor (Int32 capacity, Single loadFactor, IHashCodeProvider hcp, Icomparer) [0x00000]
at System.Collections.Hashtable..ctor (Int32 capacity, Single loadFactor) [0x00000]
at System.Collections.Hashtable..ctor () [0x00000]
at Mono.CSharp.Location..cctor () [0x00000] --- End of inner exception stack trace ---


whenever I run gmcs or gmcs hello.cs without an exe being created. Strangely, mono hello.exe prints out the "Hello World!" message and then the error above. Any ideas?

Koush said...

Richard, is that happening under Debian?

Richard said...

Yes, after running a chroot. I wonder if this problem is because I mounted it under /sdcard/kit/mnt instead of under /data. What do you think?

Koush said...

Hmm possibly, but I don't see why it would.

Try using the vanilla /sdcard/kit/bash.sh that I provided and verify that it works.

Feel free to email me at koushikdutta at hotmail dawt cawm

Richard said...

I tried recompiling but I still get the error. Think you could upload your working /usr/local/bin/mono to rapidshare for me?

Koush said...

Sure, give me a moment. I've successfully gotten mono compiling and working with eglib. That decreases the footprint of Mono considerably.

I'm working on linking it with bionic now (Android's libc). After that is done, it should run on Android's flavor of Linux flawlessly.

Koush said...

Hi Richard, I've fixed the assertions/exceptions. See http://www.koushikdutta.com/2009/01/mono-for-android-first-update.html for more information as well as an updated APK.

Andy said...

Hi there,

Is it likely that consumers could benefit of these developments ? I mean could I develop applications for the Android platform that users could run on a G1 for example without having to do anything else then installing my app ?

Anonymous said...

I followed your instructions trying to run in the Android emulator a .Net 2.0 Winforms application (graphics, audio) created in SharpDevelop on Vista. First I tried using Mono.apk; my app crashed, the error raised was something like "Winforms assembly not found". Then I mounted your sdncard.img image file (the one with debian and mono prebuilt), launched the Android emulator with the mofified ext2 kernel image, then tried to run /sdcard/kit/bash.sh. As you know, files on the sdcard cannot be either run or set as executables. So I opend bash,sh on my Linux box and then executed the commands within in the Android shell. Finally, I copied my .net executable and files from my Linux box to the /home folder in the debian mounted system. Upon issuing mono myapp.net it crashed with an error message pointing to a unimplemented property in Winforms.

Bottom line, mono for debian-arm is not yet up to the job.

Congratulations on making it possible to get this far!

Laurent said...
This comment has been removed by the author.
Anonymous said...

As a final note to my previous post, my .Net app runs as expected on Linux Ubunty Hardy but was developed in #D on Windows.

I have not tried with an app developed in MonoDevelop on Linux.