Skip to content

Commit

Permalink
Move kernel stack to the bottom of RAM
Browse files Browse the repository at this point in the history
Place kernel stack in the bottom of addressable RAM. This will provide a
natural guard against stack overflows.

On systems with ROM, the bottom 512 bytes are reserved in kernel.ld.

On systems without ROM, the bottom 4KiB are reserved in boot.S by
aligning _start_kernel to 4096. The kernel boot code and init_cpus()
then assigns each hart a 512-byte segment for per-hart stack. Boot hart
will use the bottommost of these segments.
  • Loading branch information
rtfb committed May 1, 2024
1 parent 6ae1c82 commit 78a565d
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 57 deletions.
6 changes: 0 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,10 @@ $(OUT)/os_sifive_u32: ${OS_SIFIVE_U32_DEPS}
-g -include include/machine/qemu_u.h \
${OS_SIFIVE_U32_DEPS} -o $@

# XXX: investigate why STACK_SIZE=0x100 is not enough on e64
$(OUT)/os_sifive_e: ${OS_SIFIVE_E_DEPS}
$(RISCV64_GCC) -march=rv64g -mabi=lp64 $(GCC_FLAGS) \
-Wl,--defsym,ROM_START=0x20400000 -g \
-Wl,--defsym,RAM_SIZE=0x4000 \
-Wl,--defsym,STACK_SIZE=0x200 \
-include include/machine/qemu_e.h \
${OS_SIFIVE_E_DEPS} -o $@

Expand All @@ -209,7 +207,6 @@ $(OUT)/os_sifive_e32: ${OS_SIFIVE_E32_DEPS}
-Wl,--defsym,ROM_START=0x20400000 \
-Wa,--defsym,XLEN=32 \
-g -Wl,--defsym,RAM_SIZE=0x4000 \
-Wl,--defsym,STACK_SIZE=0x100 \
-include include/machine/qemu_e.h \
${OS_SIFIVE_E32_DEPS} -o $@

Expand All @@ -218,17 +215,14 @@ $(OUT)/os_test_sifive_e32: ${OS_SIFIVE_E32_DEPS}
-Wl,--defsym,ROM_START=0x20400000 \
-Wa,--defsym,XLEN=32 \
-g -Wl,--defsym,RAM_SIZE=0x4000 \
-Wl,--defsym,STACK_SIZE=0x100 \
-DHARDCODED_TEST=0x1001 \
-include include/machine/qemu_e.h \
${OS_SIFIVE_E32_DEPS} -o $@

# XXX: investigate why STACK_SIZE=0x100 is not enough on e64
$(OUT)/os_test_sifive_e: ${OS_SIFIVE_E_DEPS}
$(RISCV64_GCC) -march=rv64g -mabi=lp64 $(GCC_FLAGS) \
-Wl,--defsym,ROM_START=0x20400000 -g \
-Wl,--defsym,RAM_SIZE=0x4000 \
-Wl,--defsym,STACK_SIZE=0x200 \
-DHARDCODED_TEST=0x1001 \
-include include/machine/qemu_e.h \
${OS_SIFIVE_E_DEPS} -o $@
Expand Down
1 change: 1 addition & 0 deletions include/machine/hifive1-revb.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define BOOT_MODE_M 1
#define HAS_S_MODE 0
#define HAS_BOOTARGS 0
#define CODE_IN_ROM 1

#define PAGE_SIZE 512 // bytes

Expand Down
1 change: 1 addition & 0 deletions include/machine/qemu_e.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define BOOT_MODE_M 1
#define HAS_S_MODE 0
#define HAS_BOOTARGS 0
#define CODE_IN_ROM 1

#define PAGE_SIZE 512 // bytes

Expand Down
3 changes: 1 addition & 2 deletions include/pmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ extern void* user_payload;
extern void* rodata;

// defined in kernel.ld:
extern void* stack_bottom_addr;
extern void* stack_top_addr;
extern void* heap_start;

// Returns the end of RAM address, which is then passed to init_paged_memory.
void* init_pmp();
Expand Down
31 changes: 22 additions & 9 deletions src/boot.S
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ _start:
.balign 8
.dword LINUX_IMAGE_HEADER_TEXT_OFFSET
// Effective size of kernel image
.dword _end - _start
.dword heap_start - _start
.dword 0
.word RISCV_HEADER_VERSION
.word 0
Expand All @@ -36,6 +36,13 @@ _start:
.ascii "RSC\x05"
.word 0

// Reserve the bottom 4KiB of RAM for kernel stack, and place the actual code
// right above it. Only do that if the code is not in ROM. If it is, the bottom
// of RAM is reserved in kernel.ld.
#if !CODE_IN_ROM
.balign 4096
#endif

.globl _start_kernel
_start_kernel:
// from: sifive-interrupt-cookbook-v1p2.pdf
Expand Down Expand Up @@ -67,12 +74,12 @@ _start_kernel:
csrr a0, mhartid // set a0 to mhartid to be compatible with boot in S-mode
#endif
mv tp, a0 // store hartid in tp
// setup stack pointer:
la t0, stack_top_addr // set it at stack_top_addr for hart0,
mv t1, a0 // at stack_top_addr-512 for hart1, etc.
la t0, RAM_START
mv t1, a0
add t1, t1, 1
li t2, 512
mul t1, t1, t2
sub t0, t0, t1
add t0, t0, t1
mv sp, t0

// only allow mhartid==BOOT_HART_ID to jump to the init_segments code;
Expand Down Expand Up @@ -327,11 +334,12 @@ trap_vector: // 3.1.20 Machine Cause Register (mcause

set_global_stack:
// set the same stack location as we do during the boot time.
la t0, stack_top_addr // set it at stack_top_addr for hart0,
mv t1, tp // hartid is preserved in tp, so now t1=mhartid
la t0, RAM_START
mv t1, tp
add t1, t1, 1
li t2, 512
mul t1, t1, t2
sub t0, t0, t1
add t0, t0, t1
mv sp, t0

stack_done:
Expand Down Expand Up @@ -459,7 +467,12 @@ exception:
csrr t1, REG_EPC
csrr t2, REG_TVAL
la t3, user_payload
la t4, stack_top_addr
#if HAS_S_MODE
la t4, _start_kernel
#else
la t4, RAM_START
add t4, t4, 512
#endif
addi sp, sp, -5*REGSZ
OP_STOR t0, 0*REGSZ(sp)
OP_STOR t1, 1*REGSZ(sp)
Expand Down
5 changes: 4 additions & 1 deletion src/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

cpu_t cpus[NUM_HARTS];

// defined in kernel.ld:
extern void* RAM_START;

void init_cpus() {
memset(&cpus, sizeof(cpus), 0);
for (int i = 0; i < NUM_HARTS; i++) {
cpus[i].context.regs[REG_SP] = (regsize_t)(&stack_top_addr) - i*512;
cpus[i].context.regs[REG_SP] = (regsize_t)(&RAM_START) + i*512;
}
}
21 changes: 8 additions & 13 deletions src/kernel.ld
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ SECTIONS
RAM_START = DEFINED(RAM_START) ? RAM_START : 0x80000000;
/* Default to 16MiB of RAM if not specified otherwise: */
RAM_SIZE = DEFINED(RAM_SIZE) ? RAM_SIZE : 0x1000000;
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x1000;
PAGE_SIZE = 0x1000;

/* text: kernel code section */
Expand All @@ -27,9 +26,14 @@ SECTIONS
/* build-id: Store after readonly data */
build_id = .;
.gnu_build_id : { *(.note.gnu.build-id) }
rodata_end = .;

. = DEFINED(ROM_START) ? RAM_START : .;
. = ALIGN(PAGE_SIZE);
/*
* If our code lives in ROM, reserve the bottom 512 bytes of RAM for kernel
* stack and place all RAM sections above it. Otherwise just align data
* section to the page boundary.
*/
. = DEFINED(ROM_START) ? RAM_START + 512 : ALIGN(PAGE_SIZE);
data_start = .;
.data : { *(.data) }
bss_start = .;
Expand All @@ -41,14 +45,5 @@ SECTIONS
.sdata : { *(.sdata) }
.debug : { *(.debug) }

/* align stack to 16 bytes as required by the ABI. It could be relaxed to 4
* bytes on RV32E, but not worth the effort.
*/
. = ALIGN(16);
stack_bottom_addr = .;
. += STACK_SIZE;
stack_top_addr = .;

/* End of uninitialized data segment */
_end = .;
heap_start = .;
}
4 changes: 2 additions & 2 deletions src/pagealloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ paged_mem_t paged_memory;
void init_paged_memory(void* paged_mem_end) {
paged_memory.lock = 0;
// up-align to page size to make all pages naturally aligned:
regsize_t mem = PAGE_ROUND_UP(&stack_top_addr);
paged_memory.unclaimed_start = (regsize_t)&stack_top_addr;
regsize_t mem = PAGE_ROUND_UP(&heap_start);
paged_memory.unclaimed_start = (regsize_t)&heap_start;
paged_memory.unclaimed_end = mem;
int i = 0;
while (mem < (regsize_t)paged_mem_end && i < MAX_PAGES) {
Expand Down
37 changes: 18 additions & 19 deletions src/pmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,29 @@ void* init_pmp() {
unsigned long access_flags = (PMP_X | PMP_W | PMP_R) * PMP_0;
set_pmpcfg0(mode | access_flags);
#else
// define 4 memory address ranges for Physical Memory Protection
// 0 :: [0 .. user_payload]
// 1 :: [user_payload .. .rodata]
// 2 :: [.rodata .. stack_bottom_addr]
// 3 :: [stack_bottom_addr .. paged_mem_end]
// define 3 memory address ranges for Physical Memory Protection
// 0 :: [0 .. .rodata]
// 1 :: [.rodata .. stack_bottom_addr]
// 2 :: [stack_bottom_addr .. paged_mem_end]
set_pmpaddr0(&user_payload);
set_pmpaddr1(&rodata);
set_pmpaddr2(&stack_bottom_addr);
set_pmpaddr3(paged_mem_end);
set_pmpaddr2(paged_mem_end);

// set 4 PMP entries to TOR (Top Of the address Range) addressing mode so that the associated
// address register forms the top of the address range per entry,
// the type of PMP entry is encoded in 2 bits: OFF = 0, TOR = 1, NA4 = 2, NAPOT = 3
// and stored in 3:4 bits of every PMP entry
unsigned long mode = PMP_TOR * (PMP_0 | PMP_1 | PMP_2 | PMP_3);
// set 3 PMP entries to TOR (Top Of the address Range) addressing mode so
// that the associated address register forms the top of the address range
// per entry, the type of PMP entry is encoded in 2 bits: OFF = 0, TOR = 1,
// NA4 = 2, NAPOT = 3 and stored in 3:4 bits of every PMP entry
unsigned long mode = PMP_TOR * (PMP_0 | PMP_1 | PMP_2);

// set access flags for 4 PMP entries:
// 0 :: [0 .. user_payload] 000 M-mode kernel code, no access in U-mode
// 1 :: [user_payload .. .rodata] X0R user code, executable, non-modifiable in U-mode
// 2 :: [.rodata .. stack_bottom_addr] 000 no access in U-mode
// 3 :: [stack_bottom_addr .. paged_mem_end] 0WR user stack, modifiable, but no executable in U-mode
// access (R)ead, (W)rite and e(X)ecute are 1 bit flags and stored in 0:2 bits of every PMP entry
// set access flags for 3 PMP entries:
// 0 :: [0 .. .user_payload] 000 kernel stack and code, no access in U-mode
// 1 :: [.user_payload .. .rodata] X0R user code, executable, non-modifiable in U-mode
// 2 :: [.rodata .. paged_mem_end] 0WR heap, modifiable, but not executable in U-mode

// access (R)ead, (W)rite and e(X)ecute are 1 bit flags and stored in 0:2
// bits of every PMP entry
unsigned long access_flags = ((PMP_X | PMP_R) * PMP_1)
| ((PMP_W | PMP_R) * PMP_3);
| ((PMP_W | PMP_R) * PMP_2);
set_pmpcfg0(mode | access_flags);
#endif
#endif
Expand Down
19 changes: 14 additions & 5 deletions src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@
extern void* RAM_START;
extern void* user_code_start;
extern void* rodata_start;
extern void* rodata_end;
extern void* data_start;
extern void* _end;
extern void* heap_start;

// Note: 512 here means 512 words, which in case of 64 bits makes it a
// PAGE_SIZE, 4KiB. That's because the bottom 4KiB of RAM are reserved for
// kernel stack, and the actual code starts above it.
#define KERNEL_CODE_START (&RAM_START + 512)

// make_kernel_page_table allocates and populates a pagetable for kernel address
// space. It maps all relevant memory ranges with identity mapping (i.e. the
Expand All @@ -36,15 +42,17 @@ void* make_kernel_page_table(page_t *pages, int num_pages) {
}
memset(pagetable, PAGE_SIZE, 0);

map_page_id(pagetable, &RAM_START, PERM_KDATA, -1);

// map kernel code as kernel-executable:
map_range_id(pagetable, &RAM_START, &user_code_start, PERM_KCODE);
map_range_id(pagetable, KERNEL_CODE_START, &user_code_start, PERM_KCODE);

// map rodata:
map_range_id(pagetable, &rodata_start, &data_start, PERM_KRODATA);
map_range_id(pagetable, &rodata_start, (void*)PAGE_ROUND_UP(&rodata_end), PERM_KRODATA);

// map data segment:
void *start = (void*)PAGE_ROUND_DOWN(&data_start);
void *end = (void*)PAGE_ROUND_UP(&_end);
void *end = (void*)PAGE_ROUND_UP(&heap_start);
map_range_id(pagetable, start, end, PERM_KDATA);

// pre-map all pages in kernel space:
Expand All @@ -71,7 +79,8 @@ void init_user_page_table(void *pagetable, uint32_t pid) {
// and if it weren't mapped as executable, it would page fault. It's safe
// to do because the userland will not have access to anything mapped for
// supervisor access anyway.
map_page_id(pagetable, &RAM_START, PERM_KCODE, pid);
map_page_id(pagetable, &RAM_START, PERM_KDATA, pid);
map_page_id(pagetable, KERNEL_CODE_START, PERM_KCODE, pid);
void *trap_frame_page = (void*)PAGE_ROUND_DOWN(&trap_frame);
map_page_id(pagetable, trap_frame_page, PERM_KDATA, pid);
void *paged_memory_page = (void*)PAGE_ROUND_DOWN(&paged_memory);
Expand Down

0 comments on commit 78a565d

Please sign in to comment.