March 25, 2013

Cross-compiling applications for ARM (part 2)

In my previous post I wrote about how to setup a cross-compiling environment. In this post I'll show you how to use that environment to cross-compile everything, from simple Hello World examples to big applications with external dependencies (libraries).

You'll be able to compile whatever you want the way you already do it, except that you should prepend the command sb2 in front of every command you run. That way you're letting ScratchBox2 know what it should run with the session you setup.

Let's start with a small Hello World.

As you can see I'm able to compile and run my code with sb2 and qemu. Notice that I'm using -L in qemu so it knows where to search for I told it to look inside my toolchain, which buildroot setup for me (see previous post if you haven't).

Compiling software that depends on external libraries isn't that different from what I just did, but there are some things that you must do. First you should know that all the libraries that your application depends on must be cross-compiled too. You can't cross-compile your application for ARM linking against a library that is compiled for a different architecture. You must compile all those libraries and install them in a custom prefix to avoid overriding your system's libraries and also to be able to move them to your device and use them.

As I worked with GStreamer I'll show how to cross-compile all GStreamer's dependencies, GStreamer itself and a simple one-liner example.

First let's see a working example of what we want to cross-compile:

Now we have a small working code that requires GStreamer. We know that it works and we know that it depends on GStreamer so we just need to cross-compile it for the target device. Anyways, we can't just prepend sb2 in front of the the gcc line because, as I said, we need every single library to be cross-compiled.

Note: I'm using pkg-config to tell gcc where are GStreamer's libs. It's important to be aware of that as I'll use sb2 and pkg-config when I'm cross-compiling.

We need to get the sources of GStreamer and all it's dependencies and compile/install them somewhere. For that we'll use PKG_CONFIG_PATH and --prefix. That way we're telling our environment where to look for our libraries and where to install them.

I'll create two folders: deps and libs then I'll download the sources of all the dependencies in the first one and I'll install them in the second one.

As I said, I'll be using PKG_CONFIG_PATH (set it just once) and --prefix.

We're getting closer! Just note where I'm setting PKG_CONFIG_PATH's path and where I'm setting --prefix's path.

Note: I'm redirecting to /dev/null because I know that it will compile fine (as I run each command on a second shell just to be sure I'm not making a mistake) and because there is no need to show here the output of a typical compile process.

After compiling and installing all other dependencies, that's how everything looks like:

Now the environment is ready to cross-compile the code I wrote earlier. We just need to tell gcc and pkg-config where they should look at (with sb2's help).

Please note a couple of things!

  • I'm using sb2 pkg-config to tell gcc where to look for the libraries that I cross-compiled
  • I manually appended libffi path to LDLIBS (can't say if that's a bug or I screwed something, but I'm more prone to think that's a bug in libffi as the other libs work just fine and all them are detected properly by pkg-config)

As you can see we successfully cross-compiled the code. Now we can copy the whole libs folder to the target device, set PATH to something like

set PATH=/path/to/copied/libs:$PATH

and we're ready to run the binary we cross-compiled.

PS: I'm not sure how can I run the ARM binary in my machine without screwing my PATH, but if somebody knows please share your knowledge in the comments and I'll update the post.

March 20, 2013

Cross-compiling applications for ARM

Recently I had to develop a project for an ARM device and the first big problem I found during the development process was cross-compiling. There are some tutorials in Google, but most of them are old or simply won't run. Other tutorials suggest the use of big projects like Angstrom, Yocto Project or SHR.

While all those projects deserve my kudos, I really don't want to use any of those projects because
  • I don't want to waste time and energy setting up something that big 
  • It's not as flexible as I wish
  • Neither of those projects seem to have up-to-date packages for the device I'm working for
That's why I spent some time reading docs and I finally came up with (IMHO elegant) solution to my problem. ScratchBox2!

It's a shame that the README is the best doc you'll find inside the project, but luckily I was able to get everything working on a trial and error basis, searching in Google and bugging annoying stalking people in IRC. My sincere apologies to all of you! Your patience is making this post possible :)

First of all, you'll need a few things:
  • Basic developer tools (gcc, make, etc...)
  • QEMU
  • Git (and basic knowledge about how to use it, mainly how to clone repositories and switch to branches) or a distro that already packages sb2
  • A clean system (you don't want something broken in your system messing up with the whole process, so, if you know there's something fishy with your system fix it before going on!)
  • Patience
First clone sb2 and switch to the latest stable branch/release (or use the really latest revision if you're brave enough). If you're on a distro that packages sb2, install it with your package manager. It shouldn't matter either way.

Note: I'm on Arch x86_64 and I got sb2 via my package manager.

Now you need to get buildroot, just download it somewhere and untar it. Once untared, go to that directory and run make menuconfig

You should see something like this:

Now you should change what you need for your device. I'm going to set mine to little endian ARM architecture, generic arm variant.
I suggest you not changing anything in Build options unless you know what you're doing. In Toolchain maybe you'd like to change some of the options. I changed the Toolchain type to External toolchain and then I selected Sourcery CodeBench ARM.
But as I said, maybe you have some specific needs so adjust buildroot to your needs.

Once you're done you should be looking at something like this:

I didn't changed anything in System configuration nor Package Selection, but maybe you'd like to change something (busybox version, add/remove applications, etc) so check all the options.

You'll probably want to select something in Filesystem images as that's what you'll use for your device's rootfs. Anyways, this post is about cross-compiling, so I'll try to stay on topic.

Check the remaining options and once you're done move to the Exit option. You'll be prompted to store the changes you just did, so save them. Now run make and buildroot should start downloading and building everything you selected.

The process will take more or less according to your internet connection and your CPU speed. On my i7-3820 and my 950KB/s connection it took around 2 minutes.

Once done you'll like to see what's inside the output/images and output/target folders. 

The first one contains the real rootfs that you'll probably want to use with your device while the second one contains a nearly-usable rootfs. The main difference between the files from the first folder and the second folder is that the rootfs in the first folder isn't untared and completely usable. This is done because buildroot doesn't run with root privileges, hence it's able to create some special files only inside a tared file.

As I said, I'm trying to stay on topic, but as you can see both things (cross-compiling and deploying) are related.

The output/target folder is the one you'll be using with sb2, so go to that folder. You also need to know where is qemu's binary that you'll be using for your device and the toolchain that buildroot prepared for you, so look for them.

This is how it should be looking (note that paths can change depending on what toolchain you selected):

You need to use sb2-init to create a session with which you'll be compiling.
Also, as you can see I'm inside output/target and I'm using readlink because sb2 needs absolute paths.

If everything is ok you'll see a wonderful message saying: sb2-init completed successfully, have fun!

I'm not sure about this, but sometimes sb2 gives false hopes, as in, it didn't actually setup your session but it says it did, so check the last ~20 lines for any errors, just to be sure.

From now on, unless you change sb2's session, every command prepended with sb2 will run with your device's configuration inside the toolchain buildroot made for you!

I'll make another post about how to cross-compile everything from little Hello World examples to huge projects with tons of dependencies, so just stay tuned :)