world leader in high performance signal processing
Trace: » simple_hello_world_application_example_asm

Simple Hello World Application Example (in assembly)

This section shows how to write a simple “Hello, World” Linux application in Blackfin assembly. For details on how to actually compile this code and run it on a Blackfin board, please read the first Simple Hello World Application Example.

Due to the way data and functions are referenced differently in the binary formats FLAT and FDPIC ELF, we will present a few examples.

Note that we deliberately skim over the inner details of FDPIC ELF here. For in-depth details, please check out the FDPIC ELF page.

If you plan on writing assembly code, make sure you adhere to the Blackfin ABI.

C Code

Here is what Hello, World would look like if it were written in C. The assembly examples will behave exactly like this code.

#include <stdio.h>
int main()
{
	printf("Hello, World\n");
	return 0;
}

Assembly Code (FLAT)

Since the FLAT format statically links all code and data together in one object, we use absolute references for both data and functions. At runtime, the kernel will automatically process all these relocations and replace the code call _printf with call 0x412959AE.

The limitation here is that code segments cannot be shared at runtime between processes.

.section .rodata
.align 4
 
hello_string: .string   "Hello, World\n"
 
 
.text
.align 4
 
.global _main;
.type _main, STT_FUNC;
_main:
	/* Blackfin requires at least 12 bytes when calling functions */
	LINK 12;
 
	/* load up address of the string */
	R0.L = hello_string;
	R0.H = hello_string;
 
	/* call printf ! */
	call _printf;
 
	UNLINK;
	RTS;
.size _main,.-_main;

To compile:

bfin-uclinux-gcc -Wl,-elf2flt test.S -o test

Assembly Code (FDPIC ELF 1)

Since the FDPIC ELF format is designed from the ground up to share read-only sections between different processes, we need to make sure that code can be run independent of its position in memory. In order to access writable data that cannot be shared, each process needs its own table of pointers (called the GOT). In the following example we see how to access data via the GOT.

This example uses an absolute reference to the printf function; this works because the assembler/linker automatically inserts a trampoline to accessing the correct location of printf.

.section .rodata
.align 4
 
hello_string: .string   "Hello, World\n"
 
 
.text
.align 4
 
.global _main;
.type _main, STT_FUNC;
_main:
	/* Blackfin requires at least 12 bytes when calling functions */
	LINK 12;
 
	/* load up address of the string */
	R0 = [P3 + hello_string@GOT17M4];
 
	/* call printf ! */
	call _printf;
 
	UNLINK;
	RTS;
.size _main,.-_main;

To compile:

bfin-linux-uclibc-gcc test.S -o test

For the curious, the 17M4 suffix comes from common Blackfin conventions: divide by 4 and 16 bits of relocation information

Assembly Code (FDPIC ELF 2)

This example is just like the one above except for the way we call printf. Previously, we just let the assembler/linker take care of the gory details, but now for fun we'll go ahead and look up the printf function ourselves and then call it.

.section .rodata
.align 4
 
hello_string: .string   "Hello, World\n"
 
 
.text
.align 4
 
.global _main;
.type _main, STT_FUNC;
_main:
	/* Blackfin requires at least 12 bytes when calling functions */
	LINK 12;
 
	/* load up address of the string */
	R0 = [P3 + hello_string@GOT17M4];
 
	/* load up address of the printf func descriptor */
	P0 = [P3 + _printf@FUNCDESC_GOT17M4];
	/* load up the address of the actual function */
	P1 = [P0];
	/* load up the address of the GOT for printf */
	P3 = [P0+4];
	/* call printf ! */
	call (P1);
 
	UNLINK;
	RTS;
.size _main,.-_main;

To compile:

bfin-linux-uclibc-gcc test.S -o test

Complete Table of Contents/Topics