Lab1: Basic Initialization

Requirement:

  1. Let only one core proceed, and let others enter a busy loop.
  2. Initialize the BSS segment.
  3. Set the stack pointer to an appropriate position.

解析 start.s

start.s 符合上述要求,解析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.section ".text.boot"

.global _start

_start:
// read cpu id, stop slave cores
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
// cpu id > 0, stop
1: wfe
b 1b
2: // cpu id == 0

// set top of stack just before our code (stack grows to a lower address per AAPCS64)
ldr x1, =_start
mov sp, x1

// clear bss
ldr x1, =__bss_start
ldr w2, =__bss_size
3: cbz w2, 4f
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, 3b

// jump to C code, should not return
4: bl main
// for failsafe, halt this core too
b 1b
  • MRS Xd, <system register>: 將system register內容寫入至Xd (d=0-30),Xd為AArch64提供的31個64-bit general purpose register。用Wd存取Xd的低32-bit。
  • CBZ Xn, label: 用來測試ALU指令運算後的flags是否為0,含0則跳躍至labelCNBZ是相反的情況。
  • WFE: 在競爭相同資源的場景中,使CPU進入STANDBTWFE省電狀態,由其他CPU執行SEV指令喚醒所有在STANDBTWFE狀態的CPU。
  • B label: 跳躍至label
  • BL label: 複製下一個指令的地址到r14 (Link Register),同時跳躍至label
  • LDR W0/X0, [<address>]:載入(load)到X0/W0,寫入(store)對應到STR。定址模式參考ARM Cortex-A Series Programmer’s Guide for ARMv8的Index modes章節。
  • sp: stack pointer
  • 1f, 1b,...Nb,Nf: Symbol Names1f為往該指令前找1的label,1b往該指令後找1的label。

我們的目標是希望GPU在載入kernel8.img到記憶體(0x80000)之後,各個CPU讀取同一份kernel時依據CPU ID(mpidr_el1)來決定執行流程。

  1. 看到CPU ID>0時跑進無窮迴圈:

    1
    2
    1:  wfe
    b 1b
  2. CPU ID=0初始化stack並呼叫main (雖然題目只有要求初始化stack pointer),在此之前需要曉得ARM stack frame是如何佈局。

如果要在組語中用到 #include 之類的還得先把副檔名改成大寫 .S

You can use the GNU C compiler driver to get other “CPP” style preprocessing, by giving the input file a ’.S’ suffix. See section `Options Controlling the Kind of Output’ in Using GNU CC.

Stack Frame Layout

X86

呼叫函數時進行以下動作

  1. push arguments
  2. push eip(return address)
  3. push ebp
  4. mov ebp, esp:esp設為當前frame pointer
  5. sub esp, n:預留n bytes給local variable
1
2
3
4
5
6
7
8
9
-stack bottom-
Function arg N +4N(%ebp)
Function arg 2 +12(%ebp)
Function arg 1 +8(%ebp)
Return Address(saved eip) +4(%ebp)
Old EBP Value (%ebp)
Local Variable -4(%ebp)
(%esp)
-stack top-

Stack保存了argument、frame pointer、local variable、return address。

ARM

  • 使用B系列指令跳躍,其中BL指令將return address儲存在LR register (Link Register)
  • 參數透過 R0~R7 傳入,如果超過8個,就要從後面的參數開始 push stack,最後返回透過 R0~R1。
1
2
3
4
5
6
7
8
9
10
11
12
Register
R0: 參數1
R1: 參數2
(略)
R7: 參數8
LR: return address

-stack bottom-
參數 11
參數 10
參數 9
-stack top-

記憶體佈局

因此,在沒有參數、沒local variable的情況下,預期進到main的記憶體佈局:

1
2
3
4
5
6
7
Register
LR: return addreas
FP: unknown (只有要求設定SP register)
SP: _start (從_start往下增長)

Bottom of stack (fp)
Top of stack (sp)

所以start.s分成三步驟進行

  1. 設定sp
  2. __bss_start為起點、8 bytes為單位初始化。
1
2
3
4
5
6
7
	  // clear bss
ldr x1, =__bss_start
ldr w2, =__bss_size
3: cbz w2, 4f // w2 = 0?
str xzr, [x1], #8 // *x1 = 0; x1+=8; (Post-index addressing modes)
sub w2, w2, #1 // w2 = w2-1
cbnz w2, 3b // w2 = 0?
  1. 最後用BL指令設定LR register。

Linker script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SECTIONS
{
. = 0x80000;
.text : {
KEEP(*(.text.boot))
*(.text .text.*)
}
.data : {
*(.data .data.*)
}
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
__bss_end = .;
}
_end = .;
}
__bss_size = (__bss_end - __bss_start)>>3;

NOLOAD: Mark a section to not be loaded at run time. (REF: Optional Section Attributes)

KEEP: Mark sections that should not be eliminated. (REF: Input Section and Garbage Collection)

Makefile

1
CFLAGS = -Wall -I include -c -ffreestanding -O2 -nostdinc -nostdlib -nostartfiles

nostdinc: Do not use the C library or system libraries tightly coupled with it when linking. (REF:Options for Linking)

nostdlib: Do not use the standard system startup files or libraries when linking.

nostartfiles: Do not use the standard system startup files when linking.

References