Lab5: User Library

User library

user/ulib.h

user library 因為大部份的函式 kernel 也需要,所以在 kernel file 內也 include 不少

1
2
3
4
5
6
#include "include/syscall.h"
#include "include/stdlib.h"
#include "include/stdio.h"
#include "include/string.h"
#include "include/signal.h"
#include "include/types.h"

user/main.c

簡易的 main function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "user/ulib.h"

void delay(int period)
{
while (period--)
;
}

int main()
{
int count = 0;
while (1) {
printf("[PID %d] Hello world x%d\n", get_taskid(), count++);
delay(10000000);
}
return 0;
}

user/crt0.S

0x0 作為 user program 的起點,跳轉到 main 並在 return 時呼叫 exit 回收資源

1
2
3
4
5
6
.section ".text.entry"

.global _user_entry
_user_entry:
bl main
bl exit // syscall

user/linker.ld

放上 .text.entry symbol 以便在 objdmup 時清楚看到 crt0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SECTIONS
{
. = 0x0;
.text.entry : {
*(.text.entry)
}
.text : {
*(.text)
}
.data : {
*(.data)
}
.bss : {
. = ALIGN(16);
*(.bss .bss.*)
}
}

Embed compiled binary to kernel

將 user program 嵌入到 kernel

  1. 生成 elf

    1
    $(LD) -T $(USER_LINKER_FILE) $^ -o $@
  2. 轉成 raw binary

    1
    $(OBJCPY) -O binary $< $@
  3. 轉成 allocatable binary ,user program loader 得以透過下列 symbol 定位 user program 及取得大小

    • _binary_<input name>_start
    • _binary_<input name>_end
    • _binary_<input name>_size
    1
    $(LD) -r -b binary $< -o $@

kernel/Makefile

加上 -fno-zero-initialized-in-bss 來讓 zero-initialized global variable 放在 .data 而非 .bss section,參考 .bss section:C 語言所種下的因

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
USER_BIN = user/user.bin

# library
lib/%.o: lib/%.c
$(CC) $(CFLAGS) $< -c -o $@

# user
user/%.o: user/%.c
$(CC) $(CFLAGS) -fno-zero-initialized-in-bss $< -c -o $@

user/%.asm.o: user/%.S
$(CC) $(CFLAGS) -fno-zero-initialized-in-bss $< -c -o user/$*.asm.o

user/user.elf: $(LIB_OBJ) $(USER_OBJS)
$(LD) -T $(USER_LINKER_FILE) $^ -o $@

user/user.img: user/user.elf
$(OBJCPY) -O binary $< $@

user/user.bin: user/user.img
$(LD) -r -b binary $< -o $@

#kernel
kernel/kernel8.elf: $(KERNEL_OBJS) $(LIB_OBJ) $(USER_BIN)
$(LD) -T $(KERNEL_LINKER_FILE) $^ -o $@

Raw binary loading and mapping

kernel/demo.c

從 crt0 的起始位置 0x0 開始執行

1
2
3
4
5
6
7
8
9
// kernel task
void required_3_1()
{
extern char _binary_user_user_img_start;
extern char _binary_user_user_img_size;
void *start = (void *) &_binary_user_user_img_start;
ssize_t size = (ssize_t) &_binary_user_user_img_size;
do_exec(start, size, (uint64_t) 0x0);
}

Demo

剛開始執行的時候,一直跳以下錯誤

1
2
3
Exception return address 0x01010
Exception class (EC) 0x20
Instruction specific synfrome (ISS) 0xC

檢查 page table 許久找不到原因。後來看到 main 的地址 0x1010 想到是 user image 太大導致 do_exec 複製超出預先分配的 4KB page (連結的 user library 超級大),main 自然無法存取到。至於 EC 和 ISS 為何查 ARM manual 結果是 level 0 permission denied 就不曉得…

1
2
3
0000000000001362 D _binary_user_user_img_end
0000000000001362 A _binary_user_user_img_size
0000000000000000 D _binary_user_user_img_start
1
2
3
4
5
6
7
0000000000000000 <_user_entry>:
.section ".text.entry"

.global _user_entry
_user_entry:
bl main
0: 94000404 bl 1010 <main>

解決的方法是多分配一點 page

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
31
32
33
34
35
36
37
38
39
40
41
42
void do_exec(void (*func)(), size_t size, uint64_t pc)
{
/*
* If the task was created by privilege_task_create() and hasn't initlized
* page table, the default value of TTBR0_EL1 is 0x0
*/
const task_t *task = get_current();
uintptr_t sp =
USER_VIRT_TOP -
sizeof(uintptr_t); // stack address grows towards lower memory address

// allocate pages for user program
size_t upper_bound = ROUNDUP(size, PAGE_SIZE);
for (size_t offset = 0; offset < upper_bound;
offset += PAGE_SIZE, size -= PAGE_SIZE) {
void *utext = map_addr_user((void *) (pc + offset));
if (!utext) {
goto _do_exec_err;
}
memcpy(utext, (char *) func + offset, MIN(size, PAGE_SIZE));
}

// assume stack size < 4KB
void *ustack = map_addr_user((void *) sp);
if (!ustack) {
goto _do_exec_err;
}

// switch to el0
update_pgd(task->mm.pgd);
asm volatile(
"msr sp_el0, %0\n\t"
"msr elr_el1, %1\n\t"
"msr spsr_el1, %2\n\t"
"eret" ::"r"(sp),
"r"(pc), "r"(SPSR_EL1_VALUE));

_do_exec_err:
// TODO: reclaim all pages

return;
}

跑起來畫面如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[PID 2] Hello world x35
[PID 2] Hello world x36
[PID 2] Hello world x37
[PID 2] Hello world x38
[PID 2] Hello world x39
[PID 2] Hello world x40
[PID 2] Hello world x41
[PID 2] Hello world x42
[PID 2] Hello world x43
[PID 2] Hello world x44
[PID 2] Hello world x45
[PID 2] Hello world x46
[PID 2] Hello world x47
[PID 2] Hello world x48
[PID 2] Hello world x49
[PID 2] Hello world x50
[PID 2] Hello world x51

Reference