3 - Linking Bare Metal

With the three object files generated, it is time to stich everything togehter. These object files are compiled, so they are converted to machine code. They are organised in sections and they all start at address 0x0. (At the bottom of this page, three object dumps are found so you can verify for yourself.) The image below shows which object file contains which sections. As mentioned before, it is the linker’s job to do the stiching. A recipe for this can be provided in the form of a linker script.

sections


The linker script shown here, first states that the entry point is the label start.

Then the available memories are defined. In the case of a RISC processor, the architecture is a Harvard architecture (in contrast to the von Neumann architecture that is typically found in CISC processors). This implies that the memories for data and instructions are split. In the definition, two memories are made to facilitate this:

  • a ROM:
    • with read-and-execute-rights
    • 2048 bytes in size
    • starts at address 0x0
  • a RAM:
    • with read-write-and-execute-rights
    • 2048 bytes in size
    • starts at address 0x800 (2048)
firmware.ld
ENTRY(start)

MEMORY 
{
	ROM (rx) : ORIGIN = 0x00000000, LENGTH = 2k
	RAM (rwx) : ORIGIN = 0x00000800, LENGTH = 2k
}
SECTIONS {
	/* this part has to go into the IMEM */
	.text : {
		. = 0x000000;
		*(.init);
		*(.text*);
	} > ROM

	/* this part has to go into the DMEM */
	.data : {
        . = 0x000;
		*(.data);
	} > RAM

	.rodata : {
        . = 0x400;
		*(.rodata);
		. = ALIGN(4);
		FILL(0xFF)
	} > RAM

	/DISCARD/ :
    {
        *(.note*);
        *(.iplt*);
        *(.igot*);
        *(.rel*);
        *(.comment);
        *(.riscv.attributes);
    }
}

It’s worth pointing out the difference between kB and KiB: the base for kB is 10, and the base for KiB is 2.
Back-in-the-day it was custom to state that there are 1024 bytes in a kilobyte. The reason for using 1024 is that this is the power of 2 that is closest to 1000.
This, however, lead to confusion. After all, there are only 1000 meters in a kilometer. To avoid this ambiguity, in 1998 an new set of binary prefixes was introduced. Everything that uses a base 2 got a new prefix.
The current defintions hence are: 1000 bytes in a kilobyte and 1024 bytes in a kibibyte.

Similar definitions hold for: mebibyte (MiB), gibibyte (GiB), tebibyte (TiB), … e.g. 1 TB = 1'000'000'000 bytes and 1 TiB = 1'099'511'627'776 bytes

After the memories are defined, the script dictates which sections have to put where.

  • The text section

    • starts at address 0x0
    • appends the .init sections that can be found in all the object files
    • appends the .text* sections that can be found in all the object files
      • note the trailing wildcard!
    • => is appended to the ROM memory
  • The data section

    • starts at address 0x0
    • appends the .data sections that can be found in all the object files
    • => is appended to the RAM memory
  • The rodata section

    • starts at address 0x400 (1024)
    • appends the .rodata sections that can be found in all the object files
    • aligns to 4 bytes, while filling with 0xFF
    • => is appended to the RAM memory
  • dicards other sections

linker

The image below shows the final picture, for this exercise. With these 5 files (1 header file, 3 C files and a linker script) the final binary file can be generated. This binary is in the Executable and Linkable Format (.elf)

compile4

Making a human-readable format of the .elf

After running the tool chain, an .elf file is generated. It might become useful to understand what is going on in this file (as humans). Unfortunately, chances are that a hardware designer needs this to figure out what the implemented processor is doing.

The generated binary file can be disassembled again. Doing this allows to read what was eventually generated by the compiler. Againthe the riscv32-unknown-elf-objdump program can come to our rescue. The object dumper can be used to disassemble the .elf by using the -D option.

The disassembly of the .elf file shows all the instructions with the address, the machine code of the instruction and the human readable mnemonic.

objdump of firmware.elf

firmware.elf:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <start>:
   0:	00000093          	addi	ra,zero,0
   4:	00000113          	addi	sp,zero,0
   8:	00000193          	addi	gp,zero,0
   c:	00000213          	addi	tp,zero,0
  10:	00000293          	addi	t0,zero,0
  14:	00000313          	addi	t1,zero,0
  18:	00000393          	addi	t2,zero,0
  1c:	00000413          	addi	s0,zero,0
  20:	00000493          	addi	s1,zero,0
  24:	00000513          	addi	a0,zero,0
  28:	00000593          	addi	a1,zero,0
  2c:	00000613          	addi	a2,zero,0
  30:	00000693          	addi	a3,zero,0
  34:	00000713          	addi	a4,zero,0
  38:	00000793          	addi	a5,zero,0
  3c:	00000813          	addi	a6,zero,0
  40:	00000893          	addi	a7,zero,0
  44:	00000913          	addi	s2,zero,0
  48:	00000993          	addi	s3,zero,0
  4c:	00000a13          	addi	s4,zero,0
  50:	00000a93          	addi	s5,zero,0
  54:	00000b13          	addi	s6,zero,0
  58:	00000b93          	addi	s7,zero,0
  5c:	00000c13          	addi	s8,zero,0
  60:	00000c93          	addi	s9,zero,0
  64:	00000d13          	addi	s10,zero,0
  68:	00000d93          	addi	s11,zero,0
  6c:	00000e13          	addi	t3,zero,0
  70:	00000e93          	addi	t4,zero,0
  74:	00000f13          	addi	t5,zero,0
  78:	00000f93          	addi	t6,zero,0
  7c:	00001137          	lui	sp,0x1
  80:	008000ef          	jal	ra,88 <main>

00000084 <done>:
  84:	0000006f          	jal	zero,84 <done>

00000088 <main>:
  88:	00001537          	lui	a0,0x1
  8c:	ff010113          	addi	sp,sp,-16 # ff0 <print_hex+0xf1c>
  90:	c0050513          	addi	a0,a0,-1024 # c00 <print_hex+0xb2c>
  94:	00112623          	sw	ra,12(sp)
  98:	020000ef          	jal	ra,b8 <print_str>
  9c:	00c12083          	lw	ra,12(sp)
  a0:	00000513          	addi	a0,zero,0
  a4:	01010113          	addi	sp,sp,16
  a8:	00008067          	jalr	zero,0(ra)

000000ac <print_chr>:
  ac:	800007b7          	lui	a5,0x80000
  b0:	00a7a023          	sw	a0,0(a5) # 80000000 <print_hex+0x7fffff2c>
  b4:	00008067          	jalr	zero,0(ra)

000000b8 <print_str>:
  b8:	80000737          	lui	a4,0x80000
  bc:	00054783          	lbu	a5,0(a0)
  c0:	00079463          	bne	a5,zero,c8 <print_str+0x10>
  c4:	00008067          	jalr	zero,0(ra)
  c8:	00150513          	addi	a0,a0,1
  cc:	00f72023          	sw	a5,0(a4) # 80000000 <print_hex+0x7fffff2c>
  d0:	fedff06f          	jal	zero,bc <print_str+0x4>

000000d4 <print_hex>:
  d4:	fff58593          	addi	a1,a1,-1
  d8:	00259593          	slli	a1,a1,0x2
  dc:	000016b7          	lui	a3,0x1
  e0:	80000637          	lui	a2,0x80000
  e4:	0005da63          	bge	a1,zero,f8 <print_hex+0x24>
  e8:	800007b7          	lui	a5,0x80000
  ec:	00a00713          	addi	a4,zero,10
  f0:	00e7a023          	sw	a4,0(a5) # 80000000 <print_hex+0x7fffff2c>
  f4:	00008067          	jalr	zero,0(ra)
  f8:	00b55733          	srl	a4,a0,a1
  fc:	c1068793          	addi	a5,a3,-1008 # c10 <print_hex+0xb3c>
 100:	0ff77713          	andi	a4,a4,255
 104:	00e787b3          	add	a5,a5,a4
 108:	0007c783          	lbu	a5,0(a5)
 10c:	ffc58593          	addi	a1,a1,-4
 110:	00f62023          	sw	a5,0(a2) # 80000000 <print_hex+0x7fffff2c>
 114:	fd1ff06f          	jal	zero,e4 <print_hex+0x10>

Disassembly of section .rodata.str1.4:

00000c00 <.rodata.str1.4>:
 c00:	6548                	.insn	2, 0x6548
 c02:	6c6c                	.insn	2, 0x6c6c
 c04:	6f77206f          	jal	zero,73afa <print_hex+0x73a26>
 c08:	6c72                	.insn	2, 0x6c72
 c0a:	2164                	.insn	2, 0x2164
 c0c:	2121                	.insn	2, 0x2121
 c0e:	000a                	.insn	2, 0x000a
 c10:	3130                	.insn	2, 0x3130
 c12:	3332                	.insn	2, 0x3332
 c14:	3534                	.insn	2, 0x3534
 c16:	3736                	.insn	2, 0x3736
 c18:	3938                	.insn	2, 0x3938
 c1a:	4241                	.insn	2, 0x4241
 c1c:	46454443          	.insn	4, 0x46454443
	...

The objdumps

Below are the object dumps of start.o, print.o, and firmware.o.

objdump of start.o

build/start.o:     file format elf32-littleriscv
build/start.o
architecture: riscv:rv32, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000000  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000034  2**0
                  ALLOC
  3 .init         00000088  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  4 .riscv.attributes 0000001a  00000000  00000000  000000bc  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l    d  .init	00000000 .init
00000084 l       .init	00000000 done
00000000 l    d  .riscv.attributes	00000000 .riscv.attributes
00000000 g       .init	00000000 start
00000000         *UND*	00000000 main



Disassembly of section .init:

00000000 <start>:
   0:	00000093          	li	ra,0
   4:	00000113          	li	sp,0
   8:	00000193          	li	gp,0
   c:	00000213          	li	tp,0
  10:	00000293          	li	t0,0
  14:	00000313          	li	t1,0
  18:	00000393          	li	t2,0
  1c:	00000413          	li	s0,0
  20:	00000493          	li	s1,0
  24:	00000513          	li	a0,0
  28:	00000593          	li	a1,0
  2c:	00000613          	li	a2,0
  30:	00000693          	li	a3,0
  34:	00000713          	li	a4,0
  38:	00000793          	li	a5,0
  3c:	00000813          	li	a6,0
  40:	00000893          	li	a7,0
  44:	00000913          	li	s2,0
  48:	00000993          	li	s3,0
  4c:	00000a13          	li	s4,0
  50:	00000a93          	li	s5,0
  54:	00000b13          	li	s6,0
  58:	00000b93          	li	s7,0
  5c:	00000c13          	li	s8,0
  60:	00000c93          	li	s9,0
  64:	00000d13          	li	s10,0
  68:	00000d93          	li	s11,0
  6c:	00000e13          	li	t3,0
  70:	00000e93          	li	t4,0
  74:	00000f13          	li	t5,0
  78:	00000f93          	li	t6,0
  7c:	00001137          	lui	sp,0x1
  80:	f81ff0ef          	jal	0 <start>
			80: R_RISCV_JAL	main

00000084 <done>:
  84:	0000006f          	j	84 <done>
			84: R_RISCV_JAL	done
objdump of print.o

build/print.o:     file format elf32-littleriscv
build/print.o
architecture: riscv:rv32, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000006c  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  000000a0  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  000000a0  2**0
                  ALLOC
  3 .rodata.str1.4 00000011  00000000  00000000  000000a0  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000001a  00000000  00000000  000000b1  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000000cb  2**0
                  CONTENTS, READONLY
  6 .riscv.attributes 0000001c  00000000  00000000  000000cb  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*	00000000 print.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l    d  .rodata.str1.4	00000000 .rodata.str1.4
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .comment	00000000 .comment
00000000 l    d  .riscv.attributes	00000000 .riscv.attributes
00000000 g     F .text	0000000c print_chr
0000000c g     F .text	0000001c print_str
00000028 g     F .text	00000044 print_hex



Disassembly of section .text:

00000000 <print_chr>:
   0:	800007b7          	lui	a5,0x80000
   4:	00a7a023          	sw	a0,0(a5) # 80000000 <.L7+0x7fffffb4>
   8:	00008067          	ret

0000000c <print_str>:
   c:	80000737          	lui	a4,0x80000

00000010 <.L3>:
  10:	00054783          	lbu	a5,0(a0)
  14:	00079463          	bnez	a5,1c <.L4>
			14: R_RISCV_BRANCH	.L4
  18:	00008067          	ret

0000001c <.L4>:
  1c:	00150513          	addi	a0,a0,1
  20:	00f72023          	sw	a5,0(a4) # 80000000 <.L7+0x7fffffb4>
  24:	fedff06f          	j	10 <.L3>
			24: R_RISCV_JAL	.L3

00000028 <print_hex>:
  28:	fff58593          	addi	a1,a1,-1
  2c:	00259593          	slli	a1,a1,0x2
  30:	000006b7          	lui	a3,0x0
			30: R_RISCV_HI20	.LC0
			30: R_RISCV_RELAX	*ABS*
  34:	80000637          	lui	a2,0x80000

00000038 <.L6>:
  38:	0005da63          	bgez	a1,4c <.L7>
			38: R_RISCV_BRANCH	.L7
  3c:	800007b7          	lui	a5,0x80000
  40:	00a00713          	li	a4,10
  44:	00e7a023          	sw	a4,0(a5) # 80000000 <.L7+0x7fffffb4>
  48:	00008067          	ret

0000004c <.L7>:
  4c:	00b55733          	srl	a4,a0,a1
  50:	00068793          	mv	a5,a3
			50: R_RISCV_LO12_I	.LC0
			50: R_RISCV_RELAX	*ABS*
  54:	0ff77713          	zext.b	a4,a4
  58:	00e787b3          	add	a5,a5,a4
  5c:	0007c783          	lbu	a5,0(a5)
  60:	ffc58593          	addi	a1,a1,-4
  64:	00f62023          	sw	a5,0(a2) # 80000000 <.L7+0x7fffffb4>
  68:	fd1ff06f          	j	38 <.L6>
			68: R_RISCV_JAL	.L6
objdump of firmware.o

build/firmware.o:     file format elf32-littleriscv
build/firmware.o
architecture: riscv:rv32, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000000  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000034  2**0
                  ALLOC
  3 .rodata.str1.4 00000010  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .text.startup 00000028  00000000  00000000  00000044  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  5 .comment      0000001a  00000000  00000000  0000006c  2**0
                  CONTENTS, READONLY
  6 .note.GNU-stack 00000000  00000000  00000000  00000086  2**0
                  CONTENTS, READONLY
  7 .riscv.attributes 0000001c  00000000  00000000  00000086  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*	00000000 firmware.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l    d  .rodata.str1.4	00000000 .rodata.str1.4
00000000 l    d  .text.startup	00000000 .text.startup
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .comment	00000000 .comment
00000000 l    d  .riscv.attributes	00000000 .riscv.attributes
00000000 g     F .text.startup	00000028 main
00000000         *UND*	00000000 print_str



Disassembly of section .text.startup:

00000000 <main>:
   0:	00000537          	lui	a0,0x0
			0: R_RISCV_HI20	.LC0
			0: R_RISCV_RELAX	*ABS*
   4:	ff010113          	addi	sp,sp,-16
   8:	00050513          	mv	a0,a0
			8: R_RISCV_LO12_I	.LC0
			8: R_RISCV_RELAX	*ABS*
   c:	00112623          	sw	ra,12(sp)
  10:	00000097          	auipc	ra,0x0
			10: R_RISCV_CALL_PLT	print_str
			10: R_RISCV_RELAX	*ABS*
  14:	000080e7          	jalr	ra # 10 <main+0x10>
  18:	00c12083          	lw	ra,12(sp)
  1c:	00000513          	li	a0,0
  20:	01010113          	addi	sp,sp,16
  24:	00008067          	ret