[Tutorial] Cross compile SDL2 C(++) applications from Linux to clockworkOS

Finally managed to successfully cross compile one of my work-in-progress emulators to clockworkOS so I figured maybe someone here could use some help with this.

Please note that this instructions only work for the Fbturbo driver. I would appreciate if someone has any directions on how to make this work for Lima, because that’s the most promising driver on clockworkOS.

There are several ways to build a cross compile toolchain, I used crosstool-ng. Simply follow the instructions on how to set up a toolchain from their Documentation page (pages 1-4). If you’re unsure about how to configure crosstool-ng, you can use my configuration available here. Some useful bits:

  • GameShell machine name is arm-linux-gnueabihf
  • Target OS is linux
  • Version is 4.20.8 (based on clockworkOS image 0.3)
  • glibc version is 2.24

Once you have a cross compile toolchain (building takes a while), you’re ready to cross compile SDL2. Get the source code and run the following for the configuration and installation:

./configure --host=armv7l-unknown-linux-gnueabihf --build=x86_64-linux-gnu --disable-pulseaudio --prefix=/usr
make
chmod -R +w ~/x-tools/armv7l-unknown-linux-gnueabihf/armv7l-unknown-linux-gnueabihf/sysroot
make DESTDIR=~/x-tools/armv7l-unknown-linux-gnueabihf/armv7l-unknown-linux-gnueabihf/sysroot install
chmod -R -w ~/x-tools/armv7l-unknown-linux-gnueabihf/armv7l-unknown-linux-gnueabihf/sysroot

Your toolchain path might look different depending on your configuration. Finally, in order to compile your applications you can take some inspiration from this simple Makefile:

CROSSTOOL_HOME := $(HOME)/x-tools/armv7l-unknown-linux-gnueabihf
SYSROOT := $(CROSSTOOL_HOME)/armv7l-unknown-linux-gnueabihf/sysroot
SDL_FLAGS := `$(SYSROOT)/usr/bin/sdl2-config --cflags`
CXX := armv7l-unknown-linux-gnueabihf-g++
CXX_FLAGS := -Wall -Wextra -std=c++17 -ggdb3 $(SDL_FLAGS)
BIN	:= bin
SRC	:= src
INCLUDE	:= include
LIB := lib

LIBRARIES := `$(SYSROOT)/usr/bin/sdl2-config --libs`
EXECUTABLE := gs-cross-compile

all: $(BIN)/$(EXECUTABLE)

$(BIN)/$(EXECUTABLE): $(SRC)/*.cpp
	$(CXX) $(CXX_FLAGS) -I$(INCLUDE) -L$(LIB) $^ -o $@ $(LIBRARIES)

clean:
	rm -rf $(BIN)/*

The only important part here is to use the cross compile toolchain of course and use the sdl2-config binary inside your cross compile toolchain, this binary will correctly set the necessary flags for gcc.

Hope this helps someone, would love some feedback from people with more experience than me in embedded, this is my first time messing with this stuff.

4 Likes

i was amazed to see this, and gave it a try. 3x now and it just will NOT compile the actual binary. I figure its something I’m doing wrong so i’m spinning up a couple different variants in VM’s just to be sure. will report soon

You don’t need a VM to cross compile but if course you can use one, as long as it’s x86_64. If you can share the compiler output I could try to help, it took me a while to get the cross compilation toolchain set up properly to be honest.

I use a VM because I have it hosted on my server and I can easily access/use it from anywhere via VNC [KVM setup] but the problem i’m encountering is with SDL2. your instructions build SDL2 for the host not the cross compiler so the armv7l compiler doesnt recognize the binary type

What CPU architecture does your VM have? These instructions are meant to be used with a x86_64 host. You can check this with gcc -dumpmachine. If you’re using an ARMv7 VM you obviously don’t need to cross-compile anything by the way.

I can’t remember if I had other environment variables set that I didn’t mention in my instructions, but I’ll check this on a VM later today. You could try setting CC or CROSS_COMPILE if you want to test yourself, see crosstool-ng usage.

the VM is not armv7l it is x86_64-linux-gnu. and I’m not 100% atm as I don’t have my GameShell here to test, but I deleted EVERYTHING (x-tools, src packages/etc) and then went through the whole compile again. this time it actually created the gs-cross-compile binary from your sample ‘just clear the screen’ code. I will update this evening on if this is runnable code (I see NO reason it wouldnt be). then I will be getting a few other SDL libraries put together to use and I can start my project. MANY thanks (in advance) I had never heard of crosstool-ng saved me hours (even with the issue I seemed to have had)

Glad you got it sorted out. I’m curious what are you going to use to test the binary if you don’t have a GameShell, have you successfully booted up clockworkOS on a VM or are you going to use any armv7 VM?

I’m not sure if anyone has got clockworkOS running on QEMU or similar, it would be really cool to have a guide about that.

nonono, I DO have a gameShell. but I am also working on getting an armv7l VM under qemu for development testing and such. the plan is to setup a dev environment and a test environment without ever needing the physical GameShell as it could be for beta and release/etc. the actual image we can use on the gameshell will NOT boot under qemu in all my testing, so I’ve taken a debian armv7l build and am configuring it with the loader/etc. and once its done I will be making it available on the forums and would like to link your article here as well. get a nice little suite setup and maybe we can attract some more developers.

That’ll be awesome, looking forward to your guide, would love to have a VM I can use instead of real hardware.

so I was looking to get some SDL2 extensions built in as well and having troubles as they expect the HOST to have them. maybe you can help. but SDL2_image, SDL2_ttf SDL2_Mixer and SDL2_Net are the specifics I was looking to build. wanna give them a shot? and provide some feedback/insight? they all expect the HOST to have them, but obvioulsy we dont want the HOST compiled in.

Also. although it compiled no problem, the sample code will NOT run. it fails to load OpenGL on the actual ClockWorkPi. ideas? [I’m going to rewrite it to try and get more verbose error information]

So, HOST in cross-compiling terminology is BUILD machine, in this case the cross-compiler sysroot. What I suspect we have to do is to install all these SDL extensions dependencies on the sysroot before attempting to install SDL with the extensions.

Could you share the full command you’re using to compile SDL with all those extensions? Also, do you happen to know what are the dependencies? I know on Debian there are packages for these extensions: libsdl2-image-dev, libsdl2-mixer-dev, libsdl2-ttf-dev, libsdl2-gfx-dev, but we need to figure out what are the dependencies of those packages I think.

How are you running the generated binary? If you’re using SSH, you need to select the default display (:0), so you would run the binary with something like this:

DISPLAY=:0 /path/to/binary

Note that if you use the launcher, you don’t have to do this.

yes. and if we are able to leverage the hosts development libraries to build those I’ll try that. I assumed that they would require armv7l versions of the SDL libraries.

as for running the binary yes. I both ran from ssh using
"export DISPLAY=:0 && ./testapp" (testapp is what I named it)
and when that gave the GL error i made “testapp.sh” and set it up for the launcher. it calls
"./testapp 2>&1 > log.txt"

which just puts stderr into stdio and stdio into log.txt and ran from the launcher. which resulted in the same error “Failed to load OpenGL” aka the error message from the test code when gladLoadGLLoader fails. which is indeed what is happening. is it possible that theres a GL or glad library that should be copied over because the lib folder is obviously empty and the Makefile does nothing to copy any.

Can you try forwarding the X server over SSH? Add -X to your command.

Maybe the OpenGL libraries are missing on GameShell, I can’t remember if I installed those at some point before attempting to run the cross-compiled binary. Could you double check your binary is dynamically linked? On GameShell that would be

$ ldd /path/to/binary

This will display the path of the dynamic libraries the binary depends on, double check that the library glad is trying to load exists there. Alternatively you could debug it with gcc and step through the glad function to see exactly where is failing, that’s more or less what I had to do to figure our how to run stuff on GameShell.

crosstool-ng has an ldd version for armv7 you can run in your computer by the way, it’s in the bin directory inside the x-tools generated directory.

I can attempt to run a newly compiled dynamically linked binary on a fresh clockworkOS 0.3 image tonight and report back, maybe I forgot to add a step to this guide.

forwarding the x-server did nothing to change the effect. and when I run LDD on the CPI and look through all 56 dynamic libraries it has loaded, not 1 of them is glad or GL, but that could be because its a runtime load. so I used the crosstools version (which required a --root option) and i see 9 libraries (several of which are dependant on the host system) but not one of them is GL or glad either

PS: i Love the BadFood and DeadCode “addresses” it uses

glad is statically compiled, it’s not a library it’s just a single .c file, but you’re right, the glad loader loads the OpenGL functions at runtime so maybe ldd doesn’t show them, but as far as I remember it did… I’ll try this tonight on a fresh clockwork 0.3 image and report back.

I Finally have a debian image that will boot AND it can execute the binaries from armv7l . I am now doing some integration to ensure it’s completely compatible and if so I will package it up and share. any particular file sharing host I should use? I was thinking google drive. (its about 2G)

I just tested this on a freshly imaged SD card with clockwork OS 0.3 and it works.

IMG_20190319_223400

This is the ldd output of my build on GameShell, maybe it helps:

cpi@clockworkpi:~$ ldd ./gs-cross-compile 
	libSDL2-2.0.so.0 => /usr/lib/arm-linux-gnueabihf/libSDL2-2.0.so.0 (0xb6e14000)
	libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6e01000)
	libstdc++.so.6 => /usr/lib/arm-linux-gnueabihf/libstdc++.so.6 (0xb6cf5000)
	libm.so.6 => /lib/arm-linux-gnueabihf/libm.so.6 (0xb6c7d000)
	libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6c54000)
	libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6b66000)
	libasound.so.2 => /usr/lib/arm-linux-gnueabihf/libasound.so.2 (0xb6ab1000)
	libpulse-simple.so.0 => /usr/lib/arm-linux-gnueabihf/libpulse-simple.so.0 (0xb6a9d000)
	libpulse.so.0 => /usr/lib/arm-linux-gnueabihf/libpulse.so.0 (0xb6a59000)
	libsndio.so.6.1 => /usr/lib/arm-linux-gnueabihf/libsndio.so.6.1 (0xb6a3d000)
	libX11.so.6 => /usr/lib/arm-linux-gnueabihf/libX11.so.6 (0xb6949000)
	libXext.so.6 => /usr/lib/arm-linux-gnueabihf/libXext.so.6 (0xb692e000)
	libXcursor.so.1 => /usr/lib/arm-linux-gnueabihf/libXcursor.so.1 (0xb6917000)
	libXinerama.so.1 => /usr/lib/arm-linux-gnueabihf/libXinerama.so.1 (0xb6904000)
	libXi.so.6 => /usr/lib/arm-linux-gnueabihf/libXi.so.6 (0xb68ea000)
	libXrandr.so.2 => /usr/lib/arm-linux-gnueabihf/libXrandr.so.2 (0xb68d3000)
	libXss.so.1 => /usr/lib/arm-linux-gnueabihf/libXss.so.1 (0xb6f05000)
	libXxf86vm.so.1 => /usr/lib/arm-linux-gnueabihf/libXxf86vm.so.1 (0xb68bf000)
	libwayland-egl.so.1 => /usr/lib/arm-linux-gnueabihf/libwayland-egl.so.1 (0xb68ab000)
	libwayland-client.so.0 => /usr/lib/arm-linux-gnueabihf/libwayland-client.so.0 (0xb6893000)
	libwayland-cursor.so.0 => /usr/lib/arm-linux-gnueabihf/libwayland-cursor.so.0 (0xb687c000)
	libxkbcommon.so.0 => /usr/lib/arm-linux-gnueabihf/libxkbcommon.so.0 (0xb683c000)
	libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6818000)
	librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb6802000)
	/lib/ld-linux-armhf.so.3 (0xb6eed000)
	libpulsecommon-10.0.so => /usr/lib/arm-linux-gnueabihf/pulseaudio/libpulsecommon-10.0.so (0xb679c000)
	libcap.so.2 => /lib/arm-linux-gnueabihf/libcap.so.2 (0xb6788000)
	libdbus-1.so.3 => /lib/arm-linux-gnueabihf/libdbus-1.so.3 (0xb6747000)
	libbsd.so.0 => /lib/arm-linux-gnueabihf/libbsd.so.0 (0xb6724000)
	libxcb.so.1 => /usr/lib/arm-linux-gnueabihf/libxcb.so.1 (0xb66fa000)
	libXrender.so.1 => /usr/lib/arm-linux-gnueabihf/libXrender.so.1 (0xb66e3000)
	libXfixes.so.3 => /usr/lib/arm-linux-gnueabihf/libXfixes.so.3 (0xb66cf000)
	libffi.so.6 => /usr/lib/arm-linux-gnueabihf/libffi.so.6 (0xb66b9000)
	libX11-xcb.so.1 => /usr/lib/arm-linux-gnueabihf/libX11-xcb.so.1 (0xb66a7000)
	libICE.so.6 => /usr/lib/arm-linux-gnueabihf/libICE.so.6 (0xb6686000)
	libSM.so.6 => /usr/lib/arm-linux-gnueabihf/libSM.so.6 (0xb6670000)
	libXtst.so.6 => /usr/lib/arm-linux-gnueabihf/libXtst.so.6 (0xb665c000)
	libsystemd.so.0 => /lib/arm-linux-gnueabihf/libsystemd.so.0 (0xb6603000)
	libwrap.so.0 => /lib/arm-linux-gnueabihf/libwrap.so.0 (0xb65ec000)
	libsndfile.so.1 => /usr/lib/arm-linux-gnueabihf/libsndfile.so.1 (0xb6585000)
	libasyncns.so.0 => /usr/lib/arm-linux-gnueabihf/libasyncns.so.0 (0xb6570000)
	libXau.so.6 => /usr/lib/arm-linux-gnueabihf/libXau.so.6 (0xb6565000)
	libXdmcp.so.6 => /usr/lib/arm-linux-gnueabihf/libXdmcp.so.6 (0xb6551000)
	libuuid.so.1 => /lib/arm-linux-gnueabihf/libuuid.so.1 (0xb653d000)
	libselinux.so.1 => /lib/arm-linux-gnueabihf/libselinux.so.1 (0xb6513000)
	liblzma.so.5 => /lib/arm-linux-gnueabihf/liblzma.so.5 (0xb64e9000)
	liblz4.so.1 => /usr/lib/arm-linux-gnueabihf/liblz4.so.1 (0xb64cd000)
	libgcrypt.so.20 => /lib/arm-linux-gnueabihf/libgcrypt.so.20 (0xb6422000)
	libnsl.so.1 => /lib/arm-linux-gnueabihf/libnsl.so.1 (0xb6402000)
	libFLAC.so.8 => /usr/lib/arm-linux-gnueabihf/libFLAC.so.8 (0xb63b3000)
	libogg.so.0 => /usr/lib/arm-linux-gnueabihf/libogg.so.0 (0xb63a6000)
	libvorbis.so.0 => /usr/lib/arm-linux-gnueabihf/libvorbis.so.0 (0xb6375000)
	libvorbisenc.so.2 => /usr/lib/arm-linux-gnueabihf/libvorbisenc.so.2 (0xb62e3000)
	libresolv.so.2 => /lib/arm-linux-gnueabihf/libresolv.so.2 (0xb62c3000)
	libpcre.so.3 => /lib/arm-linux-gnueabihf/libpcre.so.3 (0xb6264000)
	libgpg-error.so.0 => /lib/arm-linux-gnueabihf/libgpg-error.so.0 (0xb6247000)

Did you manage to boot clockwork OS on QEMU? I also have a armv7 with Debian although I think having the official images as VMs would be really cool. Having a QEMU guide in the forum would be awesome anyway!

no. I have not successfully got the real Image to boot yet. I’m trying to get it sorted. it doesnt use an initrd and qemu is having a small fit with that I think. I should have it sorted soon. also my ldd is identical can you maybe send me your binary? I would like to test yours and if it boots do a DIFF with mine because I literally copied and pasted everything you had and it runs in my armv7l debian image

Also. it seems when I run my binary it locks up the Launcher and I need to reboot the CPI or the launcher.

well. If i remove anything GL it will work. but of course I actually need GL for several things lol. I’m going to dig a little and see if I can figure out why. my Image is a stock 0.30 and it will not run with the GL code in it. I can start on what I want to do like this but I will need GL for things soon enough. I will push forward on my native version and see if i can get the 0.30 to boot in some way. (going to try with the debian 9 armhf kernel & init tomorrow)

IMG_20190319_184342

PS: I LOVE that you chose the yellow shell too.

My work-in-progress PlayStation emulator uses OpenGL 3.3 and works on GameShell on the same 0.3 version: https://github.com/Ruenzuo/ruby

Check the makefile and the way I’m loading the functions, maybe that helps: https://github.com/Ruenzuo/ruby/blob/master/armv7l.mk https://github.com/Ruenzuo/ruby/blob/master/src/Renderer.cpp

My GameShell is white tho, but it looks yellow in that picture, had a hard time deciding between those two, yellow looks awesome.

1 Like