1.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.lds
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 <main>:
   0:	ff010113          	addi	sp,sp,-16
   4:	00200593          	addi	a1,zero,2
   8:	00100513          	addi	a0,zero,1
   c:	00112623          	sw	ra,12(sp)
  10:	154000ef          	jal	ra,164 <print_hex>
  14:	00200593          	addi	a1,zero,2
  18:	00100513          	addi	a0,zero,1
  1c:	148000ef          	jal	ra,164 <print_hex>
  20:	00200593          	addi	a1,zero,2
  24:	00058513          	addi	a0,a1,0
  28:	13c000ef          	jal	ra,164 <print_hex>
  2c:	00200593          	addi	a1,zero,2
  30:	00300513          	addi	a0,zero,3
  34:	130000ef          	jal	ra,164 <print_hex>
  38:	00200593          	addi	a1,zero,2
  3c:	00500513          	addi	a0,zero,5
  40:	124000ef          	jal	ra,164 <print_hex>
  44:	00200593          	addi	a1,zero,2
  48:	00800513          	addi	a0,zero,8
  4c:	118000ef          	jal	ra,164 <print_hex>
  50:	00200593          	addi	a1,zero,2
  54:	00d00513          	addi	a0,zero,13
  58:	10c000ef          	jal	ra,164 <print_hex>
  5c:	00200593          	addi	a1,zero,2
  60:	01500513          	addi	a0,zero,21
  64:	100000ef          	jal	ra,164 <print_hex>
  68:	0000006f          	jal	zero,68 <main+0x68>

0000006c <print_chr>:
  6c:	800007b7          	lui	a5,0x80000
  70:	00a7a023          	sw	a0,0(a5) # 80000000 <print_hex+0x7ffffe9c>
  74:	00008067          	jalr	zero,0(ra)

00000078 <print_str>:
  78:	80000737          	lui	a4,0x80000
  7c:	00054783          	lbu	a5,0(a0)
  80:	00079463          	bne	a5,zero,88 <print_str+0x10>
  84:	00008067          	jalr	zero,0(ra)
  88:	00150513          	addi	a0,a0,1
  8c:	00f72023          	sw	a5,0(a4) # 80000000 <print_hex+0x7ffffe9c>
  90:	fedff06f          	jal	zero,7c <print_str+0x4>

00000094 <_print_dec>:
  94:	ff010113          	addi	sp,sp,-16
  98:	00812423          	sw	s0,8(sp)
  9c:	00112623          	sw	ra,12(sp)
  a0:	00050413          	addi	s0,a0,0
  a4:	00900793          	addi	a5,zero,9
  a8:	00000513          	addi	a0,zero,0
  ac:	0287ec63          	bltu	a5,s0,e4 <_print_dec+0x50>
  b0:	04a7e063          	bltu	a5,a0,f0 <_print_dec+0x5c>
  b4:	03050513          	addi	a0,a0,48
  b8:	0ff57513          	andi	a0,a0,255
  bc:	800007b7          	lui	a5,0x80000
  c0:	00a7a023          	sw	a0,0(a5) # 80000000 <print_hex+0x7ffffe9c>
  c4:	03040413          	addi	s0,s0,48
  c8:	0ff47413          	andi	s0,s0,255
  cc:	800007b7          	lui	a5,0x80000
  d0:	00c12083          	lw	ra,12(sp)
  d4:	0087a023          	sw	s0,0(a5) # 80000000 <print_hex+0x7ffffe9c>
  d8:	00812403          	lw	s0,8(sp)
  dc:	01010113          	addi	sp,sp,16
  e0:	00008067          	jalr	zero,0(ra)
  e4:	00150513          	addi	a0,a0,1
  e8:	ff640413          	addi	s0,s0,-10
  ec:	fc1ff06f          	jal	zero,ac <_print_dec+0x18>
  f0:	fa5ff0ef          	jal	ra,94 <_print_dec>
  f4:	fd1ff06f          	jal	zero,c4 <_print_dec+0x30>

000000f8 <print_dec>:
  f8:	ff010113          	addi	sp,sp,-16
  fc:	00812423          	sw	s0,8(sp)
 100:	00112623          	sw	ra,12(sp)
 104:	00050413          	addi	s0,a0,0
 108:	00900793          	addi	a5,zero,9
 10c:	00000513          	addi	a0,zero,0
 110:	0487e063          	bltu	a5,s0,150 <print_dec+0x58>
 114:	04a7e463          	bltu	a5,a0,15c <print_dec+0x64>
 118:	03050513          	addi	a0,a0,48
 11c:	0ff57513          	andi	a0,a0,255
 120:	800007b7          	lui	a5,0x80000
 124:	00a7a023          	sw	a0,0(a5) # 80000000 <print_hex+0x7ffffe9c>
 128:	03040413          	addi	s0,s0,48
 12c:	0ff47413          	andi	s0,s0,255
 130:	800007b7          	lui	a5,0x80000
 134:	0087a023          	sw	s0,0(a5) # 80000000 <print_hex+0x7ffffe9c>
 138:	00c12083          	lw	ra,12(sp)
 13c:	00812403          	lw	s0,8(sp)
 140:	00a00713          	addi	a4,zero,10
 144:	00e7a023          	sw	a4,0(a5)
 148:	01010113          	addi	sp,sp,16
 14c:	00008067          	jalr	zero,0(ra)
 150:	00150513          	addi	a0,a0,1
 154:	ff640413          	addi	s0,s0,-10
 158:	fb9ff06f          	jal	zero,110 <print_dec+0x18>
 15c:	f39ff0ef          	jal	ra,94 <_print_dec>
 160:	fc9ff06f          	jal	zero,128 <print_dec+0x30>

00000164 <print_hex>:
 164:	04058263          	beq	a1,zero,1a8 <print_hex+0x44>
 168:	00259593          	slli	a1,a1,0x2
 16c:	ffc58593          	addi	a1,a1,-4
 170:	000016b7          	lui	a3,0x1
 174:	80000637          	lui	a2,0x80000
 178:	0005d863          	bge	a1,zero,188 <print_hex+0x24>
 17c:	00001537          	lui	a0,0x1
 180:	c0050513          	addi	a0,a0,-1024 # c00 <print_hex+0xa9c>
 184:	ef5ff06f          	jal	zero,78 <print_str>
 188:	00b55733          	srl	a4,a0,a1
 18c:	c0468793          	addi	a5,a3,-1020 # c04 <print_hex+0xaa0>
 190:	00f77713          	andi	a4,a4,15
 194:	00e787b3          	add	a5,a5,a4
 198:	0007c783          	lbu	a5,0(a5)
 19c:	ffc58593          	addi	a1,a1,-4
 1a0:	00f62023          	sw	a5,0(a2) # 80000000 <print_hex+0x7ffffe9c>
 1a4:	fd5ff06f          	jal	zero,178 <print_hex+0x14>
 1a8:	00008067          	jalr	zero,0(ra)

Disassembly of section .rodata.str1.4:

00000c00 <.rodata.str1.4>:
 c00:	000a                	.insn	2, 0x000a
 c02:	0000                	.insn	2, 0x
 c04:	3130                	.insn	2, 0x3130
 c06:	3332                	.insn	2, 0x3332
 c08:	3534                	.insn	2, 0x3534
 c0a:	3736                	.insn	2, 0x3736
 c0c:	3938                	.insn	2, 0x3938
 c0e:	4241                	.insn	2, 0x4241
 c10:	46454443          	.insn	4, 0x46454443
	...

The objdumps

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

objdump of start.o

start.o:     file format elf32-littleriscv
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         00000090  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  4 .riscv.attributes 0000001a  00000000  00000000  000000c4  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
0000008c l       .init	00000000 end
00000000 l    d  .riscv.attributes	00000000 .riscv.attributes
00000000 g       .init	00000000 start
00000000         *UND*	00000000 main


RELOCATION RECORDS FOR [.init]:
OFFSET   TYPE              VALUE
00000080 R_RISCV_JAL       main
00000084 R_RISCV_JAL       end
0000008c R_RISCV_JAL       end
objdump of print.o

print.o:     file format elf32-littleriscv
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         0000014c  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000180  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000180  2**0
                  ALLOC
  3 .rodata.str1.4 00000015  00000000  00000000  00000180  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000001a  00000000  00000000  00000195  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000001af  2**0
                  CONTENTS, READONLY
  6 .riscv.attributes 0000001c  00000000  00000000  000001af  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	00000068 _print_dec
00000090 g     F .text	00000070 print_dec
00000100 g     F .text	0000004c print_hex


RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE
00000014 R_RISCV_BRANCH    .L4
00000024 R_RISCV_JAL       .L3
00000040 R_RISCV_BRANCH    .L7
00000044 R_RISCV_BRANCH    .L8
00000080 R_RISCV_JAL       .L6
00000084 R_RISCV_CALL_PLT  _print_dec
00000084 R_RISCV_RELAX     *ABS*
0000008c R_RISCV_JAL       .L9
000000a8 R_RISCV_BRANCH    .L13
000000ac R_RISCV_BRANCH    .L14
000000f0 R_RISCV_JAL       .L12
000000f4 R_RISCV_CALL_PLT  _print_dec
000000f4 R_RISCV_RELAX     *ABS*
000000fc R_RISCV_JAL       .L15
00000100 R_RISCV_BRANCH    .L17
0000010c R_RISCV_HI20      .LC0
0000010c R_RISCV_RELAX     *ABS*
00000114 R_RISCV_BRANCH    .L20
00000118 R_RISCV_HI20      .LC1
00000118 R_RISCV_RELAX     *ABS*
0000011c R_RISCV_LO12_I    .LC1
0000011c R_RISCV_RELAX     *ABS*
00000120 R_RISCV_CALL_PLT  print_str
00000120 R_RISCV_RELAX     *ABS*
0000012c R_RISCV_LO12_I    .LC0
0000012c R_RISCV_RELAX     *ABS*
00000144 R_RISCV_JAL       .L19
objdump of firmware.o

firmware.o:     file format elf32-littleriscv
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 .text.startup 0000008c  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  4 .comment      0000001a  00000000  00000000  000000c0  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000000da  2**0
                  CONTENTS, READONLY
  6 .riscv.attributes 0000001c  00000000  00000000  000000da  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  .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	0000008c main
00000000         *UND*	00000000 print_hex


RELOCATION RECORDS FOR [.text.startup]:
OFFSET   TYPE              VALUE
00000010 R_RISCV_CALL_PLT  print_hex
00000010 R_RISCV_RELAX     *ABS*
00000020 R_RISCV_CALL_PLT  print_hex
00000020 R_RISCV_RELAX     *ABS*
00000030 R_RISCV_CALL_PLT  print_hex
00000030 R_RISCV_RELAX     *ABS*
00000040 R_RISCV_CALL_PLT  print_hex
00000040 R_RISCV_RELAX     *ABS*
00000050 R_RISCV_CALL_PLT  print_hex
00000050 R_RISCV_RELAX     *ABS*
00000060 R_RISCV_CALL_PLT  print_hex
00000060 R_RISCV_RELAX     *ABS*
00000070 R_RISCV_CALL_PLT  print_hex
00000070 R_RISCV_RELAX     *ABS*
00000080 R_RISCV_CALL_PLT  print_hex
00000080 R_RISCV_RELAX     *ABS*
00000088 R_RISCV_JAL       .L2